PageInfoChild.sys.mjs (12767B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs", 9 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 10 setTimeout: "resource://gre/modules/Timer.sys.mjs", 11 }); 12 13 export class PageInfoChild extends JSWindowActorChild { 14 async receiveMessage(message) { 15 let window = this.contentWindow; 16 let document = window.document; 17 18 //Handles two different types of messages: one for general info (PageInfo:getData) 19 //and one for media info (PageInfo:getMediaData) 20 switch (message.name) { 21 case "PageInfo:getData": { 22 return Promise.resolve({ 23 metaViewRows: this.getMetaInfo(document), 24 docInfo: this.getDocumentInfo(document), 25 windowInfo: this.getWindowInfo(window), 26 }); 27 } 28 case "PageInfo:getMediaData": { 29 return Promise.resolve({ 30 mediaItems: await this.getDocumentMedia(document), 31 }); 32 } 33 case "PageInfo:getPartitionKey": { 34 return Promise.resolve({ 35 partitionKey: await this.getPartitionKey(document), 36 }); 37 } 38 } 39 40 return undefined; 41 } 42 43 getPartitionKey(document) { 44 let partitionKey = document.cookieJarSettings.partitionKey; 45 return partitionKey; 46 } 47 48 getMetaInfo(document) { 49 let metaViewRows = []; 50 51 // Get the meta tags from the page. 52 let metaNodes = document.getElementsByTagName("meta"); 53 54 for (let metaNode of metaNodes) { 55 metaViewRows.push([ 56 metaNode.name || 57 metaNode.httpEquiv || 58 metaNode.getAttribute("property"), 59 metaNode.content, 60 ]); 61 } 62 63 return metaViewRows; 64 } 65 66 getWindowInfo(window) { 67 let windowInfo = {}; 68 windowInfo.isTopWindow = window == window.top; 69 70 let hostName = null; 71 try { 72 hostName = Services.io.newURI(window.location.href).displayHost; 73 } catch (exception) {} 74 75 windowInfo.hostName = hostName; 76 return windowInfo; 77 } 78 79 getDocumentInfo(document) { 80 let docInfo = {}; 81 docInfo.title = document.title; 82 docInfo.location = document.location.toString(); 83 try { 84 docInfo.location = Services.io.newURI( 85 document.location.toString() 86 ).displaySpec; 87 } catch (exception) {} 88 docInfo.referrer = document.referrer; 89 try { 90 if (document.referrer) { 91 docInfo.referrer = Services.io.newURI(document.referrer).displaySpec; 92 } 93 } catch (exception) {} 94 docInfo.compatMode = document.compatMode; 95 docInfo.contentType = document.contentType; 96 docInfo.characterSet = document.characterSet; 97 docInfo.lastModified = document.lastModified; 98 docInfo.principal = document.nodePrincipal; 99 docInfo.cookieJarSettings = lazy.E10SUtils.serializeCookieJarSettings( 100 document.cookieJarSettings 101 ); 102 103 let documentURIObject = {}; 104 documentURIObject.spec = document.documentURIObject.spec; 105 docInfo.documentURIObject = documentURIObject; 106 107 docInfo.isContentWindowPrivate = 108 lazy.PrivateBrowsingUtils.isContentWindowPrivate(document.ownerGlobal); 109 110 return docInfo; 111 } 112 113 /** 114 * Returns an array that stores all mediaItems found in the document 115 * Calls getMediaItems for all nodes within the constructed tree walker and forms 116 * resulting array. 117 */ 118 async getDocumentMedia(document) { 119 let nodeCount = 0; 120 let content = document.ownerGlobal; 121 let iterator = document.createTreeWalker( 122 document, 123 content.NodeFilter.SHOW_ELEMENT 124 ); 125 126 let totalMediaItems = []; 127 128 while (iterator.nextNode()) { 129 let mediaItems = this.getMediaItems(document, iterator.currentNode); 130 131 if (++nodeCount % 500 == 0) { 132 // setTimeout every 500 elements so we don't keep blocking the content process. 133 await new Promise(resolve => lazy.setTimeout(resolve, 10)); 134 } 135 totalMediaItems.push(...mediaItems); 136 } 137 138 return totalMediaItems; 139 } 140 141 getMediaItems(document, elem) { 142 // Check for images defined in CSS (e.g. background, borders) 143 let computedStyle = elem.ownerGlobal.getComputedStyle(elem); 144 // A node can have multiple media items associated with it - for example, 145 // multiple background images. 146 let mediaItems = []; 147 let content = document.ownerGlobal; 148 149 let addMedia = (url, type, alt, el, isBg, altNotProvided = false) => { 150 let element = this.serializeElementInfo(document, url, el, isBg); 151 mediaItems.push({ 152 url, 153 type, 154 alt, 155 altNotProvided, 156 element, 157 isBg, 158 }); 159 }; 160 161 if (computedStyle) { 162 let addImgFunc = (type, urls) => { 163 for (let url of urls) { 164 addMedia(url, type, "", elem, true, true); 165 } 166 }; 167 // FIXME: This is missing properties. See the implementation of 168 // getCSSImageURLs for a list of properties. 169 // 170 // If you don't care about the message you can also pass "all" here and 171 // get all the ones the browser knows about. 172 addImgFunc("bg-img", computedStyle.getCSSImageURLs("background-image")); 173 addImgFunc( 174 "border-img", 175 computedStyle.getCSSImageURLs("border-image-source") 176 ); 177 addImgFunc("list-img", computedStyle.getCSSImageURLs("list-style-image")); 178 addImgFunc("cursor", computedStyle.getCSSImageURLs("cursor")); 179 } 180 181 // One swi^H^H^Hif-else to rule them all. 182 if (content.HTMLImageElement.isInstance(elem)) { 183 addMedia( 184 elem.currentSrc, 185 "img", 186 elem.getAttribute("alt"), 187 elem, 188 false, 189 !elem.hasAttribute("alt") 190 ); 191 } else if (content.SVGImageElement.isInstance(elem)) { 192 // Note: makeURLAbsolute will throw if either the baseURI is not a valid URI 193 // or the URI formed from the baseURI and the URL is not a valid URI. 194 if (elem.href.baseVal) { 195 let href = URL.parse(elem.href.baseVal, elem.baseURI)?.href; 196 if (href) { 197 addMedia(href, "img", "", elem, false); 198 } 199 } 200 } else if (content.HTMLVideoElement.isInstance(elem)) { 201 addMedia(elem.currentSrc, "video", "", elem, false); 202 } else if (content.HTMLAudioElement.isInstance(elem)) { 203 addMedia(elem.currentSrc, "audio", "", elem, false); 204 } else if (content.HTMLLinkElement.isInstance(elem)) { 205 if (elem.rel && /\bicon\b/i.test(elem.rel)) { 206 addMedia(elem.href, "link", "", elem, false); 207 } 208 } else if ( 209 content.HTMLInputElement.isInstance(elem) || 210 content.HTMLButtonElement.isInstance(elem) 211 ) { 212 if (elem.type.toLowerCase() == "image") { 213 addMedia( 214 elem.src, 215 "input", 216 elem.getAttribute("alt"), 217 elem, 218 false, 219 !elem.hasAttribute("alt") 220 ); 221 } 222 } else if (content.HTMLObjectElement.isInstance(elem)) { 223 addMedia(elem.data, "object", this.getValueText(elem), elem, false); 224 } else if (content.HTMLEmbedElement.isInstance(elem)) { 225 addMedia(elem.src, "embed", "", elem, false); 226 } 227 228 return mediaItems; 229 } 230 231 /** 232 * Set up a JSON element object with all the instanceOf and other infomation that 233 * makePreview in pageInfo.js uses to figure out how to display the preview. 234 */ 235 236 serializeElementInfo(document, url, item, isBG) { 237 let result = {}; 238 let content = document.ownerGlobal; 239 240 let imageText; 241 if ( 242 !isBG && 243 !content.SVGImageElement.isInstance(item) && 244 !content.ImageDocument.isInstance(document) 245 ) { 246 imageText = item.title || item.alt; 247 248 if (!imageText && !content.HTMLImageElement.isInstance(item)) { 249 imageText = this.getValueText(item); 250 } 251 } 252 253 result.imageText = imageText; 254 result.longDesc = item.longDesc; 255 result.numFrames = 1; 256 257 if ( 258 content.HTMLObjectElement.isInstance(item) || 259 content.HTMLEmbedElement.isInstance(item) || 260 content.HTMLLinkElement.isInstance(item) 261 ) { 262 result.mimeType = item.type; 263 } 264 265 if ( 266 !result.mimeType && 267 !isBG && 268 item instanceof Ci.nsIImageLoadingContent 269 ) { 270 // Interface for image loading content. 271 let imageRequest = item.getRequest( 272 Ci.nsIImageLoadingContent.CURRENT_REQUEST 273 ); 274 if (imageRequest) { 275 result.mimeType = imageRequest.mimeType; 276 let image = 277 !(imageRequest.imageStatus & imageRequest.STATUS_ERROR) && 278 imageRequest.image; 279 if (image) { 280 result.numFrames = image.numFrames; 281 } 282 } 283 } 284 285 // If we have a data url, get the MIME type from the url. 286 if (!result.mimeType && url.startsWith("data:")) { 287 let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url); 288 if (dataMimeType) { 289 result.mimeType = dataMimeType[1].toLowerCase(); 290 } 291 } 292 293 result.HTMLLinkElement = content.HTMLLinkElement.isInstance(item); 294 result.HTMLInputElement = content.HTMLInputElement.isInstance(item); 295 result.HTMLImageElement = content.HTMLImageElement.isInstance(item); 296 result.HTMLObjectElement = content.HTMLObjectElement.isInstance(item); 297 result.SVGImageElement = content.SVGImageElement.isInstance(item); 298 result.HTMLVideoElement = content.HTMLVideoElement.isInstance(item); 299 result.HTMLAudioElement = content.HTMLAudioElement.isInstance(item); 300 301 if (isBG) { 302 // Items that are showing this image as a background 303 // image might not necessarily have a width or height, 304 // so we'll dynamically generate an image and send up the 305 // natural dimensions. 306 let img = content.document.createElement("img"); 307 img.src = url; 308 result.naturalWidth = img.naturalWidth; 309 result.naturalHeight = img.naturalHeight; 310 } else if (!content.SVGImageElement.isInstance(item)) { 311 // SVG items do not have integer values for height or width, 312 // so we must handle them differently in order to correctly 313 // serialize 314 315 // Otherwise, we can use the current width and height 316 // of the image. 317 result.width = item.width; 318 result.height = item.height; 319 } 320 321 if (content.SVGImageElement.isInstance(item)) { 322 result.SVGImageElementWidth = item.width.baseVal.value; 323 result.SVGImageElementHeight = item.height.baseVal.value; 324 } 325 326 result.baseURI = item.baseURI; 327 328 return result; 329 } 330 331 // Other Misc Stuff 332 // Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html 333 // parse a node to extract the contents of the node 334 getValueText(node) { 335 let valueText = ""; 336 let content = node.ownerGlobal; 337 338 // Form input elements don't generally contain information that is useful to our callers, so return nothing. 339 if ( 340 content.HTMLInputElement.isInstance(node) || 341 content.HTMLSelectElement.isInstance(node) || 342 content.HTMLTextAreaElement.isInstance(node) 343 ) { 344 return valueText; 345 } 346 347 // Otherwise recurse for each child. 348 let length = node.childNodes.length; 349 350 for (let i = 0; i < length; i++) { 351 let childNode = node.childNodes[i]; 352 let nodeType = childNode.nodeType; 353 354 // Text nodes are where the goods are. 355 if (nodeType == content.Node.TEXT_NODE) { 356 valueText += " " + childNode.nodeValue; 357 } else if (nodeType == content.Node.ELEMENT_NODE) { 358 // And elements can have more text inside them. 359 // Images are special, we want to capture the alt text as if the image weren't there. 360 if (content.HTMLImageElement.isInstance(childNode)) { 361 valueText += " " + this.getAltText(childNode); 362 } else { 363 valueText += " " + this.getValueText(childNode); 364 } 365 } 366 } 367 368 return this.stripWS(valueText); 369 } 370 371 // Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html. 372 // Traverse the tree in search of an img or area element and grab its alt tag. 373 getAltText(node) { 374 let altText = ""; 375 376 if (node.alt) { 377 return node.alt; 378 } 379 let length = node.childNodes.length; 380 for (let i = 0; i < length; i++) { 381 if ((altText = this.getAltText(node.childNodes[i]) != undefined)) { 382 // stupid js warning... 383 return altText; 384 } 385 } 386 return ""; 387 } 388 389 // Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html. 390 // Strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space. 391 stripWS(text) { 392 let middleRE = /\s+/g; 393 let endRE = /(^\s+)|(\s+$)/g; 394 395 text = text.replace(middleRE, " "); 396 return text.replace(endRE, ""); 397 } 398 }