378 lines
12 KiB
JavaScript
378 lines
12 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/. */
|
|
|
|
const { PrivateBrowsingUtils } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
|
|
);
|
|
const { EnableDelayHelper } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/PromptUtils.sys.mjs"
|
|
);
|
|
|
|
class MozHandler extends window.MozElements.MozRichlistitem {
|
|
static get markup() {
|
|
return `
|
|
<vbox pack="center">
|
|
<html:img alt="" height="32" width="32" loading="lazy" />
|
|
</vbox>
|
|
<vbox flex="1">
|
|
<label class="name"/>
|
|
<label class="description"/>
|
|
</vbox>
|
|
`;
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.textContent = "";
|
|
this.appendChild(this.constructor.fragment);
|
|
this.initializeAttributeInheritance();
|
|
}
|
|
|
|
static get inheritedAttributes() {
|
|
return {
|
|
img: "src=image,disabled",
|
|
".name": "value=name,disabled",
|
|
".description": "value=description,disabled",
|
|
};
|
|
}
|
|
|
|
get label() {
|
|
return `${this.getAttribute("name")} ${this.getAttribute("description")}`;
|
|
}
|
|
}
|
|
|
|
customElements.define("mozapps-handler", MozHandler, {
|
|
extends: "richlistitem",
|
|
});
|
|
|
|
window.addEventListener("DOMContentLoaded", () => dialog.initialize(), {
|
|
once: true,
|
|
});
|
|
|
|
let dialog = {
|
|
/**
|
|
* This function initializes the content of the dialog.
|
|
*/
|
|
initialize() {
|
|
let args = window.arguments[0].wrappedJSObject || window.arguments[0];
|
|
let { handler, outArgs, usePrivateBrowsing, enableButtonDelay } = args;
|
|
|
|
this._handlerInfo = handler.QueryInterface(Ci.nsIHandlerInfo);
|
|
this._outArgs = outArgs;
|
|
|
|
this.isPrivate =
|
|
usePrivateBrowsing ||
|
|
(window.opener && PrivateBrowsingUtils.isWindowPrivate(window.opener));
|
|
|
|
this._dialog = document.querySelector("dialog");
|
|
this._itemChoose = document.getElementById("item-choose");
|
|
this._rememberCheck = document.getElementById("remember");
|
|
|
|
let items = document.getElementById("items");
|
|
items.addEventListener("dblclick", () => this.onDblClick());
|
|
items.addEventListener("select", () => this.updateAcceptButton());
|
|
document
|
|
.getElementById("choose-app-btn")
|
|
.addEventListener("command", () => this.chooseApplication());
|
|
|
|
// Register event listener for the checkbox hint.
|
|
this._rememberCheck.addEventListener("change", () => this.onCheck());
|
|
|
|
document.addEventListener("dialogaccept", () => {
|
|
this.onAccept();
|
|
});
|
|
|
|
// UI is ready, lets populate our list
|
|
this.populateList();
|
|
|
|
this.initL10n();
|
|
|
|
if (enableButtonDelay) {
|
|
this._delayHelper = new EnableDelayHelper({
|
|
disableDialog: () => {
|
|
this._acceptBtnDisabled = true;
|
|
this.updateAcceptButton();
|
|
},
|
|
enableDialog: () => {
|
|
this._acceptBtnDisabled = false;
|
|
this.updateAcceptButton();
|
|
},
|
|
focusTarget: window,
|
|
});
|
|
}
|
|
},
|
|
|
|
initL10n() {
|
|
let rememberLabel = document.getElementById("remember-label");
|
|
document.l10n.setAttributes(rememberLabel, "chooser-dialog-remember", {
|
|
scheme: this._handlerInfo.type,
|
|
});
|
|
|
|
let description = document.getElementById("description");
|
|
document.l10n.setAttributes(description, "chooser-dialog-description", {
|
|
scheme: this._handlerInfo.type,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Populates the list that a user can choose from.
|
|
*/
|
|
populateList: function populateList() {
|
|
var items = document.getElementById("items");
|
|
var possibleHandlers = this._handlerInfo.possibleApplicationHandlers;
|
|
var preferredHandler = this._handlerInfo.preferredApplicationHandler;
|
|
for (let i = possibleHandlers.length - 1; i >= 0; --i) {
|
|
let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
|
|
let elm = document.createXULElement("richlistitem", {
|
|
is: "mozapps-handler",
|
|
});
|
|
elm.setAttribute("name", app.name);
|
|
elm.obj = app;
|
|
|
|
// We defer loading the favicon so it doesn't delay load. The dialog is
|
|
// opened in a SubDialog which will only show on window load.
|
|
if (app instanceof Ci.nsIGIOHandlerApp) {
|
|
elm.setAttribute("image", "moz-icon://" + app.id + "?size=32");
|
|
} else if (app instanceof Ci.nsILocalHandlerApp) {
|
|
// See if we have an nsILocalHandlerApp and set the icon
|
|
let uri = Services.io.newFileURI(app.executable);
|
|
elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32");
|
|
} else if (app instanceof Ci.nsIWebHandlerApp) {
|
|
let uri = Services.io.newURI(app.uriTemplate);
|
|
if (/^https?$/.test(uri.scheme)) {
|
|
// Unfortunately we can't use the favicon service to get the favicon,
|
|
// because the service looks for a record with the exact URL we give
|
|
// it, and users won't have such records for URLs they don't visit,
|
|
// and users won't visit the handler's URL template, they'll only
|
|
// visit URLs derived from that template (i.e. with %s in the template
|
|
// replaced by the URL of the content being handled).
|
|
elm.setAttribute("image", uri.prePath + "/favicon.ico");
|
|
}
|
|
elm.setAttribute("description", uri.prePath);
|
|
|
|
// Check for extensions needing private browsing access before
|
|
// creating UI elements.
|
|
if (this.isPrivate) {
|
|
let policy = WebExtensionPolicy.getByURI(uri);
|
|
if (policy && !policy.privateBrowsingAllowed) {
|
|
elm.setAttribute("disabled", true);
|
|
this.getPrivateBrowsingDisabledLabel().then(label => {
|
|
elm.setAttribute("description", label);
|
|
});
|
|
if (app == preferredHandler) {
|
|
preferredHandler = null;
|
|
}
|
|
}
|
|
}
|
|
} else if (app instanceof Ci.nsIDBusHandlerApp) {
|
|
elm.setAttribute("description", app.method);
|
|
} else if (!(app instanceof Ci.nsIGIOMimeApp)) {
|
|
// We support GIO application handler, but no action required there
|
|
throw new Error("unknown handler type");
|
|
}
|
|
|
|
items.insertBefore(elm, this._itemChoose);
|
|
if (preferredHandler && app == preferredHandler) {
|
|
this.selectedItem = elm;
|
|
}
|
|
}
|
|
|
|
if (this._handlerInfo.hasDefaultHandler) {
|
|
let elm = document.createXULElement("richlistitem", {
|
|
is: "mozapps-handler",
|
|
});
|
|
elm.id = "os-default-handler";
|
|
elm.setAttribute("name", this._handlerInfo.defaultDescription);
|
|
|
|
items.insertBefore(elm, items.firstChild);
|
|
if (
|
|
this._handlerInfo.preferredAction == Ci.nsIHandlerInfo.useSystemDefault
|
|
) {
|
|
this.selectedItem = elm;
|
|
}
|
|
}
|
|
|
|
// Add gio handlers
|
|
if (Cc["@mozilla.org/gio-service;1"]) {
|
|
let gIOSvc = Cc["@mozilla.org/gio-service;1"].getService(
|
|
Ci.nsIGIOService
|
|
);
|
|
var gioApps = gIOSvc.getAppsForURIScheme(this._handlerInfo.type);
|
|
for (let handler of gioApps.enumerate(Ci.nsIHandlerApp)) {
|
|
// OS handler share the same name, it's most likely the same app, skipping...
|
|
if (handler.name == this._handlerInfo.defaultDescription) {
|
|
continue;
|
|
}
|
|
// Check if the handler is already in possibleHandlers
|
|
let appAlreadyInHandlers = false;
|
|
for (let i = possibleHandlers.length - 1; i >= 0; --i) {
|
|
let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
|
|
// nsGIOMimeApp::Equals is able to compare with nsILocalHandlerApp
|
|
if (handler.equals(app)) {
|
|
appAlreadyInHandlers = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!appAlreadyInHandlers) {
|
|
let elm = document.createXULElement("richlistitem", {
|
|
is: "mozapps-handler",
|
|
});
|
|
elm.setAttribute("name", handler.name);
|
|
elm.obj = handler;
|
|
items.insertBefore(elm, this._itemChoose);
|
|
}
|
|
}
|
|
}
|
|
|
|
items.ensureSelectedElementIsVisible();
|
|
},
|
|
|
|
/**
|
|
* Brings up a filepicker and allows a user to choose an application.
|
|
*/
|
|
async chooseApplication() {
|
|
let title = await this.getChooseAppWindowTitle();
|
|
|
|
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
|
fp.init(window.browsingContext, title, Ci.nsIFilePicker.modeOpen);
|
|
fp.appendFilters(Ci.nsIFilePicker.filterApps);
|
|
|
|
fp.open(rv => {
|
|
if (rv == Ci.nsIFilePicker.returnOK && fp.file) {
|
|
let uri = Services.io.newFileURI(fp.file);
|
|
|
|
let handlerApp = Cc[
|
|
"@mozilla.org/uriloader/local-handler-app;1"
|
|
].createInstance(Ci.nsILocalHandlerApp);
|
|
handlerApp.executable = fp.file;
|
|
|
|
// if this application is already in the list, select it and don't add it again
|
|
let parent = document.getElementById("items");
|
|
for (let i = 0; i < parent.childNodes.length; ++i) {
|
|
let elm = parent.childNodes[i];
|
|
if (
|
|
elm.obj instanceof Ci.nsILocalHandlerApp &&
|
|
elm.obj.equals(handlerApp)
|
|
) {
|
|
parent.selectedItem = elm;
|
|
parent.ensureSelectedElementIsVisible();
|
|
return;
|
|
}
|
|
}
|
|
|
|
let elm = document.createXULElement("richlistitem", {
|
|
is: "mozapps-handler",
|
|
});
|
|
elm.setAttribute("name", fp.file.leafName);
|
|
elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32");
|
|
elm.obj = handlerApp;
|
|
|
|
parent.selectedItem = parent.insertBefore(elm, parent.firstChild);
|
|
parent.ensureSelectedElementIsVisible();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Function called when the OK button is pressed.
|
|
*/
|
|
onAccept() {
|
|
this.updateHandlerData(this._rememberCheck.checked);
|
|
this._outArgs.setProperty("openHandler", true);
|
|
},
|
|
|
|
/**
|
|
* Determines if the accept button should be disabled or not
|
|
*/
|
|
updateAcceptButton() {
|
|
this._dialog.setAttribute(
|
|
"buttondisabledaccept",
|
|
this._acceptBtnDisabled || this._itemChoose.selected
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Update the handler info to reflect the user choice.
|
|
* @param {boolean} skipAsk - Whether we should persist the application
|
|
* choice and skip asking next time.
|
|
*/
|
|
updateHandlerData(skipAsk) {
|
|
// We need to make sure that the default is properly set now
|
|
if (this.selectedItem.obj) {
|
|
// default OS handler doesn't have this property
|
|
this._outArgs.setProperty(
|
|
"preferredAction",
|
|
Ci.nsIHandlerInfo.useHelperApp
|
|
);
|
|
this._outArgs.setProperty(
|
|
"preferredApplicationHandler",
|
|
this.selectedItem.obj
|
|
);
|
|
} else {
|
|
this._outArgs.setProperty(
|
|
"preferredAction",
|
|
Ci.nsIHandlerInfo.useSystemDefault
|
|
);
|
|
}
|
|
this._outArgs.setProperty("alwaysAskBeforeHandling", !skipAsk);
|
|
},
|
|
|
|
/**
|
|
* Updates the UI based on the checkbox being checked or not.
|
|
*/
|
|
onCheck() {
|
|
if (document.getElementById("remember").checked) {
|
|
document.getElementById("remember-text").setAttribute("visible", "true");
|
|
} else {
|
|
document.getElementById("remember-text").removeAttribute("visible");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Function called when the user double clicks on an item of the list
|
|
*/
|
|
onDblClick: function onDblClick() {
|
|
if (this.selectedItem == this._itemChoose) {
|
|
this.chooseApplication();
|
|
} else {
|
|
this._dialog.acceptDialog();
|
|
}
|
|
},
|
|
|
|
// Getters / Setters
|
|
|
|
/**
|
|
* Returns/sets the selected element in the richlistbox
|
|
*/
|
|
get selectedItem() {
|
|
return document.getElementById("items").selectedItem;
|
|
},
|
|
set selectedItem(aItem) {
|
|
document.getElementById("items").selectedItem = aItem;
|
|
},
|
|
|
|
/**
|
|
* Lazy l10n getter for the title of the app chooser window
|
|
*/
|
|
async getChooseAppWindowTitle() {
|
|
if (!this._chooseAppWindowTitle) {
|
|
this._chooseAppWindowTitle = await document.l10n.formatValues([
|
|
"choose-other-app-window-title",
|
|
]);
|
|
}
|
|
return this._chooseAppWindowTitle;
|
|
},
|
|
|
|
/**
|
|
* Lazy l10n getter for handler menu items which are disabled due to private
|
|
* browsing.
|
|
*/
|
|
async getPrivateBrowsingDisabledLabel() {
|
|
if (!this._privateBrowsingDisabledLabel) {
|
|
this._privateBrowsingDisabledLabel = await document.l10n.formatValues([
|
|
"choose-dialog-privatebrowsing-disabled",
|
|
]);
|
|
}
|
|
return this._privateBrowsingDisabledLabel;
|
|
},
|
|
};
|