--- a/dom/browser-element/BrowserElementAudioChannel.cpp
+++ b/dom/browser-element/BrowserElementAudioChannel.cpp
@@ -2,25 +2,31 @@
* 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 "BrowserElementAudioChannel.h"
#include "mozilla/Services.h"
#include "mozilla/dom/BrowserElementAudioChannelBinding.h"
#include "mozilla/dom/DOMRequest.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/ToJSValue.h"
#include "AudioChannelService.h"
#include "nsIBrowserElementAPI.h"
#include "nsIDocShell.h"
+#include "nsIDOMDocument.h"
#include "nsIDOMDOMRequest.h"
#include "nsIObserverService.h"
#include "nsISupportsPrimitives.h"
+#include "nsISystemMessagesInternal.h"
#include "nsITabParent.h"
+#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
namespace {
void
AssertIsInMainProcess()
{
MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
}
@@ -45,38 +51,42 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Brows
mTabParent,
mBrowserElementAPI)
/* static */ already_AddRefed<BrowserElementAudioChannel>
BrowserElementAudioChannel::Create(nsPIDOMWindow* aWindow,
nsIFrameLoader* aFrameLoader,
nsIBrowserElementAPI* aAPI,
AudioChannel aAudioChannel,
+ const nsAString& aManifestURL,
ErrorResult& aRv)
{
RefPtr<BrowserElementAudioChannel> ac =
- new BrowserElementAudioChannel(aWindow, aFrameLoader, aAPI, aAudioChannel);
+ new BrowserElementAudioChannel(aWindow, aFrameLoader, aAPI,
+ aAudioChannel, aManifestURL);
aRv = ac->Initialize();
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return ac.forget();
}
BrowserElementAudioChannel::BrowserElementAudioChannel(
- nsPIDOMWindow* aWindow,
- nsIFrameLoader* aFrameLoader,
- nsIBrowserElementAPI* aAPI,
- AudioChannel aAudioChannel)
+ nsPIDOMWindow* aWindow,
+ nsIFrameLoader* aFrameLoader,
+ nsIBrowserElementAPI* aAPI,
+ AudioChannel aAudioChannel,
+ const nsAString& aManifestURL)
: DOMEventTargetHelper(aWindow)
, mFrameLoader(aFrameLoader)
, mBrowserElementAPI(aAPI)
, mAudioChannel(aAudioChannel)
+ , mManifestURL(aManifestURL)
, mState(eStateUnknown)
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsInMainProcess();
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
nsAutoString name;
@@ -297,16 +307,53 @@ public:
protected:
virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override
{
JS::Rooted<JS::Value> value(aCx);
mRequest->FireSuccess(value);
}
};
+class RespondSuccessHandler final : public PromiseNativeHandler
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit RespondSuccessHandler(DOMRequest* aRequest)
+ : mDomRequest(aRequest)
+ {};
+
+ virtual void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ virtual void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+private:
+ ~RespondSuccessHandler() {};
+
+ RefPtr<DOMRequest> mDomRequest;
+};
+NS_IMPL_ISUPPORTS0(RespondSuccessHandler);
+
+void
+RespondSuccessHandler::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ JS::Rooted<JS::Value> value(aCx);
+ mDomRequest->FireSuccess(value);
+}
+
+void
+RespondSuccessHandler::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ mDomRequest->FireError(NS_ERROR_FAILURE);
+}
+
} // anonymous namespace
already_AddRefed<dom::DOMRequest>
BrowserElementAudioChannel::GetVolume(ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsInMainProcess();
@@ -454,16 +501,72 @@ BrowserElementAudioChannel::IsActive(Err
nsCOMPtr<nsIRunnable> runnable =
new IsActiveRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel);
NS_DispatchToMainThread(runnable);
return domRequest.forget();
}
+already_AddRefed<dom::DOMRequest>
+BrowserElementAudioChannel::NotifyChannel(const nsAString& aEvent,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!mFrameWindow) {
+ nsCOMPtr<nsIDOMDOMRequest> request;
+ aRv = mBrowserElementAPI->NotifyChannel(aEvent, mManifestURL,
+ (uint32_t)mAudioChannel,
+ getter_AddRefs(request));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return request.forget().downcast<DOMRequest>();
+ }
+
+ nsCOMPtr<nsISystemMessagesInternal> systemMessenger =
+ do_GetService("@mozilla.org/system-message-internal;1");
+ MOZ_ASSERT(systemMessenger);
+
+ AutoJSAPI jsAPI;
+ if (!jsAPI.Init(GetOwner())) {
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> value(jsAPI.cx());
+ value.setInt32((uint32_t)mAudioChannel);
+
+ nsCOMPtr<nsIURI> manifestURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(manifestURI), mManifestURL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // Since the pageURI of the app has been registered to the system messager,
+ // when the app was installed. The system messager can only use the manifest
+ // to send the message to correct page.
+ nsCOMPtr<nsISupports> promise;
+ rv = systemMessenger->SendMessage(aEvent, value, nullptr, manifestURI,
+ JS::UndefinedHandleValue,
+ getter_AddRefs(promise));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> promiseIns = static_cast<Promise*>(promise.get());
+ RefPtr<DOMRequest> request = new DOMRequest(GetOwner());
+ RefPtr<RespondSuccessHandler> handler = new RespondSuccessHandler(request);
+ promiseIns->AppendNativeHandler(handler);
+
+ return request.forget();
+}
+
NS_IMETHODIMP
BrowserElementAudioChannel::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
nsAutoString name;
AudioChannelService::GetAudioChannelString(mAudioChannel, name);
nsAutoCString topic;
--- a/dom/browser-element/BrowserElementAudioChannel.h
+++ b/dom/browser-element/BrowserElementAudioChannel.h
@@ -35,16 +35,17 @@ public:
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BrowserElementAudioChannel,
DOMEventTargetHelper)
static already_AddRefed<BrowserElementAudioChannel>
Create(nsPIDOMWindow* aWindow,
nsIFrameLoader* aFrameLoader,
nsIBrowserElementAPI* aAPI,
AudioChannel aAudioChannel,
+ const nsAString& aManifestURL,
ErrorResult& aRv);
// WebIDL methods
virtual JSObject* WrapObject(JSContext *aCx,
JS::Handle<JSObject*> aGivenProto) override;
AudioChannel Name() const;
@@ -52,35 +53,40 @@ public:
already_AddRefed<dom::DOMRequest> GetVolume(ErrorResult& aRv);
already_AddRefed<dom::DOMRequest> SetVolume(float aVolume, ErrorResult& aRv);
already_AddRefed<dom::DOMRequest> GetMuted(ErrorResult& aRv);
already_AddRefed<dom::DOMRequest> SetMuted(bool aMuted, ErrorResult& aRv);
already_AddRefed<dom::DOMRequest> IsActive(ErrorResult& aRv);
+ already_AddRefed<dom::DOMRequest> NotifyChannel(const nsAString& aEvent,
+ ErrorResult& aRv);
+
IMPL_EVENT_HANDLER(activestatechanged);
private:
BrowserElementAudioChannel(nsPIDOMWindow* aWindow,
nsIFrameLoader* aFrameLoader,
nsIBrowserElementAPI* aAPI,
- AudioChannel aAudioChannel);
+ AudioChannel aAudioChannel,
+ const nsAString& aManifestURL);
~BrowserElementAudioChannel();
nsresult Initialize();
void ProcessStateChanged(const char16_t* aData);
nsCOMPtr<nsIFrameLoader> mFrameLoader;
nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
nsCOMPtr<nsITabParent> mTabParent;
nsCOMPtr<nsPIDOMWindow> mFrameWindow;
AudioChannel mAudioChannel;
+ nsString mManifestURL;
enum {
eStateActive,
eStateInactive,
eStateUnknown
} mState;
};
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -18,16 +18,20 @@ Cu.import("resource://gre/modules/Servic
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
Cu.import("resource://gre/modules/Webapps.jsm");
return DOMApplicationRegistry;
});
+XPCOMUtils.defineLazyServiceGetter(this, "systemMessenger",
+ "@mozilla.org/system-message-internal;1",
+ "nsISystemMessagesInternal");
+
function debug(msg) {
//dump("BrowserElementParent - " + msg + "\n");
}
function getIntPref(prefName, def) {
try {
return Services.prefs.getIntPref(prefName);
}
@@ -1203,16 +1207,36 @@ BrowserElementParent.prototype = {
muted: aMuted});
},
isAudioChannelActive: function(aAudioChannel) {
return this._sendDOMRequest('get-is-audio-channel-active',
{audioChannel: aAudioChannel});
},
+ notifyChannel: function(aEvent, aManifest, aAudioChannel) {
+ var self = this;
+ var req = Services.DOMRequest.createRequest(self._window);
+
+ // Since the pageURI of the app has been registered to the system messager,
+ // when the app was installed. The system messager can only use the manifest
+ // to send the message to correct page.
+ let manifestURL = Services.io.newURI(aManifest, null, null);
+ systemMessenger.sendMessage(aEvent, aAudioChannel, null, manifestURL)
+ .then(function() {
+ Services.DOMRequest.fireSuccess(req,
+ Cu.cloneInto(true, self._window));
+ }, function() {
+ debug("Error : NotifyChannel fail.");
+ Services.DOMRequest.fireErrorAsync(req,
+ Cu.cloneInto("NotifyChannel fail.", self._window));
+ });
+ return req;
+ },
+
getStructuredData: defineDOMRequestMethod('get-structured-data'),
/**
* Called when the visibility of the window which owns this iframe changes.
*/
_ownerVisibilityChange: function() {
this._sendAsyncMsg('owner-visibility-change',
{visible: !this._window.document.hidden});
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/browserElement_NotifyChannel.js
@@ -0,0 +1,118 @@
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+const { classes: Cc, interfaces: Ci } = Components;
+const systemMessenger = Cc["@mozilla.org/system-message-internal;1"]
+ .getService(Ci.nsISystemMessagesInternal);
+const ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+var tests = [false /* INPROC */, true /* OOP */];
+var rootURI = "http://test/chrome/dom/browser-element/mochitest/";
+var manifestURI = rootURI + "manifest.webapp";
+var srcURI = rootURI + "file_browserElement_NotifyChannel.html";
+var generator = runTests();
+var app = null;
+
+addLoadEvent(() => {
+ SpecialPowers.pushPermissions(
+ [{ "type": "webapps-manage", "allow": 1, "context": document },
+ { "type": "browser", "allow": 1, "context": document },
+ { "type": "embed-apps", "allow": 1, "context": document }],
+ function() {
+ SpecialPowers.pushPrefEnv(
+ {'set': [["dom.mozBrowserFramesEnabled", true],
+ ["dom.sysmsg.enabled", true]]},
+ () => { generator.next(); })
+ });
+});
+
+function error(message) {
+ ok(false, message);
+ SimpleTest.finish();
+}
+
+function continueTest() {
+ try {
+ generator.next();
+ } catch (e if e instanceof StopIteration) {
+ error("Stop test because of exception!");
+ }
+}
+
+function registerPage(aEvent) {
+ systemMessenger.registerPage(aEvent,
+ ioService.newURI(srcURI, null, null),
+ ioService.newURI(manifestURI, null, null));
+}
+
+function runTest(aEnable) {
+ var request = navigator.mozApps.install(manifestURI, {});
+ request.onerror = () => {
+ error("Install app failed!");
+ };
+
+ request.onsuccess = () => {
+ app = request.result;
+ ok(app, "App is installed. remote = " + aEnable);
+ is(app.manifestURL, manifestURI, "App manifest url is correct.");
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('mozbrowser', true);
+ iframe.setAttribute('remote', aEnable);
+ iframe.setAttribute('mozapp', manifestURI);
+ iframe.src = srcURI;
+ document.body.appendChild(iframe);
+
+ iframe.addEventListener('mozbrowserloadend', () => {
+ var channels = iframe.allowedAudioChannels;
+ is(channels.length, 1, "1 audio channel by default");
+
+ var ac = channels[0];
+ ok(ac instanceof BrowserElementAudioChannel, "Correct class");
+ ok("notifyChannel" in ac, "ac.notifyChannel exists");
+
+ var message = "audiochannel-interruption-begin";
+ registerPage(message);
+ ac.notifyChannel(message);
+ iframe.addEventListener("mozbrowsershowmodalprompt", function (e) {
+ is(e.detail.message, message,
+ "App got audiochannel-interruption-begin.");
+
+ if (app) {
+ request = navigator.mozApps.mgmt.uninstall(app);
+ app = null;
+ request.onerror = () => {
+ error("Uninstall app failed!");
+ };
+ request.onsuccess = () => {
+ is(request.result, manifestURI, "App uninstalled.");
+ runNextTest();
+ }
+ }
+ });
+ });
+ };
+}
+
+function runNextTest() {
+ if (tests.length) {
+ var isEnabledOOP = tests.shift();
+ runTest(isEnabledOOP);
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+function runTests() {
+ SpecialPowers.setAllAppsLaunchable(true);
+ SpecialPowers.autoConfirmAppInstall(continueTest);
+ yield undefined;
+
+ SpecialPowers.autoConfirmAppUninstall(continueTest);
+ yield undefined;
+
+ runNextTest();
+ yield undefined;
+}
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/chrome.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
+
+support-files =
+ browserElement_NotifyChannel.js
+ file_browserElement_NotifyChannel.html
+ manifest.webapp
+ manifest.webapp^headers^
+
+[test_browserElement_NotifyChannel.html]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_browserElement_NotifyChannel.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test of browser element audio channel method, notifyChannel</title>
+</head>
+<body>
+<script type="application/javascript;version=1.8">
+ "use strict";
+ navigator.mozSetMessageHandler('audiochannel-interruption-begin',
+ function (e) {
+ alert("audiochannel-interruption-begin");
+ });
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/manifest.webapp
@@ -0,0 +1,7 @@
+{
+ "name": "NotifyChannel Test",
+ "launch_path": "/index.html",
+ "messages": [
+ { "audiochannel-interruption-begin": "./file_browserElement_NotifyChannel.html" }
+ ]
+}
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/manifest.webapp^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/manifest+json
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_NotifyChannel.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BrowserElementAudioChannel function : notifyChannel().</title>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.7" src="browserElement_NotifyChannel.js">
+</script>
+</body>
+</html>
--- a/dom/browser-element/moz.build
+++ b/dom/browser-element/moz.build
@@ -49,11 +49,12 @@ LOCAL_INCLUDES += [
'/dom/ipc',
]
MOCHITEST_MANIFESTS += [
'mochitest/mochitest-oop.ini',
'mochitest/mochitest.ini',
'mochitest/priority/mochitest.ini',
]
+MOCHITEST_CHROME_MANIFESTS += ['mochitest/chrome.ini']
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wshadow']
--- a/dom/browser-element/nsIBrowserElementAPI.idl
+++ b/dom/browser-element/nsIBrowserElementAPI.idl
@@ -21,17 +21,17 @@ interface nsIBrowserElementNextPaintList
{ 0x651db7e3, 0x1734, 0x4536, \
{ 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
%}
/**
* Interface to the BrowserElementParent implementation. All methods
* but setFrameLoader throw when the remote process is dead.
*/
-[scriptable, uuid(9946695c-1ed3-4abb-bc60-6f8947fd5641)]
+[scriptable, uuid(57758c10-6036-11e5-a837-0800200c9a66)]
interface nsIBrowserElementAPI : nsISupports
{
const long FIND_CASE_SENSITIVE = 0;
const long FIND_CASE_INSENSITIVE = 1;
const long FIND_FORWARD = 0;
const long FIND_BACKWARD = 1;
@@ -92,16 +92,20 @@ interface nsIBrowserElementAPI : nsISupp
nsIDOMDOMRequest getAudioChannelVolume(in uint32_t audioChannel);
nsIDOMDOMRequest setAudioChannelVolume(in uint32_t audioChannel, in float volume);
nsIDOMDOMRequest getAudioChannelMuted(in uint32_t audioChannel);
nsIDOMDOMRequest setAudioChannelMuted(in uint32_t audioChannel, in bool muted);
nsIDOMDOMRequest isAudioChannelActive(in uint32_t audioChannel);
+ nsIDOMDOMRequest notifyChannel(in DOMString event,
+ in DOMString manifest,
+ in uint32_t audioChannel);
+
void setNFCFocus(in boolean isFocus);
nsIDOMDOMRequest executeScript(in DOMString script, in jsval options);
/**
* Returns a JSON string representing Microdata objects on the page.
* Format is described at:
* https://html.spec.whatwg.org/multipage/microdata.html#json
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -598,17 +598,18 @@ nsBrowserElement::GenerateAllowedAudioCh
return;
}
// Normal is always allowed.
nsTArray<RefPtr<BrowserElementAudioChannel>> channels;
RefPtr<BrowserElementAudioChannel> ac =
BrowserElementAudioChannel::Create(aWindow, aFrameLoader, aAPI,
- AudioChannel::Normal, aRv);
+ AudioChannel::Normal,
+ aManifestURL, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
channels.AppendElement(ac);
if (app) {
const nsAttrValue::EnumTable* audioChannelTable =
@@ -625,17 +626,17 @@ nsBrowserElement::GenerateAllowedAudioCh
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (allowed) {
RefPtr<BrowserElementAudioChannel> ac =
BrowserElementAudioChannel::Create(aWindow, aFrameLoader, aAPI,
(AudioChannel)audioChannelTable[i].value,
- aRv);
+ aManifestURL, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
channels.AppendElement(ac);
}
}
}
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -131,17 +131,19 @@ this.SystemMessagePermissionsTable = {
"nfc-manager-send-file": {
"nfc-manager": []
},
"wifip2p-pairing-request": {
"wifi-manage": []
},
"first-run-with-sim": {
"settings": ["read", "write"]
- }
+ },
+ "audiochannel-interruption-begin" : {},
+ "audiochannel-interruption-ended" : {}
};
this.SystemMessagePermissionsChecker = {
/**
* Return all the needed permission names for the given system message.
* @param string aSysMsgName
* The system messsage name.
--- a/dom/webidl/BrowserElementAudioChannel.webidl
+++ b/dom/webidl/BrowserElementAudioChannel.webidl
@@ -22,16 +22,19 @@ interface BrowserElementAudioChannel : E
[Throws]
DOMRequest getMuted();
[Throws]
DOMRequest setMuted(boolean aMuted);
[Throws]
DOMRequest isActive();
+
+ [Throws]
+ DOMRequest notifyChannel(DOMString aEvent);
};
partial interface BrowserElementPrivileged {
[Pure, Cached, Throws,
Pref="dom.mozBrowserFramesEnabled",
CheckAnyPermissions="browser"]
readonly attribute sequence<BrowserElementAudioChannel> allowedAudioChannels;