Bug 1750871 - run mochitest-remote on fission everywhere. r=releng-reviewers,aki
[gecko.git] / dom / base / ContentAreaDropListener.jsm
blobd3d64d9a81f9ec104cf7025fd2691f9cd6c066ef
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 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
6 const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
8 // This component is used for handling dragover and drop of urls.
9 //
10 // It checks to see whether a drop of a url is allowed. For instance, a url
11 // cannot be dropped if it is not a valid uri or the source of the drag cannot
12 // access the uri. This prevents, for example, a source document from tricking
13 // the user into dragging a chrome url.
15 function ContentAreaDropListener() {}
17 ContentAreaDropListener.prototype = {
18   classID: Components.ID("{1f34bc80-1bc7-11d6-a384-d705dd0746fc}"),
19   QueryInterface: ChromeUtils.generateQI(["nsIDroppedLinkHandler"]),
21   _addLink(links, url, name, type) {
22     links.push({ url, name, type });
23   },
25   _addLinksFromItem(links, dt, i) {
26     let types = dt.mozTypesAt(i);
27     let type, data;
29     type = "text/uri-list";
30     if (types.contains(type)) {
31       data = dt.mozGetDataAt(type, i);
32       if (data) {
33         let urls = data.split("\n");
34         for (let url of urls) {
35           // lines beginning with # are comments
36           if (url.startsWith("#")) {
37             continue;
38           }
39           url = url.replace(/^\s+|\s+$/g, "");
40           this._addLink(links, url, url, type);
41         }
42         return;
43       }
44     }
46     type = "text/x-moz-url";
47     if (types.contains(type)) {
48       data = dt.mozGetDataAt(type, i);
49       if (data) {
50         let lines = data.split("\n");
51         for (let i = 0, length = lines.length; i < length; i += 2) {
52           this._addLink(links, lines[i], lines[i + 1], type);
53         }
54         return;
55       }
56     }
58     for (let type of ["text/plain", "text/x-moz-text-internal"]) {
59       if (types.contains(type)) {
60         data = dt.mozGetDataAt(type, i);
61         if (data) {
62           let lines = data.replace(/^\s+|\s+$/gm, "").split("\n");
63           if (!lines.length) {
64             return;
65           }
67           // For plain text, there are 2 cases:
68           //   * if there is at least one URI:
69           //       Add all URIs, ignoring non-URI lines, so that all URIs
70           //       are opened in tabs.
71           //   * if there's no URI:
72           //       Add the entire text as a single entry, so that the entire
73           //       text is searched.
74           let hasURI = false;
75           // We don't care whether we are in a private context, because we are
76           // only using fixedURI and thus there's no risk to use the wrong
77           // search engine.
78           let flags =
79             Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
80             Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
81           for (let line of lines) {
82             let info = Services.uriFixup.getFixupURIInfo(line, flags);
83             if (info.fixedURI) {
84               // Use the original line here, and let the caller decide
85               // whether to perform fixup or not.
86               hasURI = true;
87               this._addLink(links, line, line, type);
88             }
89           }
91           if (!hasURI) {
92             this._addLink(links, data, data, type);
93           }
94           return;
95         }
96       }
97     }
99     // For shortcuts, we want to check for the file type last, so that the
100     // url pointed to in one of the url types is found first before the file
101     // type, which points to the actual file.
102     let files = dt.files;
103     if (files && i < files.length) {
104       this._addLink(
105         links,
106         OS.Path.toFileURI(files[i].mozFullPath),
107         files[i].name,
108         "application/x-moz-file"
109       );
110     }
111   },
113   _getDropLinks(dt) {
114     let links = [];
115     for (let i = 0; i < dt.mozItemCount; i++) {
116       this._addLinksFromItem(links, dt, i);
117     }
118     return links;
119   },
121   _validateURI(dataTransfer, uriString, disallowInherit, triggeringPrincipal) {
122     if (!uriString) {
123       return "";
124     }
126     // Strip leading and trailing whitespace, then try to create a
127     // URI from the dropped string. If that succeeds, we're
128     // dropping a URI and we need to do a security check to make
129     // sure the source document can load the dropped URI.
130     uriString = uriString.replace(/^\s*|\s*$/g, "");
132     // Apply URI fixup so that this validation prevents bad URIs even if the
133     // similar fixup is applied later, especialy fixing typos up will convert
134     // non-URI to URI.
135     // We don't know if the uri comes from a private context, but luckily we
136     // are only using fixedURI, so there's no risk to use the wrong search
137     // engine.
138     let fixupFlags =
139       Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
140       Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
141     let info = Services.uriFixup.getFixupURIInfo(uriString, fixupFlags);
142     if (!info.fixedURI || info.keywordProviderName) {
143       // Loading a keyword search should always be fine for all cases.
144       return uriString;
145     }
146     let uri = info.fixedURI;
148     let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
149       Ci.nsIScriptSecurityManager
150     );
151     let flags = secMan.STANDARD;
152     if (disallowInherit) {
153       flags |= secMan.DISALLOW_INHERIT_PRINCIPAL;
154     }
156     secMan.checkLoadURIWithPrincipal(triggeringPrincipal, uri, flags);
158     // Once we validated, return the URI after fixup, instead of the original
159     // uriString.
160     return uri.spec;
161   },
163   _getTriggeringPrincipalFromDataTransfer(
164     aDataTransfer,
165     fallbackToSystemPrincipal
166   ) {
167     let sourceNode = aDataTransfer.mozSourceNode;
168     if (
169       sourceNode &&
170       (sourceNode.localName !== "browser" ||
171         sourceNode.namespaceURI !==
172           "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
173     ) {
174       // Use sourceNode's principal only if the sourceNode is not browser.
175       //
176       // If sourceNode is browser, the actual triggering principal may be
177       // differ than sourceNode's principal, since sourceNode's principal is
178       // top level document's one and the drag may be triggered from a frame
179       // with different principal.
180       if (sourceNode.nodePrincipal) {
181         return sourceNode.nodePrincipal;
182       }
183     }
185     // First, fallback to mozTriggeringPrincipalURISpec that is set when the
186     // drop comes from another content process.
187     let principalURISpec = aDataTransfer.mozTriggeringPrincipalURISpec;
188     if (!principalURISpec) {
189       // Fallback to either system principal or file principal, supposing
190       // the drop comes from outside of the browser, so that drops of file
191       // URIs are always allowed.
192       //
193       // TODO: Investigate and describe the difference between them,
194       //       or use only one principal. (Bug 1367038)
195       if (fallbackToSystemPrincipal) {
196         let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
197           Ci.nsIScriptSecurityManager
198         );
199         return secMan.getSystemPrincipal();
200       } else {
201         principalURISpec = "file:///";
202       }
203     }
204     let ioService = Cc["@mozilla.org/network/io-service;1"].getService(
205       Ci.nsIIOService
206     );
207     let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
208       Ci.nsIScriptSecurityManager
209     );
210     return secMan.createContentPrincipal(
211       ioService.newURI(principalURISpec),
212       {}
213     );
214   },
216   getTriggeringPrincipal(aEvent) {
217     let dataTransfer = aEvent.dataTransfer;
218     return this._getTriggeringPrincipalFromDataTransfer(dataTransfer, true);
219   },
221   getCSP(aEvent) {
222     let sourceNode = aEvent.dataTransfer.mozSourceNode;
223     if (aEvent.dataTransfer.mozCSP !== null) {
224       return aEvent.dataTransfer.mozCSP;
225     }
227     if (
228       sourceNode &&
229       (sourceNode.localName !== "browser" ||
230         sourceNode.namespaceURI !==
231           "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
232     ) {
233       // Use sourceNode's csp only if the sourceNode is not browser.
234       //
235       // If sourceNode is browser, the actual triggering csp may be differ than sourceNode's csp,
236       // since sourceNode's csp is top level document's one and the drag may be triggered from a
237       // frame with different csp.
238       return sourceNode.csp;
239     }
240     return null;
241   },
243   canDropLink(aEvent, aAllowSameDocument) {
244     if (this._eventTargetIsDisabled(aEvent)) {
245       return false;
246     }
248     let dataTransfer = aEvent.dataTransfer;
249     let types = dataTransfer.types;
250     if (
251       !types.includes("application/x-moz-file") &&
252       !types.includes("text/x-moz-url") &&
253       !types.includes("text/uri-list") &&
254       !types.includes("text/x-moz-text-internal") &&
255       !types.includes("text/plain")
256     ) {
257       return false;
258     }
260     if (aAllowSameDocument) {
261       return true;
262     }
264     let sourceNode = dataTransfer.mozSourceNode;
265     if (!sourceNode) {
266       return true;
267     }
269     // don't allow a drop of a node from the same document onto this one
270     let sourceDocument = sourceNode.ownerDocument;
271     let eventDocument = aEvent.originalTarget.ownerDocument;
272     if (sourceDocument == eventDocument) {
273       return false;
274     }
276     // also check for nodes in other child or sibling frames by checking
277     // if both have the same top window.
278     if (sourceDocument && eventDocument) {
279       if (sourceDocument.defaultView == null) {
280         return true;
281       }
282       let sourceRoot = sourceDocument.defaultView.top;
283       if (sourceRoot && sourceRoot == eventDocument.defaultView.top) {
284         return false;
285       }
286     }
288     return true;
289   },
291   dropLink(aEvent, aName, aDisallowInherit) {
292     aName.value = "";
293     let links = this.dropLinks(aEvent, aDisallowInherit);
294     let url = "";
295     if (links.length > 0) {
296       url = links[0].url;
297       let name = links[0].name;
298       if (name) {
299         aName.value = name;
300       }
301     }
303     return url;
304   },
306   dropLinks(aEvent, aDisallowInherit) {
307     if (aEvent && this._eventTargetIsDisabled(aEvent)) {
308       return [];
309     }
311     let dataTransfer = aEvent.dataTransfer;
312     let links = this._getDropLinks(dataTransfer);
313     let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(
314       dataTransfer,
315       false
316     );
318     for (let link of links) {
319       try {
320         link.url = this._validateURI(
321           dataTransfer,
322           link.url,
323           aDisallowInherit,
324           triggeringPrincipal
325         );
326       } catch (ex) {
327         // Prevent the drop entirely if any of the links are invalid even if
328         // one of them is valid.
329         aEvent.stopPropagation();
330         aEvent.preventDefault();
331         throw ex;
332       }
333     }
335     return links;
336   },
338   validateURIsForDrop(aEvent, aURIs, aDisallowInherit) {
339     let dataTransfer = aEvent.dataTransfer;
340     let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(
341       dataTransfer,
342       false
343     );
345     for (let uri of aURIs) {
346       this._validateURI(
347         dataTransfer,
348         uri,
349         aDisallowInherit,
350         triggeringPrincipal
351       );
352     }
353   },
355   queryLinks(aDataTransfer) {
356     return this._getDropLinks(aDataTransfer);
357   },
359   _eventTargetIsDisabled(aEvent) {
360     let ownerDoc = aEvent.originalTarget.ownerDocument;
361     if (!ownerDoc || !ownerDoc.defaultView) {
362       return false;
363     }
365     return ownerDoc.defaultView.windowUtils.isNodeDisabledForEvents(
366       aEvent.originalTarget
367     );
368   },
371 var EXPORTED_SYMBOLS = ["ContentAreaDropListener"];