Source code

Revision control

Other Tools

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
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
var EXPORTED_SYMBOLS = ["PageInfoChild"];
6
7
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8
const { XPCOMUtils } = ChromeUtils.import(
10
);
11
12
const { ActorChild } = ChromeUtils.import(
14
);
15
16
XPCOMUtils.defineLazyModuleGetters(this, {
19
});
20
21
class PageInfoChild extends ActorChild {
22
/* nsIMessageListener */
23
receiveMessage(message) {
24
let strings = message.data.strings;
25
let window;
26
27
let frameOuterWindowID = message.data.frameOuterWindowID;
28
29
// If inside frame then get the frame's window and document.
30
if (frameOuterWindowID != undefined) {
31
window = Services.wm.getOuterWindowWithId(frameOuterWindowID);
32
} else {
33
window = message.target.content;
34
}
35
36
let document = window.document;
37
38
let pageInfoData = {
39
metaViewRows: this.getMetaInfo(document),
40
docInfo: this.getDocumentInfo(document),
41
windowInfo: this.getWindowInfo(window),
42
};
43
44
message.target.sendAsyncMessage("PageInfo:data", pageInfoData);
45
46
// Separate step so page info dialog isn't blank while waiting for this to finish.
47
this.getMediaInfo(document, window, strings, message.target);
48
}
49
50
getMetaInfo(document) {
51
let metaViewRows = [];
52
53
// Get the meta tags from the page.
54
let metaNodes = document.getElementsByTagName("meta");
55
56
for (let metaNode of metaNodes) {
57
metaViewRows.push([
58
metaNode.name ||
59
metaNode.httpEquiv ||
60
metaNode.getAttribute("property"),
61
metaNode.content,
62
]);
63
}
64
65
return metaViewRows;
66
}
67
68
getWindowInfo(window) {
69
let windowInfo = {};
70
windowInfo.isTopWindow = window == window.top;
71
72
let hostName = null;
73
try {
74
hostName = Services.io.newURI(window.location.href).displayHost;
75
} catch (exception) {}
76
77
windowInfo.hostName = hostName;
78
return windowInfo;
79
}
80
81
getDocumentInfo(document) {
82
let docInfo = {};
83
docInfo.title = document.title;
84
docInfo.location = document.location.toString();
85
try {
86
docInfo.location = Services.io.newURI(
87
document.location.toString()
88
).displaySpec;
89
} catch (exception) {}
90
docInfo.referrer = document.referrer;
91
try {
92
if (document.referrer) {
93
docInfo.referrer = Services.io.newURI(document.referrer).displaySpec;
94
}
95
} catch (exception) {}
96
docInfo.compatMode = document.compatMode;
97
docInfo.contentType = document.contentType;
98
docInfo.characterSet = document.characterSet;
99
docInfo.lastModified = document.lastModified;
100
docInfo.principal = document.nodePrincipal;
101
102
let documentURIObject = {};
103
documentURIObject.spec = document.documentURIObject.spec;
104
docInfo.documentURIObject = documentURIObject;
105
106
docInfo.isContentWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(
107
document.ownerGlobal
108
);
109
110
return docInfo;
111
}
112
113
// Only called once to get the media tab's media elements from the content page.
114
getMediaInfo(document, window, strings, mm) {
115
let frameList = this.goThroughFrames(document, window);
116
this.processFrames(document, frameList, strings, mm);
117
}
118
119
goThroughFrames(document, window) {
120
let frameList = [document];
121
if (window && window.frames.length > 0) {
122
let num = window.frames.length;
123
for (let i = 0; i < num; i++) {
124
// Recurse through the frames.
125
frameList = frameList.concat(
126
this.goThroughFrames(window.frames[i].document, window.frames[i])
127
);
128
}
129
}
130
return frameList;
131
}
132
133
async processFrames(document, frameList, strings, mm) {
134
let nodeCount = 0;
135
let content = document.ownerGlobal;
136
for (let doc of frameList) {
137
let iterator = doc.createTreeWalker(doc, content.NodeFilter.SHOW_ELEMENT);
138
139
// Goes through all the elements on the doc. imageViewRows takes only the media elements.
140
while (iterator.nextNode()) {
141
let mediaItems = this.getMediaItems(
142
document,
143
strings,
144
iterator.currentNode
145
);
146
147
if (mediaItems.length) {
148
mm.sendAsyncMessage("PageInfo:mediaData", {
149
mediaItems,
150
isComplete: false,
151
});
152
}
153
154
if (++nodeCount % 500 == 0) {
155
// setTimeout every 500 elements so we don't keep blocking the content process.
156
await new Promise(resolve => setTimeout(resolve, 10));
157
}
158
}
159
}
160
// Send that page info media fetching has finished.
161
mm.sendAsyncMessage("PageInfo:mediaData", { isComplete: true });
162
}
163
164
getMediaItems(document, strings, elem) {
165
// Check for images defined in CSS (e.g. background, borders)
166
let computedStyle = elem.ownerGlobal.getComputedStyle(elem);
167
// A node can have multiple media items associated with it - for example,
168
// multiple background images.
169
let mediaItems = [];
170
let content = document.ownerGlobal;
171
172
let addImage = (url, type, alt, el, isBg) => {
173
let element = this.serializeElementInfo(
174
document,
175
url,
176
type,
177
alt,
178
el,
179
isBg
180
);
181
mediaItems.push([url, type, alt, element, isBg]);
182
};
183
184
if (computedStyle) {
185
let addImgFunc = (label, urls) => {
186
for (let url of urls) {
187
addImage(url, label, strings.notSet, elem, true);
188
}
189
};
190
// FIXME: This is missing properties. See the implementation of
191
// getCSSImageURLs for a list of properties.
192
//
193
// If you don't care about the message you can also pass "all" here and
194
// get all the ones the browser knows about.
195
addImgFunc(
196
strings.mediaBGImg,
197
computedStyle.getCSSImageURLs("background-image")
198
);
199
addImgFunc(
200
strings.mediaBorderImg,
201
computedStyle.getCSSImageURLs("border-image-source")
202
);
203
addImgFunc(
204
strings.mediaListImg,
205
computedStyle.getCSSImageURLs("list-style-image")
206
);
207
addImgFunc(strings.mediaCursor, computedStyle.getCSSImageURLs("cursor"));
208
}
209
210
// One swi^H^H^Hif-else to rule them all.
211
if (elem instanceof content.HTMLImageElement) {
212
addImage(
213
elem.src,
214
strings.mediaImg,
215
elem.hasAttribute("alt") ? elem.alt : strings.notSet,
216
elem,
217
false
218
);
219
} else if (elem instanceof content.SVGImageElement) {
220
try {
221
// Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
222
// or the URI formed from the baseURI and the URL is not a valid URI.
223
if (elem.href.baseVal) {
224
let href = Services.io.newURI(
225
elem.href.baseVal,
226
null,
227
Services.io.newURI(elem.baseURI)
228
).spec;
229
addImage(href, strings.mediaImg, "", elem, false);
230
}
231
} catch (e) {}
232
} else if (elem instanceof content.HTMLVideoElement) {
233
addImage(elem.currentSrc, strings.mediaVideo, "", elem, false);
234
} else if (elem instanceof content.HTMLAudioElement) {
235
addImage(elem.currentSrc, strings.mediaAudio, "", elem, false);
236
} else if (elem instanceof content.HTMLLinkElement) {
237
if (elem.rel && /\bicon\b/i.test(elem.rel)) {
238
addImage(elem.href, strings.mediaLink, "", elem, false);
239
}
240
} else if (
241
elem instanceof content.HTMLInputElement ||
242
elem instanceof content.HTMLButtonElement
243
) {
244
if (elem.type.toLowerCase() == "image") {
245
addImage(
246
elem.src,
247
strings.mediaInput,
248
elem.hasAttribute("alt") ? elem.alt : strings.notSet,
249
elem,
250
false
251
);
252
}
253
} else if (elem instanceof content.HTMLObjectElement) {
254
addImage(
255
elem.data,
256
strings.mediaObject,
257
this.getValueText(elem),
258
elem,
259
false
260
);
261
} else if (elem instanceof content.HTMLEmbedElement) {
262
addImage(elem.src, strings.mediaEmbed, "", elem, false);
263
}
264
265
return mediaItems;
266
}
267
268
/**
269
* Set up a JSON element object with all the instanceOf and other infomation that
270
* makePreview in pageInfo.js uses to figure out how to display the preview.
271
*/
272
273
serializeElementInfo(document, url, type, alt, item, isBG) {
274
let result = {};
275
let content = document.ownerGlobal;
276
277
let imageText;
278
if (
279
!isBG &&
280
!(item instanceof content.SVGImageElement) &&
281
!(document instanceof content.ImageDocument)
282
) {
283
imageText = item.title || item.alt;
284
285
if (!imageText && !(item instanceof content.HTMLImageElement)) {
286
imageText = this.getValueText(item);
287
}
288
}
289
290
result.imageText = imageText;
291
result.longDesc = item.longDesc;
292
result.numFrames = 1;
293
294
if (
295
item instanceof content.HTMLObjectElement ||
296
item instanceof content.HTMLEmbedElement ||
297
item instanceof content.HTMLLinkElement
298
) {
299
result.mimeType = item.type;
300
}
301
302
if (
303
!result.mimeType &&
304
!isBG &&
305
item instanceof Ci.nsIImageLoadingContent
306
) {
307
// Interface for image loading content.
308
let imageRequest = item.getRequest(
309
Ci.nsIImageLoadingContent.CURRENT_REQUEST
310
);
311
if (imageRequest) {
312
result.mimeType = imageRequest.mimeType;
313
let image =
314
!(imageRequest.imageStatus & imageRequest.STATUS_ERROR) &&
315
imageRequest.image;
316
if (image) {
317
result.numFrames = image.numFrames;
318
}
319
}
320
}
321
322
// If we have a data url, get the MIME type from the url.
323
if (!result.mimeType && url.startsWith("data:")) {
324
let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
325
if (dataMimeType) {
326
result.mimeType = dataMimeType[1].toLowerCase();
327
}
328
}
329
330
result.HTMLLinkElement = item instanceof content.HTMLLinkElement;
331
result.HTMLInputElement = item instanceof content.HTMLInputElement;
332
result.HTMLImageElement = item instanceof content.HTMLImageElement;
333
result.HTMLObjectElement = item instanceof content.HTMLObjectElement;
334
result.SVGImageElement = item instanceof content.SVGImageElement;
335
result.HTMLVideoElement = item instanceof content.HTMLVideoElement;
336
result.HTMLAudioElement = item instanceof content.HTMLAudioElement;
337
338
if (isBG) {
339
// Items that are showing this image as a background
340
// image might not necessarily have a width or height,
341
// so we'll dynamically generate an image and send up the
342
// natural dimensions.
343
let img = content.document.createElement("img");
344
img.src = url;
345
result.naturalWidth = img.naturalWidth;
346
result.naturalHeight = img.naturalHeight;
347
} else {
348
// Otherwise, we can use the current width and height
349
// of the image.
350
result.width = item.width;
351
result.height = item.height;
352
}
353
354
if (item instanceof content.SVGImageElement) {
355
result.SVGImageElementWidth = item.width.baseVal.value;
356
result.SVGImageElementHeight = item.height.baseVal.value;
357
}
358
359
result.baseURI = item.baseURI;
360
361
return result;
362
}
363
364
// Other Misc Stuff
365
// Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
366
// parse a node to extract the contents of the node
367
getValueText(node) {
368
let valueText = "";
369
let content = node.ownerGlobal;
370
371
// Form input elements don't generally contain information that is useful to our callers, so return nothing.
372
if (
373
node instanceof content.HTMLInputElement ||
374
node instanceof content.HTMLSelectElement ||
375
node instanceof content.HTMLTextAreaElement
376
) {
377
return valueText;
378
}
379
380
// Otherwise recurse for each child.
381
let length = node.childNodes.length;
382
383
for (let i = 0; i < length; i++) {
384
let childNode = node.childNodes[i];
385
let nodeType = childNode.nodeType;
386
387
// Text nodes are where the goods are.
388
if (nodeType == content.Node.TEXT_NODE) {
389
valueText += " " + childNode.nodeValue;
390
} else if (nodeType == content.Node.ELEMENT_NODE) {
391
// And elements can have more text inside them.
392
// Images are special, we want to capture the alt text as if the image weren't there.
393
if (childNode instanceof content.HTMLImageElement) {
394
valueText += " " + this.getAltText(childNode);
395
} else {
396
valueText += " " + this.getValueText(childNode);
397
}
398
}
399
}
400
401
return this.stripWS(valueText);
402
}
403
404
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html.
405
// Traverse the tree in search of an img or area element and grab its alt tag.
406
getAltText(node) {
407
let altText = "";
408
409
if (node.alt) {
410
return node.alt;
411
}
412
let length = node.childNodes.length;
413
for (let i = 0; i < length; i++) {
414
if ((altText = this.getAltText(node.childNodes[i]) != undefined)) {
415
// stupid js warning...
416
return altText;
417
}
418
}
419
return "";
420
}
421
422
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html.
423
// Strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space.
424
stripWS(text) {
425
let middleRE = /\s+/g;
426
let endRE = /(^\s+)|(\s+$)/g;
427
428
text = text.replace(middleRE, " ");
429
return text.replace(endRE, "");
430
}
431
}