Skip to content

Commit

Permalink
Bug 1289974 part 1: Device selection for presentation API on Firefox;…
Browse files Browse the repository at this point in the history
… r=mconley

MozReview-Commit-ID: 8z8xM4hr2F3
  • Loading branch information
ChunMinChang committed Nov 10, 2016
1 parent 4610a31 commit fcf3ec8
Show file tree
Hide file tree
Showing 11 changed files with 437 additions and 0 deletions.
1 change: 1 addition & 0 deletions browser/extensions/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ if 'a' in CONFIG['GRE_MILESTONE']:
DIRS += [
'flyweb',
'formautofill',
'presentation',
]
84 changes: 84 additions & 0 deletions browser/extensions/presentation/bootstrap.js
Original file line number Diff line number Diff line change
@@ -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;
},
};
254 changes: 254 additions & 0 deletions browser/extensions/presentation/content/PresentationDevicePrompt.jsm
Original file line number Diff line number Diff line change
@@ -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;
},
};
33 changes: 33 additions & 0 deletions browser/extensions/presentation/install.rdf.in
Original file line number Diff line number Diff line change
@@ -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>[email protected]</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>
5 changes: 5 additions & 0 deletions browser/extensions/presentation/jar.mn
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[features/[email protected]] chrome.jar:
% content presentation %content/
content/ (content/*)
% skin presentation-shared classic/1.0 %skin/shared/
skin/ (skin/*)
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit fcf3ec8

Please sign in to comment.