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/. */
7 const EXPORTED_SYMBOLS = ["ContentMetaChild"];
9 // Debounce time in milliseconds - this should be long enough to account for
10 // sync script tags that could appear between desired meta tags
11 const TIMEOUT_DELAY = 1000;
13 const ACCEPTED_PROTOCOLS = ["http:", "https:"];
15 // Possible description tags, listed in order from least favourable to most favourable
16 const DESCRIPTION_RULES = [
17 "twitter:description",
22 // Possible image tags, listed in order from least favourable to most favourable
23 const PREVIEW_IMAGE_RULES = [
28 "og:image:secure_url",
32 * Checks if the incoming meta tag has a greater score than the current best
33 * score by checking the index of the meta tag in the list of rules provided.
35 * @param {Array} aRules
36 * The list of rules for a given type of meta tag
37 * @param {String} aTag
38 * The name or property of the incoming meta tag
39 * @param {String} aEntry
40 * The current best entry for the given meta tag
42 * @returns {Boolean} true if the incoming meta tag is better than the current
43 * best meta tag of that same kind, false otherwise
45 function shouldExtractMetadata(aRules, aTag, aEntry) {
46 return aRules.indexOf(aTag) > aEntry.currMaxScore;
50 * Ensure that the preview image URL is safe and valid before storing
53 * A URL object that needs to be checked for valid principal and protocol
55 * @returns {Boolean} true if the preview URL is safe and can be stored, false otherwise
57 function checkLoadURIStr(aURL) {
58 if (!ACCEPTED_PROTOCOLS.includes(aURL.protocol)) {
62 let ssm = Services.scriptSecurityManager;
63 let principal = ssm.createNullPrincipal({});
64 ssm.checkLoadURIStrWithPrincipal(
67 ssm.DISALLOW_INHERIT_PRINCIPAL
76 * This listens to DOMMetaAdded events and collects relevant metadata about the
77 * meta tag received. Then, it sends the metadata gathered from the meta tags
78 * and the url of the page as it's payload to be inserted into moz_places.
80 class ContentMetaChild extends JSWindowActorChild {
84 // Store a mapping of the best description and preview
85 // image collected so far for a given URL.
86 this.metaTags = new Map();
90 for (let entry of this.metaTags.values()) {
91 entry.timeout.cancel();
97 case "DOMContentLoaded":
98 const metaTags = this.contentWindow.document.querySelectorAll("meta");
99 for (let metaTag of metaTags) {
100 this.onMetaTag(metaTag);
104 this.onMetaTag(event.originalTarget);
111 const window = metaTag.ownerGlobal;
113 // If there's no meta tag, ignore this. Also verify that the window
114 // matches just to be safe.
115 if (!metaTag || !metaTag.ownerDocument || window != this.contentWindow) {
119 const url = metaTag.ownerDocument.documentURI;
121 let name = metaTag.name;
122 let prop = metaTag.getAttributeNS(null, "property");
123 if (!name && !prop) {
127 let tag = name || prop;
129 const entry = this.metaTags.get(url) || {
130 description: { value: null, currMaxScore: -1 },
131 image: { value: null, currMaxScore: -1 },
135 // Malformed meta tag - do not store it
136 const content = metaTag.getAttributeNS(null, "content");
141 if (shouldExtractMetadata(DESCRIPTION_RULES, tag, entry.description)) {
142 // Extract the description
143 entry.description.value = content;
144 entry.description.currMaxScore = DESCRIPTION_RULES.indexOf(tag);
145 } else if (shouldExtractMetadata(PREVIEW_IMAGE_RULES, tag, entry.image)) {
146 // Extract the preview image
149 value = new URL(content, url);
153 if (value && checkLoadURIStr(value)) {
154 entry.image.value = value.href;
155 entry.image.currMaxScore = PREVIEW_IMAGE_RULES.indexOf(tag);
158 // We don't care about other meta tags
162 if (!this.metaTags.has(url)) {
163 this.metaTags.set(url, entry);
167 entry.timeout.delay = TIMEOUT_DELAY;
169 // We want to debounce incoming meta tags until we're certain we have the
170 // best one for description and preview image, and only store that one
171 entry.timeout = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
172 entry.timeout.initWithCallback(
174 entry.timeout = null;
175 this.metaTags.delete(url);
176 // We try to cancel the timers when we get destroyed, but if
177 // there's a race, catch it:
178 if (!this.manager || this.manager.isClosed) {
182 // Save description and preview image to moz_places
183 this.sendAsyncMessage("Meta:SetPageInfo", {
185 description: entry.description.value,
186 previewImageURL: entry.image.value,
189 // Telemetry for recording the size of page metadata
190 let metadataSize = entry.description.value
191 ? entry.description.value.length
193 metadataSize += entry.image.value ? entry.image.value.length : 0;
195 .getHistogramById("PAGE_METADATA_SIZE")
199 Ci.nsITimer.TYPE_ONE_SHOT