зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1289974 part 1: Device selection for presentation API on Firefox; r=mconley
MozReview-Commit-ID: 8z8xM4hr2F3 --HG-- extra : rebase_source : 35c2b090b3d8711df3ee9a1edbf3fe3a7535199a
This commit is contained in:
Родитель
501a9322a4
Коммит
7bc597d3c0
|
@ -17,4 +17,5 @@ if 'a' in CONFIG['GRE_MILESTONE']:
|
|||
DIRS += [
|
||||
'flyweb',
|
||||
'formautofill',
|
||||
'presentation',
|
||||
]
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* 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 {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const PRESENTATION_DEVICE_PROMPT_PATH =
|
||||
"chrome://presentation/content/PresentationDevicePrompt.jsm";
|
||||
|
||||
function log(aMsg) {
|
||||
// dump("@ Presentation: " + aMsg + "\n");
|
||||
}
|
||||
|
||||
function install(aData, aReason) {
|
||||
}
|
||||
|
||||
function uninstall(aData, aReason) {
|
||||
}
|
||||
|
||||
function startup(aData, aReason) {
|
||||
log("startup");
|
||||
Presentation.init();
|
||||
}
|
||||
|
||||
function shutdown(aData, aReason) {
|
||||
log("shutdown");
|
||||
Presentation.uninit();
|
||||
}
|
||||
|
||||
// Register/unregister a constructor as a factory.
|
||||
function Factory() {}
|
||||
Factory.prototype = {
|
||||
register: function(targetConstructor) {
|
||||
let proto = targetConstructor.prototype;
|
||||
this._classID = proto.classID;
|
||||
|
||||
let factory = XPCOMUtils._getFactory(targetConstructor);
|
||||
this._factory = factory;
|
||||
|
||||
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.registerFactory(proto.classID, proto.classDescription,
|
||||
proto.contractID, factory);
|
||||
},
|
||||
|
||||
unregister: function() {
|
||||
let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.unregisterFactory(this._classID, this._factory);
|
||||
this._factory = null;
|
||||
this._classID = null;
|
||||
},
|
||||
};
|
||||
|
||||
var Presentation = {
|
||||
// PUBLIC APIs
|
||||
init: function() {
|
||||
log("init");
|
||||
// Register PresentationDevicePrompt into a XPCOM component.
|
||||
Cu.import(PRESENTATION_DEVICE_PROMPT_PATH);
|
||||
this._register();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
log("uninit");
|
||||
// Unregister PresentationDevicePrompt XPCOM component.
|
||||
this._unregister();
|
||||
Cu.unload(PRESENTATION_DEVICE_PROMPT_PATH);
|
||||
},
|
||||
|
||||
// PRIVATE APIs
|
||||
_register: function() {
|
||||
log("_register");
|
||||
this._devicePromptFactory = new Factory();
|
||||
this._devicePromptFactory.register(PresentationDevicePrompt);
|
||||
},
|
||||
|
||||
_unregister: function() {
|
||||
log("_unregister");
|
||||
this._devicePromptFactory.unregister();
|
||||
delete this._devicePromptFactory;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,254 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* This is the implementation of nsIPresentationDevicePrompt XPCOM.
|
||||
* It will be registered into a XPCOM component by Presentation.jsm.
|
||||
*
|
||||
* This component will prompt a device selection UI for users to choose which
|
||||
* devices they want to connect, when PresentationRequest is started.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["PresentationDevicePrompt"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// An string bundle for localization.
|
||||
XPCOMUtils.defineLazyGetter(this, "Strings", function() {
|
||||
return Services.strings.createBundle("chrome://presentation/locale/presentation.properties");
|
||||
});
|
||||
// To generate a device selection prompt.
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PermissionUI",
|
||||
"resource:///modules/PermissionUI.jsm");
|
||||
/*
|
||||
* Utils
|
||||
*/
|
||||
function log(aMsg) {
|
||||
// Prefix is useful to grep log.
|
||||
// dump("@ PresentationDevicePrompt: " + aMsg + "\n");
|
||||
}
|
||||
|
||||
function GetString(aName) {
|
||||
return Strings.GetStringFromName(aName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Device Selection UI
|
||||
*/
|
||||
const kNotificationId = "presentation-device-selection";
|
||||
const kNotificationPopupIcon = "chrome://presentation-shared/skin/link.svg";
|
||||
|
||||
// There is no dependancy between kNotificationId and kNotificationAnchorId,
|
||||
// so it's NOT necessary to name them by same prefix
|
||||
// (e.g., presentation-device-selection-notification-icon).
|
||||
const kNotificationAnchorId = "presentation-device-notification-icon";
|
||||
const kNotificationAnchorIcon = "chrome://presentation-shared/skin/link.svg";
|
||||
|
||||
// This will insert our own popupnotification content with the device list
|
||||
// into the displayed popupnotification element.
|
||||
// PopupNotifications.jsm will automatically generate a popupnotification
|
||||
// element whose id is <notification id> + "-notification" and show it,
|
||||
// so kPopupNotificationId must be kNotificationId + "-notification".
|
||||
// Read more detail in PopupNotifications._refreshPanel.
|
||||
const kPopupNotificationId = kNotificationId + "-notification";
|
||||
|
||||
function PresentationPermissionPrompt(aRequest, aDevices) {
|
||||
this.request = aRequest;
|
||||
this._isResponded = false;
|
||||
this._devices = aDevices;
|
||||
}
|
||||
|
||||
PresentationPermissionPrompt.prototype = {
|
||||
__proto__: PermissionUI.PermissionPromptForRequestPrototype,
|
||||
// PUBLIC APIs
|
||||
get browser() {
|
||||
return this.request.chromeEventHandler;
|
||||
},
|
||||
get principal() {
|
||||
return this.request.principal;
|
||||
},
|
||||
get popupOptions() {
|
||||
return {
|
||||
hideNotNow: true,
|
||||
removeOnDismissal: true,
|
||||
popupIconURL: kNotificationPopupIcon, // Icon shown on prompt content
|
||||
eventCallback: (aTopic, aNewBrowser) => {
|
||||
log("eventCallback: " + aTopic);
|
||||
let handler = {
|
||||
// dismissed: () => { // Won't be fired if removeOnDismissal is true.
|
||||
// log("Dismissed by user. Cancel the request.");
|
||||
// },
|
||||
removed: () => {
|
||||
log("Prompt is removed.");
|
||||
if (!this._isResponded) {
|
||||
log("Dismissed by user. Cancel the request.");
|
||||
this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
}
|
||||
},
|
||||
showing: () => {
|
||||
log("Prompt is showing.");
|
||||
// We cannot insert the device list at "showing" phase because
|
||||
// the popupnotification content whose id is kPopupNotificationId
|
||||
// is not generated yet.
|
||||
},
|
||||
shown: () => {
|
||||
log("Prompt is shown.");
|
||||
// Insert device selection list into popupnotification element.
|
||||
this._createPopupContent();
|
||||
},
|
||||
};
|
||||
|
||||
// Call the handler for Notification events.
|
||||
handler[aTopic]();
|
||||
},
|
||||
};
|
||||
},
|
||||
get notificationID() {
|
||||
return kNotificationId;
|
||||
},
|
||||
get anchorID() {
|
||||
let chromeDoc = this.browser.ownerDocument;
|
||||
let anchor = chromeDoc.getElementById(kNotificationAnchorId);
|
||||
if (!anchor) {
|
||||
let notificationPopupBox =
|
||||
chromeDoc.getElementById("notification-popup-box");
|
||||
// Icon shown on URL bar
|
||||
let notificationIcon = chromeDoc.createElement("image");
|
||||
notificationIcon.id = kNotificationAnchorId;
|
||||
notificationIcon.setAttribute("src", kNotificationAnchorIcon);
|
||||
notificationIcon.classList.add("notification-anchor-icon");
|
||||
notificationIcon.setAttribute("role", "button");
|
||||
notificationIcon.setAttribute("tooltiptext",
|
||||
GetString("presentation.urlbar.tooltiptext"));
|
||||
notificationIcon.style.filter = "url('chrome://browser/skin/filters.svg#fill')";
|
||||
notificationIcon.style.fill = "currentcolor";
|
||||
notificationIcon.style.opacity = "0.4";
|
||||
notificationPopupBox.appendChild(notificationIcon);
|
||||
}
|
||||
|
||||
return kNotificationAnchorId;
|
||||
},
|
||||
get message() {
|
||||
return GetString("presentation.message", this._domainName);
|
||||
},
|
||||
get promptActions() {
|
||||
return [{
|
||||
label: GetString("presentation.deviceprompt.select.label"),
|
||||
accessKey: GetString("presentation.deviceprompt.select.accessKey"),
|
||||
callback: () => {
|
||||
log("Select");
|
||||
this._isResponded = true;
|
||||
if (!this._listbox || !this._devices.length) {
|
||||
log("No device can be selected!");
|
||||
this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
let device = this._devices[this._listbox.selectedIndex];
|
||||
this.request.select(device);
|
||||
log("device: " + device.name + "(" + device.id + ") is selected!");
|
||||
},
|
||||
}, {
|
||||
label: GetString("presentation.deviceprompt.cancel.label"),
|
||||
accessKey: GetString("presentation.deviceprompt.cancel.accessKey"),
|
||||
callback: () => {
|
||||
log("Cancel selection.");
|
||||
this._isResponded = true;
|
||||
this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
},
|
||||
dismiss: true, // For hideNotNow.
|
||||
}];
|
||||
},
|
||||
// PRIVATE APIs
|
||||
get _domainName() {
|
||||
if (this.principal.URI instanceof Ci.nsIFileURL) {
|
||||
return this.principal.URI.path.split('/')[1];
|
||||
}
|
||||
return this.principal.URI.hostPort;
|
||||
},
|
||||
_createPopupContent: function() {
|
||||
log("_createPopupContent");
|
||||
|
||||
if (!this._devices.length) {
|
||||
log("No available devices can be listed!");
|
||||
return;
|
||||
}
|
||||
|
||||
let chromeDoc = this.browser.ownerDocument;
|
||||
|
||||
let popupnotification = chromeDoc.getElementById(kPopupNotificationId);
|
||||
if (!popupnotification) {
|
||||
log("No available popupnotification element to be inserted!");
|
||||
return;
|
||||
}
|
||||
|
||||
let popupnotificationcontent =
|
||||
chromeDoc.createElement("popupnotificationcontent");
|
||||
|
||||
this._listbox = chromeDoc.createElement("richlistbox");
|
||||
this._listbox.setAttribute("flex", "1");
|
||||
this._devices.forEach((device) => {
|
||||
let listitem = chromeDoc.createElement("richlistitem");
|
||||
let label = chromeDoc.createElement("label");
|
||||
label.setAttribute("value", device.name);
|
||||
listitem.appendChild(label);
|
||||
this._listbox.appendChild(listitem);
|
||||
});
|
||||
|
||||
popupnotificationcontent.appendChild(this._listbox);
|
||||
popupnotification.appendChild(popupnotificationcontent);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* nsIPresentationDevicePrompt
|
||||
*/
|
||||
// For XPCOM registration
|
||||
const PRESENTATIONDEVICEPROMPT_CONTRACTID = "@mozilla.org/presentation-device/prompt;1";
|
||||
const PRESENTATIONDEVICEPROMPT_CID = Components.ID("{388bd149-c919-4a43-b646-d7ec57877689}");
|
||||
|
||||
function PresentationDevicePrompt() {}
|
||||
|
||||
PresentationDevicePrompt.prototype = {
|
||||
// properties required for XPCOM registration:
|
||||
classID: PRESENTATIONDEVICEPROMPT_CID,
|
||||
classDescription: "Presentation API Device Prompt",
|
||||
contractID: PRESENTATIONDEVICEPROMPT_CONTRACTID,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevicePrompt]),
|
||||
|
||||
// This will be fired when window.PresentationRequest(URL).start() is called.
|
||||
promptDeviceSelection: function(aRequest) {
|
||||
log("promptDeviceSelection");
|
||||
|
||||
// Cancel request if no available device.
|
||||
let devices = this._loadDevices();
|
||||
if (!devices.length) {
|
||||
log("No available device.");
|
||||
aRequest.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the prompt to users.
|
||||
let promptUI = new PresentationPermissionPrompt(aRequest, devices);
|
||||
promptUI.prompt();
|
||||
},
|
||||
_loadDevices: function() {
|
||||
let deviceManager = Cc["@mozilla.org/presentation-device/manager;1"]
|
||||
.getService(Ci.nsIPresentationDeviceManager);
|
||||
let devices = deviceManager.getAvailableDevices().QueryInterface(Ci.nsIArray);
|
||||
let list = [];
|
||||
for (let i = 0; i < devices.length; i++) {
|
||||
let device = devices.queryElementAt(i, Ci.nsIPresentationDevice);
|
||||
list.push(device);
|
||||
}
|
||||
|
||||
return list;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
#filter substitution
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>presentation@mozilla.org</em:id>
|
||||
<em:version>1.0.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
||||
<!-- Target Application this theme can install into,
|
||||
with minimum and maximum supported versions. -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<!-- Firefox GUID -->
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
|
||||
<em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Presentation</em:name>
|
||||
<em:description>Discover nearby devices in the browser</em:description>
|
||||
</Description>
|
||||
</RDF>
|
|
@ -0,0 +1,5 @@
|
|||
[features/presentation@mozilla.org] chrome.jar:
|
||||
% content presentation %content/
|
||||
content/ (content/*)
|
||||
% skin presentation-shared classic/1.0 %skin/shared/
|
||||
skin/ (skin/*)
|
|
@ -0,0 +1,6 @@
|
|||
presentation.message=Select one device to send the content.
|
||||
presentation.urlbar.tooltiptext=View the device-selection request
|
||||
presentation.deviceprompt.select.label=Send
|
||||
presentation.deviceprompt.select.accessKey=S
|
||||
presentation.deviceprompt.cancel.label=Cancel
|
||||
presentation.deviceprompt.cancel.accessKey=C
|
|
@ -0,0 +1,8 @@
|
|||
#filter substitution
|
||||
# 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/.
|
||||
|
||||
[features/presentation@mozilla.org] @AB_CD@.jar:
|
||||
% locale presentation @AB_CD@ %locale/@AB_CD@/
|
||||
locale/@AB_CD@/ (en-US/*)
|
|
@ -0,0 +1,7 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
|
@ -0,0 +1,14 @@
|
|||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
||||
|
||||
DIRS += ['locale']
|
||||
|
||||
FINAL_TARGET_FILES.features['presentation@mozilla.org'] += [
|
||||
'bootstrap.js'
|
||||
]
|
||||
|
||||
FINAL_TARGET_PP_FILES.features['presentation@mozilla.org'] += [
|
||||
'install.rdf.in'
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="gray" x="0px" y="0px" width="32px"
|
||||
height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
|
||||
<g id="layer_2">
|
||||
</g>
|
||||
<g id="layer_1">
|
||||
<g>
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.968,9.895c3.293,0,6.117,1.995,7.338,4.841l1.603-1.216
|
||||
c-1.628-3.3-4.994-5.591-8.923-5.591c-3.942,0-7.319,2.305-8.94,5.624l1.594,1.166C10.865,11.883,13.682,9.895,16.968,9.895z
|
||||
M17.135,13.964c1.948,0,3.566,1.397,3.917,3.244l1.779-1.35c-0.849-2.276-3.023-3.904-5.595-3.904
|
||||
c-2.669,0-4.904,1.758-5.677,4.171l1.647,1.205C13.509,15.424,15.145,13.964,17.135,13.964z M8.756,16.025H1.833
|
||||
c-0.737,0-1.729,0.598-1.729,1.335v11.271c0,0.738,0.596,1.335,1.334,1.335h7.318c0.738,0,1.336-0.597,1.336-1.335V17.36
|
||||
C10.092,16.623,9.494,16.025,8.756,16.025z M8.113,27.472c0,0.299-0.243,0.541-0.541,0.541H2.599
|
||||
c-0.298,0-0.541-0.242-0.541-0.541v-8.949c0-0.299,0.243-0.541,0.541-0.541h4.973c0.298,0,0.541,0.242,0.541,0.541V27.472z
|
||||
M15.246,17.992c0,1.064,0.862,1.926,1.926,1.926c1.064,0,1.926-0.862,1.926-1.926c0-1.064-0.862-1.926-1.926-1.926
|
||||
C16.108,16.066,15.246,16.929,15.246,17.992z M29.962,2.034H4.011c-1.102,0-1.996,0.894-1.996,1.996v10.008h4.042V5.937
|
||||
c0-1.102,0.894-1.996,1.996-1.996h17.973c1.103,0,1.996,0.894,1.996,1.996v16.075c0,1.103-0.894,1.996-1.996,1.996H12.089v1.918
|
||||
h17.873c1.102,0,1.996-0.894,1.996-1.996V4.03C31.958,2.927,31.064,2.034,29.962,2.034z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.6 KiB |
|
@ -100,6 +100,7 @@ libs-%:
|
|||
@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
|
||||
@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
|
||||
@$(MAKE) -C ../extensions/pocket/locale AB_CD=$* XPI_NAME=locale-$*
|
||||
@$(MAKE) -C ../extensions/presentation/locale AB_CD=$* XPI_NAME=locale-$*
|
||||
@$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
|
||||
@$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
|
||||
@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
|
||||
|
|
Загрузка…
Ссылка в новой задаче