summaryrefslogtreecommitdiffstats
path: root/browser/actors/PageInfoChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/actors/PageInfoChild.sys.mjs393
1 files changed, 393 insertions, 0 deletions
diff --git a/browser/actors/PageInfoChild.sys.mjs b/browser/actors/PageInfoChild.sys.mjs
new file mode 100644
index 0000000000..a1a65885d7
--- /dev/null
+++ b/browser/actors/PageInfoChild.sys.mjs
@@ -0,0 +1,393 @@
+/* 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),
+ });
+ }
+ }
+
+ 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;
+ 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.src,
+ "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, "");
+ }
+}