gecko-dev/browser/actors/PageInfoChild.jsm
Tim Huang 9b272a4cb1 Bug 1683244 - Part 1: Fix the issue that save As of multiple selected links in PageInfo media tab doesn't work. r=smaug
The root cause is that it doesn't pass the cookieJarSettings parameter
when calling the internalSave() in pageInfo.js. This patch fixes this by
passing the correct cookieJarSettings which was passed via the
'PageInfo:getData' message.

Differential Revision: https://phabricator.services.mozilla.com/D100130
2020-12-19 22:51:04 +00:00

399 lines
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, {
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
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;
docInfo.cookieJarSettings = E10SUtils.serializeCookieJarSettings(
document.cookieJarSettings
);
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, "");
}
}