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