tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }