|
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/. */ |
|
4 |
|
5 const Cu = Components.utils; |
|
6 Cu.import("resource://gre/modules/LoadContextInfo.jsm"); |
|
7 Cu.import("resource://gre/modules/Services.jsm"); |
|
8 |
|
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 } |
|
23 |
|
24 pageInfoTreeView.prototype = { |
|
25 set rowCount(c) { throw "rowCount is a readonly property"; }, |
|
26 get rowCount() { return this.rows; }, |
|
27 |
|
28 setTree: function(tree) |
|
29 { |
|
30 this.tree = tree; |
|
31 }, |
|
32 |
|
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 }, |
|
41 |
|
42 setCellValue: function(row, column, value) |
|
43 { |
|
44 }, |
|
45 |
|
46 setCellText: function(row, column, value) |
|
47 { |
|
48 this.data[row][column.index] = value; |
|
49 }, |
|
50 |
|
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 }, |
|
58 |
|
59 rowCountChanged: function(index, count) |
|
60 { |
|
61 this.tree.rowCountChanged(index, count); |
|
62 }, |
|
63 |
|
64 invalidate: function() |
|
65 { |
|
66 this.tree.invalidate(); |
|
67 }, |
|
68 |
|
69 clear: function() |
|
70 { |
|
71 if (this.tree) |
|
72 this.tree.rowCountChanged(0, -this.rows); |
|
73 this.rows = 0; |
|
74 this.data = [ ]; |
|
75 }, |
|
76 |
|
77 handleCopy: function(row) |
|
78 { |
|
79 return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || ""); |
|
80 }, |
|
81 |
|
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 }, |
|
89 |
|
90 onPageMediaSort : function(columnname) |
|
91 { |
|
92 var tree = document.getElementById(this.treeid); |
|
93 var treecol = tree.columns.getNamedColumn(columnname); |
|
94 |
|
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 ); |
|
105 |
|
106 this.sortcol = treecol.index; |
|
107 }, |
|
108 |
|
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 }; |
|
133 |
|
134 // mmm, yummy. global variables. |
|
135 var gWindow = null; |
|
136 var gDocument = null; |
|
137 var gImageElement = null; |
|
138 |
|
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; |
|
147 |
|
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; |
|
152 |
|
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); |
|
156 |
|
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"; |
|
165 |
|
166 if (col.element.id == "image-address") |
|
167 props += " ltr"; |
|
168 |
|
169 return props; |
|
170 }; |
|
171 |
|
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 }; |
|
184 |
|
185 gImageView.onPageMediaSort = function(columnname) { |
|
186 var tree = document.getElementById(this.treeid); |
|
187 var treecol = tree.columns.getNamedColumn(columnname); |
|
188 |
|
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 } |
|
195 |
|
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 ); |
|
206 |
|
207 this.sortcol = treecol.index; |
|
208 }; |
|
209 |
|
210 var gImageHash = { }; |
|
211 |
|
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; |
|
216 |
|
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"; |
|
220 |
|
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); |
|
226 |
|
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); |
|
232 |
|
233 const nsICookiePermission = Components.interfaces.nsICookiePermission; |
|
234 const nsIPermissionManager = Components.interfaces.nsIPermissionManager; |
|
235 |
|
236 const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs; |
|
237 const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1" |
|
238 |
|
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 } |
|
246 |
|
247 // Interface for image loading content |
|
248 const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent; |
|
249 |
|
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" |
|
256 |
|
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, ""); |
|
260 |
|
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 */ |
|
269 |
|
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 = [ ]; |
|
273 |
|
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 = [ ]; |
|
279 |
|
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 = [ ]; |
|
283 |
|
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 = [ ]; |
|
287 |
|
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 = [ ]; |
|
291 |
|
292 // These functions are called once when the Page Info window is closed. |
|
293 var onUnloadRegistry = [ ]; |
|
294 |
|
295 // These functions are called once when an image preview is shown. |
|
296 var onImagePreviewShown = [ ]; |
|
297 |
|
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"); |
|
320 |
|
321 var args = "arguments" in window && |
|
322 window.arguments.length >= 1 && |
|
323 window.arguments[0]; |
|
324 |
|
325 if (!args || !args.doc) { |
|
326 gWindow = window.opener.content; |
|
327 gDocument = gWindow.document; |
|
328 } |
|
329 |
|
330 // init media view |
|
331 var imageTree = document.getElementById("imagetree"); |
|
332 imageTree.view = gImageView; |
|
333 |
|
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 } |
|
340 |
|
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]); |
|
346 |
|
347 document.getElementById("main-window").setAttribute("relatedUrl", gDocument.location); |
|
348 |
|
349 // do the easy stuff first |
|
350 makeGeneralTab(); |
|
351 |
|
352 // and then the hard stuff |
|
353 makeTabs(gDocument, gWindow); |
|
354 |
|
355 initFeedTab(); |
|
356 onLoadPermission(); |
|
357 |
|
358 /* Call registered overlay init functions */ |
|
359 onLoadRegistry.forEach(function(func) { func(); }); |
|
360 } |
|
361 |
|
362 function resetPageInfo(args) |
|
363 { |
|
364 /* Reset Meta tags part */ |
|
365 gMetaView.clear(); |
|
366 |
|
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 = {}; |
|
377 |
|
378 /* Reset Feeds Tab */ |
|
379 var feedListbox = document.getElementById("feedListbox"); |
|
380 while (feedListbox.firstChild) |
|
381 feedListbox.removeChild(feedListbox.firstChild); |
|
382 |
|
383 /* Call registered overlay reset functions */ |
|
384 onResetRegistry.forEach(function(func) { func(); }); |
|
385 |
|
386 /* Rebuild the data */ |
|
387 loadTab(args); |
|
388 } |
|
389 |
|
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 } |
|
398 |
|
399 /* Call registered overlay unload functions */ |
|
400 onUnloadRegistry.forEach(function(func) { func(); }); |
|
401 } |
|
402 |
|
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 }; |
|
412 |
|
413 var deck = document.getElementById("mainDeck"); |
|
414 var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general"; |
|
415 openHelpLink(helpdoc); |
|
416 } |
|
417 |
|
418 function showTab(id) |
|
419 { |
|
420 var deck = document.getElementById("mainDeck"); |
|
421 var pagel = document.getElementById(id + "Panel"); |
|
422 deck.selectedPanel = pagel; |
|
423 } |
|
424 |
|
425 function loadTab(args) |
|
426 { |
|
427 if (args && args.doc) { |
|
428 gDocument = args.doc; |
|
429 gWindow = gDocument.defaultView; |
|
430 } |
|
431 |
|
432 gImageElement = args && args.imageElement; |
|
433 |
|
434 /* Load the page info */ |
|
435 loadPageInfo(); |
|
436 |
|
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 } |
|
444 |
|
445 function onClickMore() |
|
446 { |
|
447 var radioGrp = document.getElementById("viewGroup"); |
|
448 var radioElt = document.getElementById("securityTab"); |
|
449 radioGrp.selectedItem = radioElt; |
|
450 showTab('security'); |
|
451 } |
|
452 |
|
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 } |
|
469 |
|
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 } |
|
482 |
|
483 function makeGeneralTab() |
|
484 { |
|
485 var title = (gDocument.title) ? gBundle.getFormattedString("pageTitle", [gDocument.title]) : gBundle.getString("noPageTitle"); |
|
486 document.getElementById("titletext").value = title; |
|
487 |
|
488 var url = gDocument.location.toString(); |
|
489 setItemValue("urltext", url); |
|
490 |
|
491 var referrer = ("referrer" in gDocument && gDocument.referrer); |
|
492 setItemValue("refertext", referrer); |
|
493 |
|
494 var mode = ("compatMode" in gDocument && gDocument.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode"; |
|
495 document.getElementById("modetext").value = gBundle.getString(mode); |
|
496 |
|
497 // find out the mime type |
|
498 var mimeType = gDocument.contentType; |
|
499 setItemValue("typetext", mimeType); |
|
500 |
|
501 // get the document characterset |
|
502 var encoding = gDocument.characterSet; |
|
503 document.getElementById("encodingtext").value = encoding; |
|
504 |
|
505 // get the meta tags |
|
506 var metaNodes = gDocument.getElementsByTagName("meta"); |
|
507 var length = metaNodes.length; |
|
508 |
|
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; |
|
520 |
|
521 for (var i = 0; i < length; i++) |
|
522 gMetaView.addRow([metaNodes[i].name || metaNodes[i].httpEquiv, metaNodes[i].content]); |
|
523 |
|
524 metaGroup.collapsed = false; |
|
525 } |
|
526 |
|
527 // get the date of last modification |
|
528 var modifiedText = formatDate(gDocument.lastModified, gStrings.notSet); |
|
529 document.getElementById("modifiedtext").value = modifiedText; |
|
530 |
|
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 }); |
|
542 |
|
543 securityOnLoad(); |
|
544 } |
|
545 |
|
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. |
|
551 |
|
552 var gFrameList = [ ]; |
|
553 |
|
554 function makeTabs(aDocument, aWindow) |
|
555 { |
|
556 goThroughFrames(aDocument, aWindow); |
|
557 processFrames(); |
|
558 } |
|
559 |
|
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 } |
|
569 |
|
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 } |
|
583 |
|
584 function doGrab(iterator) |
|
585 { |
|
586 for (var i = 0; i < 500; ++i) |
|
587 if (!iterator.nextNode()) { |
|
588 processFrames(); |
|
589 return; |
|
590 } |
|
591 |
|
592 setTimeout(doGrab, 10, iterator); |
|
593 } |
|
594 |
|
595 function addImage(url, type, alt, elem, isBg) |
|
596 { |
|
597 if (!url) |
|
598 return; |
|
599 |
|
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); |
|
608 |
|
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 }); |
|
618 |
|
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 } |
|
634 |
|
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, ""); |
|
639 |
|
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 }; |
|
660 |
|
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 } |
|
666 |
|
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); |
|
698 |
|
699 onProcessElement.forEach(function(func) { func(elem); }); |
|
700 |
|
701 return NodeFilter.FILTER_ACCEPT; |
|
702 } |
|
703 |
|
704 //******** Link Stuff |
|
705 function openURL(target) |
|
706 { |
|
707 var url = target.parentNode.childNodes[2].value; |
|
708 window.open(url, "_blank", "chrome"); |
|
709 } |
|
710 |
|
711 function onBeginLinkDrag(event,urlField,descField) |
|
712 { |
|
713 if (event.originalTarget.localName != "treechildren") |
|
714 return; |
|
715 |
|
716 var tree = event.target; |
|
717 if (!("treeBoxObject" in tree)) |
|
718 tree = tree.parentNode; |
|
719 |
|
720 var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY); |
|
721 if (row == -1) |
|
722 return; |
|
723 |
|
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); |
|
729 |
|
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 } |
|
735 |
|
736 //******** Image Stuff |
|
737 function getSelectedRows(tree) |
|
738 { |
|
739 var start = { }; |
|
740 var end = { }; |
|
741 var numRanges = tree.view.selection.getRangeCount(); |
|
742 |
|
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 } |
|
749 |
|
750 return rowArray; |
|
751 } |
|
752 |
|
753 function getSelectedRow(tree) |
|
754 { |
|
755 var rows = getSelectedRows(tree); |
|
756 return (rows.length == 1) ? rows[0] : -1; |
|
757 } |
|
758 |
|
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 }; |
|
773 |
|
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 } |
|
787 |
|
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]; |
|
796 |
|
797 if (url) { |
|
798 var titleKey = "SaveImageTitle"; |
|
799 |
|
800 if (item instanceof HTMLVideoElement) |
|
801 titleKey = "SaveVideoTitle"; |
|
802 else if (item instanceof HTMLAudioElement) |
|
803 titleKey = "SaveAudioTitle"; |
|
804 |
|
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 }; |
|
814 |
|
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); |
|
821 |
|
822 try { |
|
823 uri.QueryInterface(Components.interfaces.nsIURL); |
|
824 dir.append(decodeURIComponent(uri.fileName)); |
|
825 } catch(ex) { |
|
826 /* data: uris */ |
|
827 } |
|
828 |
|
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 } |
|
842 |
|
843 function onBlockImage() |
|
844 { |
|
845 var permissionManager = Components.classes[PERMISSION_CONTRACTID] |
|
846 .getService(nsIPermissionManager); |
|
847 |
|
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 } |
|
855 |
|
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 } |
|
883 |
|
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; |
|
891 |
|
892 setItemValue("imageurltext", url); |
|
893 |
|
894 var imageText; |
|
895 if (!isBG && |
|
896 !(item instanceof SVGImageElement) && |
|
897 !(gDocument instanceof ImageDocument)) { |
|
898 imageText = item.title || item.alt; |
|
899 |
|
900 if (!imageText && !(item instanceof HTMLImageElement)) |
|
901 imageText = getValueText(item); |
|
902 } |
|
903 setItemValue("imagetext", imageText); |
|
904 |
|
905 setItemValue("imagelongdesctext", item.longDesc); |
|
906 |
|
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); |
|
921 |
|
922 var mimeType; |
|
923 var numFrames = 1; |
|
924 if (item instanceof HTMLObjectElement || |
|
925 item instanceof HTMLEmbedElement || |
|
926 item instanceof HTMLLinkElement) |
|
927 mimeType = item.type; |
|
928 |
|
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 } |
|
938 |
|
939 if (!mimeType) |
|
940 mimeType = getContentTypeFromHeaders(cacheEntry); |
|
941 |
|
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 } |
|
948 |
|
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); |
|
971 |
|
972 var imageContainer = document.getElementById("theimagecontainer"); |
|
973 var oldImage = document.getElementById("thepreviewimage"); |
|
974 |
|
975 var isProtocolAllowed = checkProtocol(gImageView.data[row]); |
|
976 |
|
977 var newImage = new Image; |
|
978 newImage.id = "thepreviewimage"; |
|
979 var physWidth = 0, physHeight = 0; |
|
980 var width = 0, height = 0; |
|
981 |
|
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; |
|
989 |
|
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 } |
|
1003 |
|
1004 if (item instanceof SVGImageElement) { |
|
1005 newImage.width = item.width.baseVal.value; |
|
1006 newImage.height = item.height.baseVal.value; |
|
1007 } |
|
1008 |
|
1009 width = newImage.width; |
|
1010 height = newImage.height; |
|
1011 |
|
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; |
|
1022 |
|
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; |
|
1032 |
|
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 } |
|
1042 |
|
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); |
|
1059 |
|
1060 makeBlockImage(url); |
|
1061 |
|
1062 imageContainer.removeChild(oldImage); |
|
1063 imageContainer.appendChild(newImage); |
|
1064 |
|
1065 onImagePreviewShown.forEach(function(func) { func(); }); |
|
1066 }); |
|
1067 } |
|
1068 |
|
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); |
|
1075 |
|
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 } |
|
1094 |
|
1095 var imagePermissionObserver = { |
|
1096 observe: function (aSubject, aTopic, aData) |
|
1097 { |
|
1098 if (document.getElementById("mediaPreviewBox").collapsed) |
|
1099 return; |
|
1100 |
|
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 } |
|
1114 |
|
1115 function getContentTypeFromHeaders(cacheEntryDescriptor) |
|
1116 { |
|
1117 if (!cacheEntryDescriptor) |
|
1118 return null; |
|
1119 |
|
1120 return (/^Content-Type:\s*(.*?)\s*(?:\;|$)/mi |
|
1121 .exec(cacheEntryDescriptor.getMetaDataElement("response-head")))[1]; |
|
1122 } |
|
1123 |
|
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 = ""; |
|
1130 |
|
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; |
|
1136 |
|
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; |
|
1142 |
|
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 } |
|
1155 |
|
1156 return stripWS(valueText); |
|
1157 } |
|
1158 |
|
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 = ""; |
|
1164 |
|
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 } |
|
1173 |
|
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; |
|
1180 |
|
1181 text = text.replace(middleRE, " "); |
|
1182 return text.replace(endRE, ""); |
|
1183 } |
|
1184 |
|
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 } |
|
1195 |
|
1196 function formatNumber(number) |
|
1197 { |
|
1198 return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString() |
|
1199 } |
|
1200 |
|
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); |
|
1206 |
|
1207 var date = new Date(datestr); |
|
1208 if (!date.valueOf()) |
|
1209 return unknown; |
|
1210 |
|
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 } |
|
1216 |
|
1217 function doCopy() |
|
1218 { |
|
1219 if (!gClipboardHelper) |
|
1220 return; |
|
1221 |
|
1222 var elem = document.commandDispatcher.focusedElement; |
|
1223 |
|
1224 if (elem && "treeBoxObject" in elem) { |
|
1225 var view = elem.view; |
|
1226 var selection = view.selection; |
|
1227 var text = [], tmp = ''; |
|
1228 var min = {}, max = {}; |
|
1229 |
|
1230 var count = selection.getRangeCount(); |
|
1231 |
|
1232 for (var i = 0; i < count; i++) { |
|
1233 selection.getRangeAt(i, min, max); |
|
1234 |
|
1235 for (var row = min.value; row <= max.value; row++) { |
|
1236 view.performActionOnRow("copy", row); |
|
1237 |
|
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 } |
|
1247 |
|
1248 function doSelectAll() |
|
1249 { |
|
1250 var elem = document.commandDispatcher.focusedElement; |
|
1251 |
|
1252 if (elem && "treeBoxObject" in elem) |
|
1253 elem.view.selection.selectAll(); |
|
1254 } |
|
1255 |
|
1256 function selectImage() |
|
1257 { |
|
1258 if (!gImageElement) |
|
1259 return; |
|
1260 |
|
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 } |
|
1272 |
|
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 } |