gecko-dev/browser/actors/PageInfoChild.sys.mjs

403 lines
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/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
});
export class PageInfoChild extends JSWindowActorChild {
async receiveMessage(message) {
let window = this.contentWindow;
let document = window.document;
//Handles two different types of messages: one for general info (PageInfo:getData)
//and one for media info (PageInfo:getMediaData)
switch (message.name) {
case "PageInfo:getData": {
return Promise.resolve({
metaViewRows: this.getMetaInfo(document),
docInfo: this.getDocumentInfo(document),
windowInfo: this.getWindowInfo(window),
});
}
case "PageInfo:getMediaData": {
return Promise.resolve({
mediaItems: await this.getDocumentMedia(document),
});
}
case "PageInfo:getPartitionKey": {
return Promise.resolve({
partitionKey: await this.getPartitionKey(document),
});
}
}
return undefined;
}
getPartitionKey(document) {
let partitionKey = document.cookieJarSettings.partitionKey;
return partitionKey;
}
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;
docInfo.cookieJarSettings = lazy.E10SUtils.serializeCookieJarSettings(
document.cookieJarSettings
);
let documentURIObject = {};
documentURIObject.spec = document.documentURIObject.spec;
docInfo.documentURIObject = documentURIObject;
docInfo.isContentWindowPrivate =
lazy.PrivateBrowsingUtils.isContentWindowPrivate(document.ownerGlobal);
return docInfo;
}
/**
* Returns an array that stores all mediaItems found in the document
* Calls getMediaItems for all nodes within the constructed tree walker and forms
* resulting array.
*/
async getDocumentMedia(document) {
let nodeCount = 0;
let content = document.ownerGlobal;
let iterator = document.createTreeWalker(
document,
content.NodeFilter.SHOW_ELEMENT
);
let totalMediaItems = [];
while (iterator.nextNode()) {
let mediaItems = this.getMediaItems(document, iterator.currentNode);
if (++nodeCount % 500 == 0) {
// setTimeout every 500 elements so we don't keep blocking the content process.
await new Promise(resolve => lazy.setTimeout(resolve, 10));
}
totalMediaItems.push(...mediaItems);
}
return totalMediaItems;
}
getMediaItems(document, 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 addMedia = (url, type, alt, el, isBg, altNotProvided = false) => {
let element = this.serializeElementInfo(document, url, el, isBg);
mediaItems.push({
url,
type,
alt,
altNotProvided,
element,
isBg,
});
};
if (computedStyle) {
let addImgFunc = (type, urls) => {
for (let url of urls) {
addMedia(url, type, "", elem, true, 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("bg-img", computedStyle.getCSSImageURLs("background-image"));
addImgFunc(
"border-img",
computedStyle.getCSSImageURLs("border-image-source")
);
addImgFunc("list-img", computedStyle.getCSSImageURLs("list-style-image"));
addImgFunc("cursor", computedStyle.getCSSImageURLs("cursor"));
}
// One swi^H^H^Hif-else to rule them all.
if (content.HTMLImageElement.isInstance(elem)) {
addMedia(
elem.currentSrc,
"img",
elem.getAttribute("alt"),
elem,
false,
!elem.hasAttribute("alt")
);
} else if (content.SVGImageElement.isInstance(elem)) {
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;
addMedia(href, "img", "", elem, false);
}
} catch (e) {}
} else if (content.HTMLVideoElement.isInstance(elem)) {
addMedia(elem.currentSrc, "video", "", elem, false);
} else if (content.HTMLAudioElement.isInstance(elem)) {
addMedia(elem.currentSrc, "audio", "", elem, false);
} else if (content.HTMLLinkElement.isInstance(elem)) {
if (elem.rel && /\bicon\b/i.test(elem.rel)) {
addMedia(elem.href, "link", "", elem, false);
}
} else if (
content.HTMLInputElement.isInstance(elem) ||
content.HTMLButtonElement.isInstance(elem)
) {
if (elem.type.toLowerCase() == "image") {
addMedia(
elem.src,
"input",
elem.getAttribute("alt"),
elem,
false,
!elem.hasAttribute("alt")
);
}
} else if (content.HTMLObjectElement.isInstance(elem)) {
addMedia(elem.data, "object", this.getValueText(elem), elem, false);
} else if (content.HTMLEmbedElement.isInstance(elem)) {
addMedia(elem.src, "embed", "", 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, item, isBG) {
let result = {};
let content = document.ownerGlobal;
let imageText;
if (
!isBG &&
!content.SVGImageElement.isInstance(item) &&
!content.ImageDocument.isInstance(document)
) {
imageText = item.title || item.alt;
if (!imageText && !content.HTMLImageElement.isInstance(item)) {
imageText = this.getValueText(item);
}
}
result.imageText = imageText;
result.longDesc = item.longDesc;
result.numFrames = 1;
if (
content.HTMLObjectElement.isInstance(item) ||
content.HTMLEmbedElement.isInstance(item) ||
content.HTMLLinkElement.isInstance(item)
) {
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 = content.HTMLLinkElement.isInstance(item);
result.HTMLInputElement = content.HTMLInputElement.isInstance(item);
result.HTMLImageElement = content.HTMLImageElement.isInstance(item);
result.HTMLObjectElement = content.HTMLObjectElement.isInstance(item);
result.SVGImageElement = content.SVGImageElement.isInstance(item);
result.HTMLVideoElement = content.HTMLVideoElement.isInstance(item);
result.HTMLAudioElement = content.HTMLAudioElement.isInstance(item);
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 if (!content.SVGImageElement.isInstance(item)) {
// SVG items do not have integer values for height or width,
// so we must handle them differently in order to correctly
// serialize
// Otherwise, we can use the current width and height
// of the image.
result.width = item.width;
result.height = item.height;
}
if (content.SVGImageElement.isInstance(item)) {
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 (
content.HTMLInputElement.isInstance(node) ||
content.HTMLSelectElement.isInstance(node) ||
content.HTMLTextAreaElement.isInstance(node)
) {
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 (content.HTMLImageElement.isInstance(childNode)) {
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, "");
}
}