1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
5 * Shared functions for tests related to invoking external handler applications.
8 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
9 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
11 import { Assert } from "resource://testing-common/Assert.sys.mjs";
15 XPCOMUtils.defineLazyServiceGetter(
17 "gExternalProtocolService",
18 "@mozilla.org/uriloader/external-protocol-service;1",
19 "nsIExternalProtocolService"
21 XPCOMUtils.defineLazyServiceGetter(
24 "@mozilla.org/mime;1",
27 XPCOMUtils.defineLazyServiceGetter(
30 "@mozilla.org/uriloader/handler-service;1",
34 export var HandlerServiceTestUtils = {
36 * Retrieves the names of all the MIME types and protocols configured in the
37 * handler service instance currently under testing.
39 * @return Array of strings like "example/type" or "example-scheme", sorted
40 * alphabetically regardless of category.
42 getAllHandlerInfoTypes() {
44 lazy.gHandlerService.enumerate(),
50 * Retrieves all the configured handlers for MIME types and protocols.
52 * @note The nsIHandlerInfo instances returned by the "enumerate" method
53 * cannot be used for testing because they incorporate information from
54 * the operating system and also from the default nsIHandlerService
55 * instance, independently from what instance is under testing.
57 * @return Array of nsIHandlerInfo instances sorted by their "type" property.
59 getAllHandlerInfos() {
60 return this.getAllHandlerInfoTypes().map(type => this.getHandlerInfo(type));
64 * Retrieves an nsIHandlerInfo for the given MIME type or protocol, which
65 * incorporates information from the operating system and also from the
66 * handler service instance currently under testing.
68 * @note If the handler service instance currently under testing is not the
69 * default one and the requested type is a MIME type, the returned
70 * nsIHandlerInfo will include information from the default
71 * nsIHandlerService instance. This cannot be avoided easily because the
72 * getMIMEInfoFromOS method is not exposed to JavaScript.
75 * MIME type or scheme name of the nsIHandlerInfo to retrieve.
77 * @return The populated nsIHandlerInfo instance.
79 getHandlerInfo(type) {
80 if (type.includes("/")) {
81 // We have to use the getFromTypeAndExtension method because we don't have
82 // access to getMIMEInfoFromOS. This means that we have to reset the data
83 // that may have been imported from the default nsIHandlerService instance
84 // and is not overwritten by fillHandlerInfo later.
85 let handlerInfo = lazy.gMIMEService.getFromTypeAndExtension(type, null);
86 if (AppConstants.platform == "android") {
87 // On Android, the first handler application is always the internal one.
88 while (handlerInfo.possibleApplicationHandlers.length > 1) {
89 handlerInfo.possibleApplicationHandlers.removeElementAt(1);
92 handlerInfo.possibleApplicationHandlers.clear();
94 handlerInfo.setFileExtensions("");
95 // Populate the object from the handler service instance under testing.
96 if (lazy.gHandlerService.exists(handlerInfo)) {
97 lazy.gHandlerService.fillHandlerInfo(handlerInfo, "");
102 // Populate the protocol information from the handler service instance under
103 // testing, like the nsIExternalProtocolService::GetProtocolHandlerInfo
104 // method does on the default nsIHandlerService instance.
105 let osDefaultHandlerFound = {};
107 lazy.gExternalProtocolService.getProtocolHandlerInfoFromOS(
109 osDefaultHandlerFound
111 if (lazy.gHandlerService.exists(handlerInfo)) {
112 lazy.gHandlerService.fillHandlerInfo(handlerInfo, "");
114 lazy.gExternalProtocolService.setProtocolHandlerDefaults(
116 osDefaultHandlerFound.value
123 * Creates an nsIHandlerInfo for the given MIME type or protocol, initialized
124 * to the default values for the current platform.
126 * @note For this method to work, the specified MIME type or protocol must not
127 * be configured in the default handler service instance or the one
128 * under testing, and must not be registered in the operating system.
131 * MIME type or scheme name of the nsIHandlerInfo to create.
133 * @return The blank nsIHandlerInfo instance.
135 getBlankHandlerInfo(type) {
136 let handlerInfo = this.getHandlerInfo(type);
138 let preferredAction, preferredApplicationHandler;
139 let alwaysAskBeforeHandling = true;
141 if (AppConstants.platform == "android") {
142 // On Android, the default preferredAction for MIME types is useHelperApp.
143 // For protocols, we always behave as if an operating system provided
144 // handler existed, and as such we initialize them to useSystemDefault.
145 // This is because the AndroidBridge is not available in xpcshell tests.
146 preferredAction = type.includes("/")
147 ? Ci.nsIHandlerInfo.useHelperApp
148 : Ci.nsIHandlerInfo.useSystemDefault;
149 // On Android, the default handler application is always the internal one.
150 preferredApplicationHandler = {
151 name: "Android chooser",
154 // On Desktop, the default preferredAction for MIME types is saveToDisk,
155 // while for protocols it is alwaysAsk. Since Bug 1735843, for new MIME
156 // types we default to not asking before handling unless a pref is set.
157 alwaysAskBeforeHandling = Services.prefs.getBoolPref(
158 "browser.download.always_ask_before_handling_new_types",
162 if (type.includes("/")) {
163 preferredAction = Ci.nsIHandlerInfo.saveToDisk;
165 preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
166 // we'll always ask before handling protocols
167 alwaysAskBeforeHandling = true;
169 preferredApplicationHandler = null;
172 this.assertHandlerInfoMatches(handlerInfo, {
175 alwaysAskBeforeHandling,
176 preferredApplicationHandler,
182 * Checks whether an nsIHandlerInfo instance matches the provided object.
184 assertHandlerInfoMatches(handlerInfo, expected) {
185 let expectedInterface = expected.type.includes("/")
188 Assert.ok(handlerInfo instanceof expectedInterface);
189 Assert.equal(handlerInfo.type, expected.type);
191 if (!expected.preferredActionOSDependent) {
192 Assert.equal(handlerInfo.preferredAction, expected.preferredAction);
194 handlerInfo.alwaysAskBeforeHandling,
195 expected.alwaysAskBeforeHandling
199 if (expectedInterface == Ci.nsIMIMEInfo) {
200 let fileExtensionsEnumerator = handlerInfo.getFileExtensions();
201 for (let expectedFileExtension of expected.fileExtensions || []) {
202 Assert.equal(fileExtensionsEnumerator.getNext(), expectedFileExtension);
204 Assert.ok(!fileExtensionsEnumerator.hasMore());
207 if (expected.preferredApplicationHandler) {
208 this.assertHandlerAppMatches(
209 handlerInfo.preferredApplicationHandler,
210 expected.preferredApplicationHandler
213 Assert.equal(handlerInfo.preferredApplicationHandler, null);
216 let handlerAppsArrayEnumerator =
217 handlerInfo.possibleApplicationHandlers.enumerate();
218 if (AppConstants.platform == "android") {
219 // On Android, the first handler application is always the internal one.
220 this.assertHandlerAppMatches(handlerAppsArrayEnumerator.getNext(), {
221 name: "Android chooser",
224 for (let expectedHandlerApp of expected.possibleApplicationHandlers || []) {
225 this.assertHandlerAppMatches(
226 handlerAppsArrayEnumerator.getNext(),
230 Assert.ok(!handlerAppsArrayEnumerator.hasMoreElements());
234 * Checks whether an nsIHandlerApp instance matches the provided object.
236 assertHandlerAppMatches(handlerApp, expected) {
237 Assert.ok(handlerApp instanceof Ci.nsIHandlerApp);
238 Assert.equal(handlerApp.name, expected.name);
239 if (expected.executable) {
240 Assert.ok(handlerApp instanceof Ci.nsILocalHandlerApp);
241 Assert.ok(expected.executable.equals(handlerApp.executable));
242 } else if (expected.uriTemplate) {
243 Assert.ok(handlerApp instanceof Ci.nsIWebHandlerApp);
244 Assert.equal(handlerApp.uriTemplate, expected.uriTemplate);
245 } else if (expected.service) {
246 Assert.ok(handlerApp instanceof Ci.nsIDBusHandlerApp);
247 Assert.equal(handlerApp.service, expected.service);
248 Assert.equal(handlerApp.method, expected.method);
249 Assert.equal(handlerApp.dBusInterface, expected.dBusInterface);
250 Assert.equal(handlerApp.objectPath, expected.objectPath);