RE: New Proposal: Incorporating Resume, Event-based Discovery, MessagePort

Hi,

Inspired by the API proposal using MessagePort instead of WindowProxy, here comes up with an alternative API proposal from another perspective for how to evolve Presentation API to meet the new requirements, e.g. multiple display support, session resuming. In this proposal, we follow the API paradigm of EventSource[1], WebSocket[2] and XWLHttpRequest[3] API definitions to abstract each presentation session into a Presentation object, and allow consumer to construct arbitrary number of Presentation objects by manual, so-called 'Constructor-based' approach.

Any feedback and criticism are highly appreciated, including your concerns in constructor-based approach, your preferred API definitions and the possibility to incorporate with the function based approach.

Let's have a look at the sample code snippet followed by some notes and open issues.

* Sample code

/* In opener browsing context */

function showPresentation() {
  var p = new Presentation("http://example.com/player.html");

  p.onstatechange = function() {
 	if (p.state == Presentation.SHOWING) {
 		p.port.postMessage("start");
 		p.port.onmessage = function(msg) {
 			console.log("receive data from presentation browsing context: " + msg.data);
 			if (msg.data == "close")
 				p.close();
 		}
 	}

 	if (p.state == Presentation.CLOSED) {
 		console.log("No available screen is found or error occurs!");
 	}
  }

  // Request UA to show the presentation, and trigger state transition.
  // UA would popup a permission dialog to let user select which screen to be used, 
  // if UA find the url is already showing on the same screen, it would be resumed back.
  p.show(option);
}

// Asynchronously check the screen availability.
navigator.presentation.checkScreenAvailable(function(available) {
  if (available) {
	showPresentation();
  } else {
	console.log("no available screen.")
  }
}

/* In presentation browsing context */

// The message port to remote side.
var remotePort = null;

navigator.presentation.addEventListener("onshow", function(e) {
	remotePort = e.port;
	remotePort.onmessage = function(msg) {
		console.log("message received from opener: " + msg.data);
	}
});

navigator.presentation.addEventListener("onclose", function(){
    // Invalid the message port to remote side.
	remotePort = null;
});

* Some notes:

1. multiple displays support

The multiple displays support is indeed hidden by UA and transparent to the API consumer. Each time calling show() on a Presentation object, UA will popup a permission dialog to let user select which display/screen to be used, once user selects a display, UA would decide how to show the presentation on the selected screen. If the same URL is already showing on the same screen, UA would resume it back. Conceptually, each presentation session is identified uniquely by a pair of url and the selected screen. See the comments in show() method in API IDL.

2. Session resuming

If the URL to be showed is already showing on the external screen (e.g. show as persistent presentation), and later the end-user wants to show the same URL on the same screen, UA would be responsible for resuming this session back. A session resuming is actually a process of
re-establishing the messaing channel to presentation browsing context, so UA would notify that the presentation browsing context is re-showed as Presentation again, and onshow event is fired in presentation browsing context.

When requesting UA to close a presentation, the presentation browsing context would receive 'onclose' event, but may not be closed if the persistent option is true since it may be resumed again by UA later.

* Open issues:

1. How to avoid calling 'start' or 'close' APIs on MessagePort by accident? Do we need some kind of wrapper over MessagePort?

 Since we use MessagePort for message passing between the opener and presentation browsing context, if the message port is accidently closed by "close()" method, the Presentation object might run into an inconsistent state, how can we avoid this or just tell consumer be careful to use MessagePort in Presentation API.

2. Multiple Presentation objects for the same URL?

API consumer is allowed to create arbitrary number of Presentation objects by 'new Presentation', even for the same URL. If more than one Presentation objects for the same URL invoke 'show', UA will only assign the object that invokes 'show' firstly as the owner of presentation session for that URL. The other objects won't have state transistion. For example, 

var p1 = new Presentation(url);
var p2 = new Presentation(url);

p1.show(); // onstatechange on p1 will be triggered.
p2.show(); // no state change happens on p2.

* API IDL

Below is the API IDL draft for your reference.

[Constructor(in DOMString url)]
interface Presentation {
  readonly attribute DOMString url;

  // The presentation is ready for showing on external screen.
  const unsigned short READY = 0;
  // The presentation is showing on the external screen after show() is invoked.
  const unsigned short SHOWING = 1;
  // The presentation is closed, 1) no available screen for show(); or 2) close()
  // method is invoked, or 3) fatal error is detected by UA when showing.
  const unsigned short CLOSED = 2;

  // The presentation state.
  readonly attribute unsigned short state;
  readonly attribute PresentationOption option;

  // Fired when state is changed.
  attribute EventHandler onstatechange;
  
  // Messaging port used to communicate with the browsing context identified by |url|.
  // Only valid when the |state| is SHOWING.
  MessagePort port;
 
  // Request UA to show the presentation on the external screen. UA may popup a permission dialog
  // to ask user for screen selection if have:
  // 1) If it is in READY state and no available screen for presentation, the state is transited to CLOSED.
  // 2) If it is in CLOSED state and no available screen for presentation, do nothing.
  // 3) If it is in SHOWING state, do nothing;
  // 4) If it is in READY or CLOSED state, and there is at least one available external screen for presentation,
  //    a) if the url is not showing on the selected screen yet, UA will show it on the selected screen,
  //       and the state is transited to SHOWING, the port can be used to communicate with presentation
  //       browsing context;
  //    b) if the url is already showing on the selected screen, but UA detects the messaging channel to the
  //       presentation browsing context is not established yet, UA will establish such a channel for communication
  //       and the state is transited to SHOWING. Otherwise, says UA detects the messaging channel is already
  //       established, do nothing.
  void show(PresentationOption option);

  // Request UA to close the presentation:
  // 1) If it is in READY state, the state is transited to CLOSED;
  // 2) If it is in SHOWING state, the state will be transited to CLOSED, and the presentation browsing context
  //    will receive onclose event. If the presentation doesn't have 'persistent:true' option, UA will close the
  //    presentation browsing context, otherwise, only the messaging channel will be closed.
  // 3) If it is already in CLOSED state, do nothing.
  void close();
};

// Represents a global and singleton object for presentation usage.
interface NavigatorPresentation {
  // Fired when this presentation browsing context is showed as a Presentation on external screen, or resumed as a
  // Presentation again. The event type of onshow is MessageEvent[4].
  attribute EventHandler onshow;

  // Fired when the opener browsing context requests closing this presentation browsing context. Once
  // onclose event is fired, the message port received from onshow event becomes invalid.
  attribute EventHandler onclose;

  // Fired when the first external screen is arrived, or the last external screen is removed.
  attribute EventHandler onscreenavailable;

  // Asynchronously check the screen availability.
  void checkScreenAvailable(callback);
};

Links:
[1] http://www.w3.org/TR/eventsource/#the-eventsource-interface 
[2] http://www.w3.org/TR/websockets/ 
[3] https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#interface-xmlhttprequest 
[4] http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#messageevent 

Thanks...Hongbo

> -----Original Message-----
> From: Rottsches, Dominik [mailto:dominik.rottsches@intel.com]
> Sent: Tuesday, January 21, 2014 11:38 PM
> To: public-webscreens@w3.org
> Subject: New Proposal: Incorporating Resume, Event-based Discovery,
> MessagePort
> 
> Hi,
> 
> after the previous discussions and Anton's change proposal we have been
> thinking about how to incorporate this into our spec.
> Below you find our idea on how to incorporate:
> 
> - Discovery, including resuming existing sessions, event-based
> - Handling of multiple displays (representation as a
>   list/sequence)
> - Moving from WindowProxy to MessagePorts for less
>   tight coupling, relaxing implementation requirements
> 
> With that in mind, here is a new bit of example code showing how we envision
> things to work, split into opener context and player side.
> 
> Your feedback and criticism on this proposal is highly welcome.
> We put it out here on the list before starting to edit the spec draft.
> 
> 
> ### On the opener context side:
> 
> 
> var discovery,
>     playerURL = "http://example.com/player.html";
> 
> discovery = navigator.presentation.discoverScreens(playerURL);
> discovery.addEventListener("discover", discovered);
> 
> function discovered(e) {
>   // Here we receive at least one or a list of screens
>   // in either "presenting" or "available" state for our playerURL.
>   // - "presenting" here means, that there is an existing
>   //   persistent session for playerURL on that screen.
>   // - "available" means that using the connect() method the consumer
>   //   of the API can launch the playerURL on this screen.
> 
>   // Discovery is complete, picking the first screen,
>   // we proceed to the next step:
>   resumeOrPresent(e.screens[0]);
> }
> 
> 
> This is the basic discovery procedure: We find screens that are suitable or
> already showing a session for playerURL. After the asynchronous call to
> discoverScreens() the screensDiscovered event handler is called when there is
> at least one screen found.
> 
> Then, in this example, we pick the first screen and move forward to the next
> step, which would be trying to present our playerURL content on it or resume
> an existing session:
> 
> Open issue here: Since there is no good way to choose if there are multiple
> results in e.screens, alternatively we could have only one object here, selected
> by the user during the permission request dialog, e.g. e.selectedScreen.
> 
> 
> function resumeOrPresent(screen) {
>   // Watch for state changes to either "disconnected" or
>   // (see below) "presenting", if we haven't launched a sesion yet.
>   screen.addEventListener("statechange", screenStateChange);
> 
>   if (screen.state == "presenting") {
>     // Screen is already in state "presenting" for our playerURL.
>     // We can resume communication with the session.
>     communicate(screen);
>   } else {
>     // Screen is "available", we can launch our playerURL content on it.
>     screen.present( /* Optionally with options
>                        object { "persistent" : true }. */ );
>   }
> )
> 
> 
> First, we register our event handler for getting notified about any state
> changes, either to "presenting" or "disconnected.
> 
> Now two situations can occur:
> 
> In the case of existing session, the screen is in state "presensting" and we can
> resume communications with it right away.
> 
> The other possibility is that the screen is in "available" state, in which case we
> would like to launch our player content on it. We do that by calling present().
> 
> 
> function screenStateChange(e) {
>   if (screen.state == "presenting")
>     communicate(screen);
> 
>   if (screen.state == "disconnected")
>     console.log("Screen disconnected."); }
> 
> 
> If we previously were in state "available" and had called present(), we now
> reach state "presenting" and can pick up communications with the screen.
> 
> In the case of disconnection or an error resulting from calling present() we end
> up in state "disconnected". So we're no longer presenting on the screen.
> 
> 
> function communicate(screen) {
>     // Communicates with presentation window.
>     screen.port.postMessage(/*...*/);
>     // Receiving messages.
>     screen.port.onmessage = function(e) {
>       console.log(e.data);
>     };
> }
> 
> The communicate function is just an illustration for web messaging between
> the opener context and the screen.
> 
> 
> 
> 
> ### On the presentation window side, e.g. "player.html":
> 
> var portToOpenerContext;
> navigator.presentation.addEventListener(
>   "connect", function(e) {
>     portToOpenerContext = e.port; };
> portToOpenerContext.onmessage =
>   function(e) { console.log(e.data); } // Receiving messages.
> portToOpenerContext.postMessage(...); // Sending messages.
> 
> On the presentation window side, the consumer of the API has to register for
> the connect Event on the navigator.presentation object to receive the other
> end of the MessagePort and can then proceed to communicating both ways.
> 
> An open issue here is: Do we need to notice disconnects in this direction?
> Should this just be handled through an application defined protocol which
> acknowledges messages through the MessagePort? Or do we wrap the
> MessagePort here as well? Or should we have an additional ondisconnect
> event?
> 
> 
> 
> Again, we'd be very happy to receive your feedback, criticism or improvement
> proposals before we start editing the spec.
> 
> We believe the above code solves the combination of uses cases and
> improvement requirements, especially the new discovery requirements quite
> well.
> 
> Dominik

Received on Friday, 24 January 2014 07:00:04 UTC