Bug 1888590 - Mark some subtests on trusted-types-event-handlers.html as failing...
[gecko.git] / toolkit / actors / ContentMetaChild.sys.mjs
blob929d92db476be2ac367d432b40aafa7cabb74d0c
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/. */
5 // Debounce time in milliseconds - this should be long enough to account for
6 // sync script tags that could appear between desired meta tags
7 const TIMEOUT_DELAY = 1000;
9 const ACCEPTED_PROTOCOLS = ["http:", "https:"];
11 // Possible description tags, listed in order from least favourable to most favourable
12 const DESCRIPTION_RULES = [
13   "twitter:description",
14   "description",
15   "og:description",
18 // Possible image tags, listed in order from least favourable to most favourable
19 const PREVIEW_IMAGE_RULES = [
20   "thumbnail",
21   "twitter:image",
22   "og:image",
23   "og:image:url",
24   "og:image:secure_url",
28  * Checks if the incoming meta tag has a greater score than the current best
29  * score by checking the index of the meta tag in the list of rules provided.
30  *
31  * @param {Array} aRules
32  *          The list of rules for a given type of meta tag
33  * @param {String} aTag
34  *          The name or property of the incoming meta tag
35  * @param {String} aEntry
36  *          The current best entry for the given meta tag
37  *
38  * @returns {Boolean} true if the incoming meta tag is better than the current
39  *                    best meta tag of that same kind, false otherwise
40  */
41 function shouldExtractMetadata(aRules, aTag, aEntry) {
42   return aRules.indexOf(aTag) > aEntry.currMaxScore;
46  * Ensure that the preview image URL is safe and valid before storing
47  *
48  * @param {URL} aURL
49  *          A URL object that needs to be checked for valid principal and protocol
50  *
51  * @returns {Boolean} true if the preview URL is safe and can be stored, false otherwise
52  */
53 function checkLoadURIStr(aURL) {
54   if (!ACCEPTED_PROTOCOLS.includes(aURL.protocol)) {
55     return false;
56   }
57   try {
58     let ssm = Services.scriptSecurityManager;
59     let principal = ssm.createNullPrincipal({});
60     ssm.checkLoadURIStrWithPrincipal(
61       principal,
62       aURL.href,
63       ssm.DISALLOW_INHERIT_PRINCIPAL
64     );
65   } catch (e) {
66     return false;
67   }
68   return true;
72  * This listens to DOMMetaAdded events and collects relevant metadata about the
73  * meta tag received. Then, it sends the metadata gathered from the meta tags
74  * and the url of the page as it's payload to be inserted into moz_places.
75  */
76 export class ContentMetaChild extends JSWindowActorChild {
77   constructor() {
78     super();
80     // Store a mapping of the best description and preview
81     // image collected so far for a given URL.
82     this.metaTags = new Map();
83   }
85   didDestroy() {
86     for (let entry of this.metaTags.values()) {
87       entry.timeout.cancel();
88     }
89   }
91   handleEvent(event) {
92     switch (event.type) {
93       case "DOMContentLoaded":
94         const metaTags = this.contentWindow.document.querySelectorAll("meta");
95         for (let metaTag of metaTags) {
96           this.onMetaTag(metaTag);
97         }
98         break;
99       case "DOMMetaAdded":
100         this.onMetaTag(event.originalTarget);
101         break;
102       default:
103     }
104   }
106   onMetaTag(metaTag) {
107     const window = metaTag.ownerGlobal;
109     // If there's no meta tag, ignore this. Also verify that the window
110     // matches just to be safe.
111     if (!metaTag || !metaTag.ownerDocument || window != this.contentWindow) {
112       return;
113     }
115     const url = metaTag.ownerDocument.documentURI;
117     let name = metaTag.name;
118     let prop = metaTag.getAttributeNS(null, "property");
119     if (!name && !prop) {
120       return;
121     }
123     let tag = name || prop;
125     const entry = this.metaTags.get(url) || {
126       description: { value: null, currMaxScore: -1 },
127       image: { value: null, currMaxScore: -1 },
128       timeout: null,
129     };
131     // Malformed meta tag - do not store it
132     const content = metaTag.getAttributeNS(null, "content");
133     if (!content) {
134       return;
135     }
137     if (shouldExtractMetadata(DESCRIPTION_RULES, tag, entry.description)) {
138       // Extract the description
139       entry.description.value = content;
140       entry.description.currMaxScore = DESCRIPTION_RULES.indexOf(tag);
141     } else if (shouldExtractMetadata(PREVIEW_IMAGE_RULES, tag, entry.image)) {
142       // Extract the preview image
143       let value;
144       try {
145         value = new URL(content, url);
146       } catch (e) {
147         return;
148       }
149       if (value && checkLoadURIStr(value)) {
150         entry.image.value = value.href;
151         entry.image.currMaxScore = PREVIEW_IMAGE_RULES.indexOf(tag);
152       }
153     } else {
154       // We don't care about other meta tags
155       return;
156     }
158     if (!this.metaTags.has(url)) {
159       this.metaTags.set(url, entry);
160     }
162     if (entry.timeout) {
163       entry.timeout.delay = TIMEOUT_DELAY;
164     } else {
165       // We want to debounce incoming meta tags until we're certain we have the
166       // best one for description and preview image, and only store that one
167       entry.timeout = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
168       entry.timeout.initWithCallback(
169         () => {
170           entry.timeout = null;
171           this.metaTags.delete(url);
172           // We try to cancel the timers when we get destroyed, but if
173           // there's a race, catch it:
174           if (!this.manager || this.manager.isClosed) {
175             return;
176           }
178           // Save description and preview image to moz_places
179           this.sendAsyncMessage("Meta:SetPageInfo", {
180             url,
181             description: entry.description.value,
182             previewImageURL: entry.image.value,
183           });
185           // Telemetry for recording the size of page metadata
186           let metadataSize = entry.description.value
187             ? entry.description.value.length
188             : 0;
189           metadataSize += entry.image.value ? entry.image.value.length : 0;
190           Services.telemetry
191             .getHistogramById("PAGE_METADATA_SIZE")
192             .add(metadataSize);
193         },
194         TIMEOUT_DELAY,
195         Ci.nsITimer.TYPE_ONE_SHOT
196       );
197     }
198   }