gecko-dev/browser/actors/PageInfoChild.jsm

432 строки
13 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ["PageInfoChild"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { ActorChild } = ChromeUtils.import(
"resource://gre/modules/ActorChild.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
setTimeout: "resource://gre/modules/Timer.jsm",
});
class PageInfoChild extends ActorChild {
/* nsIMessageListener */
receiveMessage(message) {
let strings = message.data.strings;
let window;
let frameOuterWindowID = message.data.frameOuterWindowID;
// If inside frame then get the frame's window and document.
if (frameOuterWindowID != undefined) {
window = Services.wm.getOuterWindowWithId(frameOuterWindowID);
} else {
window = message.target.content;
}
let document = window.document;
let pageInfoData = {
metaViewRows: this.getMetaInfo(document),
docInfo: this.getDocumentInfo(document),
windowInfo: this.getWindowInfo(window),
};
message.target.sendAsyncMessage("PageInfo:data", pageInfoData);
// Separate step so page info dialog isn't blank while waiting for this to finish.
this.getMediaInfo(document, window, strings, message.target);
}
getMetaInfo(document) {
let metaViewRows = [];
// Get the meta tags from the page.
let metaNodes = document.getElementsByTagName("meta");
for (let metaNode of metaNodes) {
metaViewRows.push([
metaNode.name ||
metaNode.httpEquiv ||
metaNode.getAttribute("property"),
metaNode.content,
]);
}
return metaViewRows;
}
getWindowInfo(window) {
let windowInfo = {};
windowInfo.isTopWindow = window == window.top;
let hostName = null;
try {
hostName = Services.io.newURI(window.location.href).displayHost;
} catch (exception) {}
windowInfo.hostName = hostName;
return windowInfo;
}
getDocumentInfo(document) {
let docInfo = {};
docInfo.title = document.title;
docInfo.location = document.location.toString();
try {
docInfo.location = Services.io.newURI(
document.location.toString()
).displaySpec;
} catch (exception) {}
docInfo.referrer = document.referrer;
try {
if (document.referrer) {
docInfo.referrer = Services.io.newURI(document.referrer).displaySpec;
}
} catch (exception) {}
docInfo.compatMode = document.compatMode;
docInfo.contentType = document.contentType;
docInfo.characterSet = document.characterSet;
docInfo.lastModified = document.lastModified;
docInfo.principal = document.nodePrincipal;
let documentURIObject = {};
documentURIObject.spec = document.documentURIObject.spec;
docInfo.documentURIObject = documentURIObject;
docInfo.isContentWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(
document.ownerGlobal
);
return docInfo;
}
// Only called once to get the media tab's media elements from the content page.
getMediaInfo(document, window, strings, mm) {
let frameList = this.goThroughFrames(document, window);
this.processFrames(document, frameList, strings, mm);
}
goThroughFrames(document, window) {
let frameList = [document];
if (window && window.frames.length > 0) {
let num = window.frames.length;
for (let i = 0; i < num; i++) {
// Recurse through the frames.
frameList = frameList.concat(
this.goThroughFrames(window.frames[i].document, window.frames[i])
);
}
}
return frameList;
}
async processFrames(document, frameList, strings, mm) {
let nodeCount = 0;
let content = document.ownerGlobal;
for (let doc of frameList) {
let iterator = doc.createTreeWalker(doc, content.NodeFilter.SHOW_ELEMENT);
// Goes through all the elements on the doc. imageViewRows takes only the media elements.
while (iterator.nextNode()) {
let mediaItems = this.getMediaItems(
document,
strings,
iterator.currentNode
);
if (mediaItems.length) {
mm.sendAsyncMessage("PageInfo:mediaData", {
mediaItems,
isComplete: false,
});
}
if (++nodeCount % 500 == 0) {
// setTimeout every 500 elements so we don't keep blocking the content process.
await new Promise(resolve => setTimeout(resolve, 10));
}
}
}
// Send that page info media fetching has finished.
mm.sendAsyncMessage("PageInfo:mediaData", { isComplete: true });
}
getMediaItems(document, strings, elem) {
// Check for images defined in CSS (e.g. background, borders)
let computedStyle = elem.ownerGlobal.getComputedStyle(elem);
// A node can have multiple media items associated with it - for example,
// multiple background images.
let mediaItems = [];
let content = document.ownerGlobal;
let addImage = (url, type, alt, el, isBg) => {
let element = this.serializeElementInfo(
document,
url,
type,
alt,
el,
isBg
);
mediaItems.push([url, type, alt, element, isBg]);
};
if (computedStyle) {
let addImgFunc = (label, urls) => {
for (let url of urls) {
addImage(url, label, strings.notSet, elem, true);
}
};
// FIXME: This is missing properties. See the implementation of
// getCSSImageURLs for a list of properties.
//
// If you don't care about the message you can also pass "all" here and
// get all the ones the browser knows about.
addImgFunc(
strings.mediaBGImg,
computedStyle.getCSSImageURLs("background-image")
);
addImgFunc(
strings.mediaBorderImg,
computedStyle.getCSSImageURLs("border-image-source")
);
addImgFunc(
strings.mediaListImg,
computedStyle.getCSSImageURLs("list-style-image")
);
addImgFunc(strings.mediaCursor, computedStyle.getCSSImageURLs("cursor"));
}
// One swi^H^H^Hif-else to rule them all.
if (elem instanceof content.HTMLImageElement) {
addImage(
elem.src,
strings.mediaImg,
elem.hasAttribute("alt") ? elem.alt : strings.notSet,
elem,
false
);
} else if (elem instanceof content.SVGImageElement) {
try {
// Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
// or the URI formed from the baseURI and the URL is not a valid URI.
if (elem.href.baseVal) {
let href = Services.io.newURI(
elem.href.baseVal,
null,
Services.io.newURI(elem.baseURI)
).spec;
addImage(href, strings.mediaImg, "", elem, false);
}
} catch (e) {}
} else if (elem instanceof content.HTMLVideoElement) {
addImage(elem.currentSrc, strings.mediaVideo, "", elem, false);
} else if (elem instanceof content.HTMLAudioElement) {
addImage(elem.currentSrc, strings.mediaAudio, "", elem, false);
} else if (elem instanceof content.HTMLLinkElement) {
if (elem.rel && /\bicon\b/i.test(elem.rel)) {
addImage(elem.href, strings.mediaLink, "", elem, false);
}
} else if (
elem instanceof content.HTMLInputElement ||
elem instanceof content.HTMLButtonElement
) {
if (elem.type.toLowerCase() == "image") {
addImage(
elem.src,
strings.mediaInput,
elem.hasAttribute("alt") ? elem.alt : strings.notSet,
elem,
false
);
}
} else if (elem instanceof content.HTMLObjectElement) {
addImage(
elem.data,
strings.mediaObject,
this.getValueText(elem),
elem,
false
);
} else if (elem instanceof content.HTMLEmbedElement) {
addImage(elem.src, strings.mediaEmbed, "", elem, false);
}
return mediaItems;
}
/**
* Set up a JSON element object with all the instanceOf and other infomation that
* makePreview in pageInfo.js uses to figure out how to display the preview.
*/
serializeElementInfo(document, url, type, alt, item, isBG) {
let result = {};
let content = document.ownerGlobal;
let imageText;
if (
!isBG &&
!(item instanceof content.SVGImageElement) &&
!(document instanceof content.ImageDocument)
) {
imageText = item.title || item.alt;
if (!imageText && !(item instanceof content.HTMLImageElement)) {
imageText = this.getValueText(item);
}
}
result.imageText = imageText;
result.longDesc = item.longDesc;
result.numFrames = 1;
if (
item instanceof content.HTMLObjectElement ||
item instanceof content.HTMLEmbedElement ||
item instanceof content.HTMLLinkElement
) {
result.mimeType = item.type;
}
if (
!result.mimeType &&
!isBG &&
item instanceof Ci.nsIImageLoadingContent
) {
// Interface for image loading content.
let imageRequest = item.getRequest(
Ci.nsIImageLoadingContent.CURRENT_REQUEST
);
if (imageRequest) {
result.mimeType = imageRequest.mimeType;
let image =
!(imageRequest.imageStatus & imageRequest.STATUS_ERROR) &&
imageRequest.image;
if (image) {
result.numFrames = image.numFrames;
}
}
}
// If we have a data url, get the MIME type from the url.
if (!result.mimeType && url.startsWith("data:")) {
let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
if (dataMimeType) {
result.mimeType = dataMimeType[1].toLowerCase();
}
}
result.HTMLLinkElement = item instanceof content.HTMLLinkElement;
result.HTMLInputElement = item instanceof content.HTMLInputElement;
result.HTMLImageElement = item instanceof content.HTMLImageElement;
result.HTMLObjectElement = item instanceof content.HTMLObjectElement;
result.SVGImageElement = item instanceof content.SVGImageElement;
result.HTMLVideoElement = item instanceof content.HTMLVideoElement;
result.HTMLAudioElement = item instanceof content.HTMLAudioElement;
if (isBG) {
// Items that are showing this image as a background
// image might not necessarily have a width or height,
// so we'll dynamically generate an image and send up the
// natural dimensions.
let img = content.document.createElement("img");
img.src = url;
result.naturalWidth = img.naturalWidth;
result.naturalHeight = img.naturalHeight;
} else {
// Otherwise, we can use the current width and height
// of the image.
result.width = item.width;
result.height = item.height;
}
if (item instanceof content.SVGImageElement) {
result.SVGImageElementWidth = item.width.baseVal.value;
result.SVGImageElementHeight = item.height.baseVal.value;
}
result.baseURI = item.baseURI;
return result;
}
// Other Misc Stuff
// Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
// parse a node to extract the contents of the node
getValueText(node) {
let valueText = "";
let content = node.ownerGlobal;
// Form input elements don't generally contain information that is useful to our callers, so return nothing.
if (
node instanceof content.HTMLInputElement ||
node instanceof content.HTMLSelectElement ||
node instanceof content.HTMLTextAreaElement
) {
return valueText;
}
// Otherwise recurse for each child.
let length = node.childNodes.length;
for (let i = 0; i < length; i++) {
let childNode = node.childNodes[i];
let nodeType = childNode.nodeType;
// Text nodes are where the goods are.
if (nodeType == content.Node.TEXT_NODE) {
valueText += " " + childNode.nodeValue;
} else if (nodeType == content.Node.ELEMENT_NODE) {
// And elements can have more text inside them.
// Images are special, we want to capture the alt text as if the image weren't there.
if (childNode instanceof content.HTMLImageElement) {
valueText += " " + this.getAltText(childNode);
} else {
valueText += " " + this.getValueText(childNode);
}
}
}
return this.stripWS(valueText);
}
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html.
// Traverse the tree in search of an img or area element and grab its alt tag.
getAltText(node) {
let altText = "";
if (node.alt) {
return node.alt;
}
let length = node.childNodes.length;
for (let i = 0; i < length; i++) {
if ((altText = this.getAltText(node.childNodes[i]) != undefined)) {
// stupid js warning...
return altText;
}
}
return "";
}
// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html.
// Strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space.
stripWS(text) {
let middleRE = /\s+/g;
let endRE = /(^\s+)|(\s+$)/g;
text = text.replace(middleRE, " ");
return text.replace(endRE, "");
}
}