Wed, 31 Dec 2014 06:09:35 +0100
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;
1002 }
1004 if (item instanceof SVGImageElement) {
1005 newImage.width = item.width.baseVal.value;
1006 newImage.height = item.height.baseVal.value;
1007 }
1009 width = newImage.width;
1010 height = newImage.height;
1012 document.getElementById("theimagecontainer").collapsed = false
1013 document.getElementById("brokenimagecontainer").collapsed = true;
1014 }
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;
1025 }
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;
1035 }
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;
1041 }
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)]);
1051 }
1052 else {
1053 imageSize = gBundle.getFormattedString("mediaDimensions",
1054 [formatNumber(width),
1055 formatNumber(height)]);
1056 }
1057 }
1058 setItemValue("imagedimensiontext", imageSize);
1060 makeBlockImage(url);
1062 imageContainer.removeChild(oldImage);
1063 imageContainer.appendChild(newImage);
1065 onImagePreviewShown.forEach(function(func) { func(); });
1066 });
1067 }
1069 function makeBlockImage(url)
1070 {
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;
1089 }
1090 else
1091 checkbox.hidden = true;
1092 }
1093 }
1095 var imagePermissionObserver = {
1096 observe: function (aSubject, aTopic, aData)
1097 {
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);
1110 }
1111 }
1112 }
1113 }
1115 function getContentTypeFromHeaders(cacheEntryDescriptor)
1116 {
1117 if (!cacheEntryDescriptor)
1118 return null;
1120 return (/^Content-Type:\s*(.*?)\s*(?:\;|$)/mi
1121 .exec(cacheEntryDescriptor.getMetaDataElement("response-head")))[1];
1122 }
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)
1128 {
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);
1153 }
1154 }
1156 return stripWS(valueText);
1157 }
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)
1162 {
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 "";
1172 }
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)
1177 {
1178 var middleRE = /\s+/g;
1179 var endRE = /(^\s+)|(\s+$)/g;
1181 text = text.replace(middleRE, " ");
1182 return text.replace(endRE, "");
1183 }
1185 function setItemValue(id, value)
1186 {
1187 var item = document.getElementById(id);
1188 if (value) {
1189 item.parentNode.collapsed = false;
1190 item.value = value;
1191 }
1192 else
1193 item.parentNode.collapsed = true;
1194 }
1196 function formatNumber(number)
1197 {
1198 return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString()
1199 }
1201 function formatDate(datestr, unknown)
1202 {
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());
1215 }
1217 function doCopy()
1218 {
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");
1242 }
1243 }
1244 gClipboardHelper.copyString(text.join("\n"), document);
1245 }
1246 }
1248 function doSelectAll()
1249 {
1250 var elem = document.commandDispatcher.focusedElement;
1252 if (elem && "treeBoxObject" in elem)
1253 elem.view.selection.selectAll();
1254 }
1256 function selectImage()
1257 {
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;
1269 }
1270 }
1271 }
1273 function checkProtocol(img)
1274 {
1275 var url = img[COL_IMAGE_ADDRESS];
1276 return /^data:image\//i.test(url) ||
1277 /^(https?|ftp|file|about|chrome|resource):/.test(url);
1278 }