fune/browser/components/pagedata/OpenGraphPageData.jsm

106 lines
3 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["OpenGraphPageData"];
const { PageDataCollector } = ChromeUtils.import(
"resource:///modules/pagedata/PageDataCollector.jsm"
);
/**
* @typedef {object} GeneralPageData
* Data about a product.
* @property {string | undefined} title
* The title describing the page.
* @property {string | undefined} site_name
* The name of the site the page is on.
* @property {string | undefined} type
* The type of the object being described by Open Graph. See
* https://ogp.me/#types for a list of possible types.
* @property {string | undefined} image
* A URL pointing to an image that describes the page.
* @property {string | undefined} url
* The permalink to the page.
*/
const RELEVANT_TAGS = ["title", "site_name", "image", "type", "url"];
/**
* Collects Open Graph related data from a page.
*
* TODO: Respond to DOM mutations to trigger recollection.
*/
class OpenGraphPageData extends PageDataCollector {
/**
* @see PageDataCollector.init
*/
async init() {
return this.#collect();
}
/**
* Collects data from the meta tags on the page.
* See https://ogp.me/ for the parsing spec.
*
* @param {NodeList} tags
* A NodeList of Open Graph meta tags.
* @returns {GeneralPageData}
* Data describing the webpage.
*/
#collectOpenGraphTags(tags) {
// Ensure all tags are present in the returned object, even if their values
// are undefined.
let pageData = Object.fromEntries(
RELEVANT_TAGS.map(tag => [tag, undefined])
);
for (let tag of tags) {
// Stripping "og:" from the property name.
let propertyName = tag.getAttribute("property").substring(3);
if (RELEVANT_TAGS.includes(propertyName)) {
pageData[propertyName] = tag.getAttribute("content");
}
}
return pageData;
}
/**
* Collects the existing data from the page.
*
* @returns {Data[]}
*/
#collect() {
/**
* A map from item type to an array of the items found in the page.
*/
let items = new Map();
let insert = (type, item) => {
let data = items.get(type);
if (!data) {
data = [];
items.set(type, data);
}
data.push(item);
};
// Sites can technically define an Open Graph prefix other than `og:`.
// However, `og:` is one of the default RDFa prefixes and it's likely
// uncommon that sites use a custom prefix. If we find that metadata is
// missing for common sites due to this issue, we could consider adding a
// basic RDFa parser.
let openGraphTags = this.document.querySelectorAll("meta[property^='og:'");
if (!openGraphTags.length) {
return [];
}
insert(
PageDataCollector.DATA_TYPE.GENERAL,
this.#collectOpenGraphTags(openGraphTags)
);
return Array.from(items, ([type, data]) => ({ type, data }));
}
}