зеркало из https://github.com/mozilla/gecko-dev.git
395 строки
12 KiB
JavaScript
395 строки
12 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"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
|
setTimeout: "resource://gre/modules/Timer.jsm",
|
|
});
|
|
|
|
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),
|
|
});
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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 => 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 (elem instanceof content.HTMLImageElement) {
|
|
addMedia(
|
|
elem.src,
|
|
"img",
|
|
elem.getAttribute("alt"),
|
|
elem,
|
|
false,
|
|
!elem.hasAttribute("alt")
|
|
);
|
|
} 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;
|
|
addMedia(href, "img", "", elem, false);
|
|
}
|
|
} catch (e) {}
|
|
} else if (elem instanceof content.HTMLVideoElement) {
|
|
addMedia(elem.currentSrc, "video", "", elem, false);
|
|
} else if (elem instanceof content.HTMLAudioElement) {
|
|
addMedia(elem.currentSrc, "audio", "", elem, false);
|
|
} else if (elem instanceof content.HTMLLinkElement) {
|
|
if (elem.rel && /\bicon\b/i.test(elem.rel)) {
|
|
addMedia(elem.href, "link", "", elem, false);
|
|
}
|
|
} else if (
|
|
elem instanceof content.HTMLInputElement ||
|
|
elem instanceof content.HTMLButtonElement
|
|
) {
|
|
if (elem.type.toLowerCase() == "image") {
|
|
addMedia(
|
|
elem.src,
|
|
"input",
|
|
elem.getAttribute("alt"),
|
|
elem,
|
|
false,
|
|
!elem.hasAttribute("alt")
|
|
);
|
|
}
|
|
} else if (elem instanceof content.HTMLObjectElement) {
|
|
addMedia(elem.data, "object", this.getValueText(elem), elem, false);
|
|
} else if (elem instanceof content.HTMLEmbedElement) {
|
|
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 &&
|
|
!(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 if (!(item instanceof content.SVGImageElement)) {
|
|
// 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 (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, "");
|
|
}
|
|
}
|