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:
Chun-Min Chang 2016-11-10 15:33:09 +08:00
Родитель 501a9322a4
Коммит 7bc597d3c0
11 изменённых файлов: 437 добавлений и 0 удалений

Просмотреть файл

@ -17,4 +17,5 @@ if 'a' in CONFIG['GRE_MILESTONE']:
DIRS += [
'flyweb',
'formautofill',
'presentation',
]

84
browser/extensions/presentation/bootstrap.js поставляемый Normal file
Просмотреть файл

@ -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-$*