Bug 1069230 - Presentation API implementation. Part 6 - mozChromeEvent for app launch. r=fabrice r=smaug

This commit is contained in:
Sean Lin 2015-03-30 15:48:11 +08:00
parent 8e140a105f
commit 7960b1aa1d
12 changed files with 310 additions and 83 deletions

View File

@ -118,3 +118,6 @@ contract @mozilla.org/app-migrator;1 {7211ece0-b458-4635-9afc-f8d7f376ee95}
component {4a300c26-e99b-4018-ab9b-c48cf9bc4de1} B2GPresentationDevicePrompt.js
contract @mozilla.org/presentation-device/prompt;1 {4a300c26-e99b-4018-ab9b-c48cf9bc4de1}
# PresentationRequestUIGlue.js
component {ccc8a839-0b64-422b-8a60-fb2af0e376d0} PresentationRequestUIGlue.js
contract @mozilla.org/presentation/requestuiglue;1 {ccc8a839-0b64-422b-8a60-fb2af0e376d0}

View File

@ -0,0 +1,62 @@
/* 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/. */
"use strict"
const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
function PresentationRequestUIGlue() {
// This is to store the session ID / resolver binding.
// An example of the object literal is shown below:
//
// {
// "sessionId1" : resolver1,
// ...
// }
this._resolvers = {};
// Listen to the result for the opened iframe from front-end.
SystemAppProxy.addEventListener("mozPresentationContentEvent", aEvent => {
let detail = aEvent.detail;
if (detail.type != "presentation-receiver-launched") {
return;
}
let sessionId = detail.sessionId;
let resolver = this._resolvers[sessionId];
if (!resolver) {
return;
}
delete this._resolvers[sessionId];
resolver(detail.frame);
});
}
PresentationRequestUIGlue.prototype = {
sendRequest: function(aUrl, aSessionId) {
SystemAppProxy._sendCustomEvent("mozPresentationChromeEvent",
{ type: "presentation-launch-receiver",
url: aUrl,
id: aSessionId });
return new Promise(function(aResolve, aReject) {
this._resolvers[aSessionId] = aResolve;
}.bind(this));
},
classID: Components.ID("{ccc8a839-0b64-422b-8a60-fb2af0e376d0}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationRequestUIGlue]);

View File

@ -23,6 +23,7 @@ EXTRA_COMPONENTS += [
'OMAContentHandler.js',
'PaymentGlue.js',
'PaymentProviderStrategy.js',
'PresentationRequestUIGlue.js',
'ProcessGlobal.js',
'SmsProtocolHandler.js',
'SystemMessageGlue.js',

View File

@ -7,6 +7,7 @@ support-files =
screenshot_helper.js
systemapp_helper.js
presentation_prompt_handler_chrome.js
presentation_ui_glue_handler_chrome.js
[test_filepicker_path.html]
[test_permission_deny.html]
@ -17,3 +18,4 @@ skip-if = true # Bug 1019572 - frequent timeouts
[test_systemapp.html]
[test_presentation_device_prompt.html]
[test_permission_visibilitychange.html]
[test_presentation_request_ui_glue.html]

View File

@ -0,0 +1,35 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
const glue = Cc["@mozilla.org/presentation/requestuiglue;1"]
.createInstance(Ci.nsIPresentationRequestUIGlue);
SystemAppProxy.addEventListener('mozPresentationChromeEvent', function(aEvent) {
if (!aEvent.detail || aEvent.detail.type !== 'presentation-launch-receiver') {
return;
}
sendAsyncMessage('presentation-launch-receiver', aEvent.detail);
});
addMessageListener('trigger-ui-glue', function(aData) {
var promise = glue.sendRequest(aData.url, aData.sessionId);
promise.then(function(aFrame){
sendAsyncMessage('iframe-resolved', aFrame);
});
});
addMessageListener('trigger-presentation-content-event', function(aData) {
var detail = {
type: 'presentation-receiver-launched',
sessionId: aData.sessionId,
frame: aData.frame
};
SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', detail);
});

View File

@ -0,0 +1,78 @@
<!DOCTYPE HTML>
<html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<meta charset="utf-8">
<title>Test for Presentation Device Selection</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Test for Presentation UI Glue</a>
<script type="application/javascript;version=1.8">
'use strict';
SimpleTest.waitForExplicitFinish();
var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('presentation_ui_glue_handler_chrome.js'));
var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
.getService(SpecialPowers.Ci.nsIObserverService);
var url = 'http://example.com';
var sessionId = 'sessionId';
function testLaunchReceiver() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
is(aDetail.url, url, "Url should be the same.");
is(aDetail.id, sessionId, "Session ID should be the same.");
aResolve();
});
gScript.sendAsyncMessage('trigger-ui-glue',
{ url: url,
sessionId : sessionId });
});
}
function testReceiverLaunched() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('iframe-resolved', function iframeResolvedHandler(aFrame) {
gScript.removeMessageListener('iframe-resolved', iframeResolvedHandler);
ok(true, "The promise should be resolved.");
aResolve();
});
var iframe = document.createElement('iframe');
iframe.setAttribute('remote', 'true');
iframe.setAttribute('mozbrowser', 'true');
iframe.setAttribute('src', 'http://example.com');
document.body.appendChild(iframe);
gScript.sendAsyncMessage('trigger-presentation-content-event',
{ sessionId : sessionId,
frame: iframe });
});
}
function runTests() {
testLaunchReceiver()
.then(testReceiverLaunched)
.then(function() {
info('test finished, teardown');
gScript.destroy();
SimpleTest.finish();
});
}
window.addEventListener('load', runTests);
</script>
</body>
</html>

View File

@ -960,6 +960,7 @@ bin/libfreebl_32int64_3.so
@RESPATH@/components/SystemMessageGlue.js
@RESPATH@/components/B2GAppMigrator.js
@RESPATH@/components/B2GPresentationDevicePrompt.js
@RESPATH@/components/PresentationRequestUIGlue.js
#ifndef MOZ_WIDGET_GONK
@RESPATH@/components/SimulatorScreen.js

View File

@ -12,6 +12,7 @@
#include "nsIPresentationDeviceManager.h"
#include "nsIPresentationDevicePrompt.h"
#include "nsIPresentationListener.h"
#include "nsIPresentationRequestUIGlue.h"
#include "nsIPresentationSessionRequest.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
@ -336,16 +337,20 @@ PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aReques
mSessionInfo.Put(sessionId, info);
// Notify the receiver to launch.
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (NS_WARN_IF(!obs)) {
nsCOMPtr<nsIPresentationRequestUIGlue> glue =
do_CreateInstance(PRESENTATION_REQUEST_UI_GLUE_CONTRACTID);
if (NS_WARN_IF(!glue)) {
ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
return info->ReplyError(NS_ERROR_NOT_AVAILABLE);
}
rv = obs->NotifyObservers(aRequest, "presentation-launch-receiver", nullptr);
nsCOMPtr<nsISupports> promise;
rv = glue->SendRequest(url, sessionId, getter_AddRefs(promise));
if (NS_WARN_IF(NS_FAILED(rv))) {
ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
return info->ReplyError(rv);
}
nsCOMPtr<Promise> realPromise = do_QueryInterface(promise);
static_cast<PresentationResponderInfo*>(info.get())->SetPromise(realPromise);
return NS_OK;
}

View File

@ -5,12 +5,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/HTMLIFrameElementBinding.h"
#include "mozilla/dom/TabParent.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "nsIDocShell.h"
#include "nsIFrameLoader.h"
#include "nsIObserverService.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "PresentationService.h"
@ -340,7 +340,6 @@ PresentationRequesterInfo::OnStopListening(nsIServerSocket* aServerSocket,
NS_IMPL_ISUPPORTS_INHERITED(PresentationResponderInfo,
PresentationSessionInfo,
nsIObserver,
nsITimerCallback)
nsresult
@ -348,18 +347,9 @@ PresentationResponderInfo::Init(nsIPresentationControlChannel* aControlChannel)
{
PresentationSessionInfo::Init(aControlChannel);
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (NS_WARN_IF(!obs)) {
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = obs->AddObserver(this, "presentation-receiver-launched", false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Add a timer to prevent waiting indefinitely in case the receiver page fails
// to become ready.
nsresult rv;
int32_t timeout =
Preferences::GetInt("presentation.receiver.loading.timeout", 10000);
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
@ -379,17 +369,13 @@ PresentationResponderInfo::Shutdown(nsresult aReason)
{
PresentationSessionInfo::Shutdown(aReason);
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, "presentation-receiver-launched");
}
if (mTimer) {
mTimer->Cancel();
}
mLoadingCallback = nullptr;
mRequesterDescription = nullptr;
mPromise = nullptr;
}
nsresult
@ -485,66 +471,6 @@ PresentationResponderInfo::NotifyClosed(nsresult aReason)
return NS_OK;
}
// nsIObserver
NS_IMETHODIMP
PresentationResponderInfo::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(NS_IsMainThread());
// The receiver has launched.
if (!strcmp(aTopic, "presentation-receiver-launched")) {
// Ignore irrelevant notifications.
if (!mSessionId.Equals(aData)) {
return NS_OK;
}
// Remove the observer.
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, "presentation-receiver-launched");
}
// Start to listen to document state change event |STATE_TRANSFERRING|.
nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(aSubject);
if (NS_WARN_IF(!owner)) {
return ReplyError(NS_ERROR_NOT_AVAILABLE);
}
nsCOMPtr<nsIFrameLoader> frameLoader;
nsresult rv = owner->GetFrameLoader(getter_AddRefs(frameLoader));
if (NS_WARN_IF(NS_FAILED(rv))) {
return ReplyError(rv);
}
nsRefPtr<TabParent> tabParent = TabParent::GetFrom(frameLoader);
if (tabParent) {
// OOP frame
nsCOMPtr<nsIContentParent> cp = tabParent->Manager();
NS_WARN_IF(!static_cast<ContentParent*>(cp.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId));
} else {
// In-process frame
nsCOMPtr<nsIDocShell> docShell;
rv = frameLoader->GetDocShell(getter_AddRefs(docShell));
if (NS_WARN_IF(NS_FAILED(rv))) {
return ReplyError(rv);
}
mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId);
rv = mLoadingCallback->Init(docShell);
if (NS_WARN_IF(NS_FAILED(rv))) {
return ReplyError(rv);
}
}
return NS_OK;
}
MOZ_ASSERT(false, "Unexpected topic for PresentationResponderInfo.");
return NS_ERROR_UNEXPECTED;
}
// nsITimerCallback
NS_IMETHODIMP
PresentationResponderInfo::Notify(nsITimer* aTimer)
@ -555,3 +481,80 @@ PresentationResponderInfo::Notify(nsITimer* aTimer)
mTimer = nullptr;
return ReplyError(NS_ERROR_DOM_TIMEOUT_ERR);
}
// PromiseNativeHandler
void
PresentationResponderInfo::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
MOZ_ASSERT(NS_IsMainThread());
if (NS_WARN_IF(!aValue.isObject())) {
ReplyError(NS_ERROR_NOT_AVAILABLE);
return;
}
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
if (NS_WARN_IF(!obj)) {
ReplyError(NS_ERROR_NOT_AVAILABLE);
return;
}
// Start to listen to document state change event |STATE_TRANSFERRING|.
HTMLIFrameElement* frame = nullptr;
nsresult rv = UNWRAP_OBJECT(HTMLIFrameElement, obj, frame);
if (NS_WARN_IF(!frame)) {
ReplyError(NS_ERROR_NOT_AVAILABLE);
return;
}
nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface((nsIFrameLoaderOwner*) frame);
if (NS_WARN_IF(!owner)) {
ReplyError(NS_ERROR_NOT_AVAILABLE);
return;
}
nsCOMPtr<nsIFrameLoader> frameLoader;
rv = owner->GetFrameLoader(getter_AddRefs(frameLoader));
if (NS_WARN_IF(NS_FAILED(rv))) {
ReplyError(rv);
return;
}
nsRefPtr<TabParent> tabParent = TabParent::GetFrom(frameLoader);
if (tabParent) {
// OOP frame
nsCOMPtr<nsIContentParent> cp = tabParent->Manager();
NS_WARN_IF(!static_cast<ContentParent*>(cp.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId));
} else {
// In-process frame
nsCOMPtr<nsIDocShell> docShell;
rv = frameLoader->GetDocShell(getter_AddRefs(docShell));
if (NS_WARN_IF(NS_FAILED(rv))) {
ReplyError(rv);
return;
}
mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId);
rv = mLoadingCallback->Init(docShell);
if (NS_WARN_IF(NS_FAILED(rv))) {
ReplyError(rv);
return;
}
}
}
void
PresentationResponderInfo::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
MOZ_ASSERT(NS_IsMainThread());
NS_WARNING("The receiver page fails to become ready before timeout.");
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
ReplyError(NS_ERROR_DOM_ABORT_ERR);
}

View File

@ -7,9 +7,10 @@
#ifndef mozilla_dom_PresentationSessionInfo_h
#define mozilla_dom_PresentationSessionInfo_h
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/nsRefPtr.h"
#include "nsCOMPtr.h"
#include "nsIObserver.h"
#include "nsIPresentationControlChannel.h"
#include "nsIPresentationDevice.h"
#include "nsIPresentationListener.h"
@ -149,13 +150,12 @@ private:
// Session info with receiver side behaviors.
class PresentationResponderInfo final : public PresentationSessionInfo
, public nsIObserver
, public PromiseNativeHandler
, public nsITimerCallback
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
NS_DECL_NSIOBSERVER
NS_DECL_NSITIMERCALLBACK
PresentationResponderInfo(const nsAString& aUrl,
@ -172,6 +172,16 @@ public:
nsresult NotifyResponderReady();
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
void SetPromise(Promise* aPromise)
{
mPromise = aPromise;
mPromise->AppendNativeHandler(this);
}
private:
~PresentationResponderInfo()
{
@ -185,6 +195,7 @@ private:
nsRefPtr<PresentationResponderLoadingCallback> mLoadingCallback;
nsCOMPtr<nsITimer> mTimer;
nsCOMPtr<nsIPresentationChannelDescription> mRequesterDescription;
nsRefPtr<Promise> mPromise;
};
} // namespace dom

View File

@ -11,6 +11,7 @@ XPIDL_SOURCES += [
'nsIPresentationDevicePrompt.idl',
'nsIPresentationDeviceProvider.idl',
'nsIPresentationListener.idl',
'nsIPresentationRequestUIGlue.idl',
'nsIPresentationService.idl',
'nsIPresentationSessionRequest.idl',
'nsIPresentationSessionTransport.idl',

View File

@ -0,0 +1,25 @@
/* 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/. */
#include "nsISupports.idl"
%{C++
#define PRESENTATION_REQUEST_UI_GLUE_CONTRACTID \
"@mozilla.org/presentation/requestuiglue;1"
%}
[scriptable, uuid(faa45119-6fb5-496c-aa4c-f740177a38b5)]
interface nsIPresentationRequestUIGlue : nsISupports
{
/*
* This method is called to open the responding app/page when a presentation
* request comes in at receiver side.
*
* @param url The url of the request.
* @param sessionId The session ID of the request.
*
* @return A promise that resolves to the opening frame.
*/
nsISupports sendRequest(in DOMString url, in DOMString sessionId);
};