Merge b2g-inbound to Mozilla-Central

This commit is contained in:
Carsten "Tomcat" Book 2013-10-24 06:50:15 +02:00
commit 417825f57d
54 changed files with 1963 additions and 470 deletions

View File

@ -18,4 +18,4 @@
# Modifying this file will now automatically clobber the buildbot machines \o/
#
Bug 918207 needed a clobber on every platform because we can't have nice things
Bug 899574 needed a clobber on every platform because we can't have nice things

View File

@ -11,6 +11,7 @@ Cu.import('resource://gre/modules/AlarmService.jsm');
Cu.import('resource://gre/modules/ActivitiesService.jsm');
Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
Cu.import('resource://gre/modules/ObjectWrapper.jsm');
Cu.import('resource://gre/modules/NotificationDB.jsm');
Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
Cu.import('resource://gre/modules/Payment.jsm');
Cu.import("resource://gre/modules/AppsUtils.jsm");

View File

@ -1,4 +1,4 @@
{
"revision": "a57a913f1dd723afa191124f27b8d9fc4b0cb1c0",
"revision": "20e3f42ccb6073c6d9bc9741de3a19a939a8a7d8",
"repo_path": "/integration/gaia-central"
}

View File

@ -352,6 +352,8 @@
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/PhoneNumberService.js
@BINPATH@/components/PhoneNumberService.manifest
@BINPATH@/components/NotificationStorage.js
@BINPATH@/components/NotificationStorage.manifest
@BINPATH@/components/PermissionSettings.js
@BINPATH@/components/PermissionSettings.manifest
@BINPATH@/components/PermissionPromptService.js
@ -543,9 +545,6 @@
@BINPATH@/components/TCPSocketParentIntermediary.js
@BINPATH@/components/TCPSocket.manifest
@BINPATH@/components/AppProtocolHandler.js
@BINPATH@/components/AppProtocolHandler.manifest
@BINPATH@/components/Payment.js
@BINPATH@/components/PaymentFlowInfo.js
@BINPATH@/components/PaymentRequestInfo.js

View File

@ -7,6 +7,7 @@ let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NotificationDB.jsm");
Cu.import("resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",

View File

@ -509,6 +509,8 @@
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/PhoneNumberService.js
@BINPATH@/components/PhoneNumberService.manifest
@BINPATH@/components/NotificationStorage.js
@BINPATH@/components/NotificationStorage.manifest
@BINPATH@/components/AlarmsManager.js
@BINPATH@/components/AlarmsManager.manifest
@BINPATH@/components/Push.js
@ -519,9 +521,6 @@
@BINPATH@/components/TCPSocketParentIntermediary.js
@BINPATH@/components/TCPSocket.manifest
@BINPATH@/components/AppProtocolHandler.js
@BINPATH@/components/AppProtocolHandler.manifest
@BINPATH@/components/Payment.js
@BINPATH@/components/PaymentFlowInfo.js
@BINPATH@/components/PaymentRequestInfo.js

View File

@ -36,15 +36,17 @@ pref("prompts.tab_modal.enabled", true);
pref("layers.offmainthreadcomposition.enabled", true);
pref("layers.async-pan-zoom.enabled", true);
pref("layers.componentalpha.enabled", false);
pref("gfx.azpc.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
pref("gfx.azpc.pan_repaint_interval", "50"); // prefer 20 fps
pref("gfx.azpc.fling_repaint_interval", "50"); // prefer 20 fps
pref("gfx.axis.fling_friction", "0.002");
pref("gfx.axis.fling_stopped_threshold", "0.2");
// Prefs to control the async pan/zoom behaviour
pref("apz.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
pref("apz.pan_repaint_interval", "50"); // prefer 20 fps
pref("apz.fling_repaint_interval", "50"); // prefer 20 fps
pref("apz.fling_friction", "0.002");
pref("apz.fling_stopped_threshold", "0.2");
// 0 = free, 1 = standard, 2 = sticky
pref("apzc.axis_lock_mode", 2);
pref("apzc.cross_slide.enabled", true);
pref("apz.axis_lock_mode", 2);
pref("apz.cross_slide.enabled", true);
// Enable Microsoft TSF support by default for imes.
pref("intl.enable_tsf_support", true);

View File

@ -3986,7 +3986,7 @@ MOZ_PDF_PRINTING=
MOZ_DISABLE_CRYPTOLEGACY=
NSS_DISABLE_DBM=
NECKO_COOKIES=1
NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource websocket wyciwyg device"
NECKO_PROTOCOLS_DEFAULT="about app data file ftp http res viewsource websocket wyciwyg device"
USE_ARM_KUSER=
BUILD_CTYPES=1
MOZ_USE_NATIVE_POPUP_WINDOWS=

View File

@ -166,7 +166,8 @@ this.OperatorAppsRegistry = {
},
appId: undefined,
isBrowser: false,
isPackage: isPackage
isPackage: isPackage,
forceSuccessAck: true
};
if (isPackage) {

View File

@ -2155,6 +2155,20 @@ this.DOMApplicationRegistry = {
queuedPackageDownload: {},
onInstallSuccessAck: function onInstallSuccessAck(aManifestURL) {
// If we are offline, register to run when we'll be online.
if (Services.io.offline) {
let onlineWrapper = {
observe: function(aSubject, aTopic, aData) {
Services.obs.removeObserver(onlineWrapper,
"network:offline-status-changed");
DOMApplicationRegistry.onInstallSuccessAck(aManifestURL);
}
}
Services.obs.addObserver(onlineWrapper,
"network:offline-status-changed", false);
return;
}
let cacheDownload = this.queuedDownload[aManifestURL];
if (cacheDownload) {
this.startOfflineCacheDownload(cacheDownload.manifest,
@ -2371,12 +2385,12 @@ this.DOMApplicationRegistry = {
app: appObject,
callback: aInstallSuccessCallback
};
}
if (aData.app.localInstallPath) {
// if it's a local install, there's no content process so just
// ack the install
this.onInstallSuccessAck(app.manifestURL);
}
if (aData.forceSuccessAck) {
// If it's a local install, there's no content process so just
// ack the install.
this.onInstallSuccessAck(app.manifestURL);
}
},

View File

@ -762,10 +762,16 @@ DeviceStorageFile::GetRootDirectoryForType(const nsAString& aStorageType,
// crash reports directory.
else if (aStorageType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
f = sDirs->crashes;
} else {
// Not a storage type that we recognize. Return null
return;
}
// in testing, we default all device storage types to a temp directory
if (f && sDirs->temp) {
// In testing, we default all device storage types to a temp directory.
// sDirs->temp will only have been initialized (in InitDirs) if the
// preference device.storage.testing was set to true. We can't test the
// preference directly here, since we may not be on the main thread.
if (sDirs->temp) {
f = sDirs->temp;
}

View File

@ -6,6 +6,7 @@
XPIDL_SOURCES += [
'nsIDOMDesktopNotification.idl',
'nsINotificationStorage.idl',
]
XPIDL_MODULE = 'dom_notification'

View File

@ -0,0 +1,92 @@
/* 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 "domstubs.idl"
[scriptable, uuid(fb089720-1c5c-11e3-b773-0800200c9a66)]
interface nsINotificationStorageCallback : nsISupports
{
/**
* Callback function used to pass single notification back
* into C++ land for Notification.get return data.
*
* @param id: a uuid for this notification
* @param title: the notification title
* @param dir: the notification direction,
* possible values are "ltr", "rtl", "auto"
* @param lang: the notification language
* @param body: the notification body
* @param tag: the notification tag
*/
[implicit_jscontext]
void handle(in DOMString id,
in DOMString title,
in DOMString dir,
in DOMString lang,
in DOMString body,
in DOMString tag,
in DOMString icon);
/**
* Callback function used to notify C++ the we have returned
* all notification objects for this Notification.get call.
*/
[implicit_jscontext]
void done();
};
/**
* Interface for notification persistence layer.
*/
[scriptable, uuid(b177b080-2a23-11e3-8224-0800200c9a66)]
interface nsINotificationStorage : nsISupports
{
/**
* Add/replace a notification to the persistence layer.
*
* @param origin: the origin/app of this notification
* @param id: a uuid for this notification
* @param title: the notification title
* @param dir: the notification direction,
* possible values are "ltr", "rtl", "auto"
* @param lang: the notification language
* @param body: the notification body
* @param tag: notification tag, will replace any existing
* notifications with same origin/tag pair
*/
void put(in DOMString origin,
in DOMString id,
in DOMString title,
in DOMString dir,
in DOMString lang,
in DOMString body,
in DOMString tag,
in DOMString icon);
/**
* Retrieve a list of notifications.
*
* @param origin: the origin/app for which to fetch notifications from
* @param tag: used to fetch only a specific tag
* @param callback: nsINotificationStorageCallback, used for
* returning notifications objects
*/
void get(in DOMString origin,
in DOMString tag,
in nsINotificationStorageCallback aCallback);
/**
* Remove a notification from storage.
*
* @param origin: the origin/app to delete the notification from
* @param id: the uuid for the notification to delete
*/
void delete(in DOMString origin,
in DOMString id);
};
%{C++
#define NS_NOTIFICATION_STORAGE_CONTRACTID "@mozilla.org/notificationStorage;1"
%}

View File

@ -189,6 +189,20 @@ Promise::EnabledForScope(JSContext* aCx, JSObject* /* unused */)
prin->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED;
}
void
Promise::MaybeResolve(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue)
{
MaybeResolveInternal(aCx, aValue);
}
void
Promise::MaybeReject(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue)
{
MaybeRejectInternal(aCx, aValue);
}
static void
EnterCompartment(Maybe<JSAutoCompartment>& aAc, JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue)
@ -229,9 +243,9 @@ Promise::JSCallback(JSContext *aCx, unsigned aArgc, JS::Value *aVp)
PromiseCallback::Task task = static_cast<PromiseCallback::Task>(v.toInt32());
if (task == PromiseCallback::Resolve) {
promise->MaybeResolve(aCx, value);
promise->MaybeResolveInternal(aCx, value);
} else {
promise->MaybeReject(aCx, value);
promise->MaybeRejectInternal(aCx, value);
}
return true;
@ -300,7 +314,7 @@ Promise::Constructor(const GlobalObject& aGlobal,
Maybe<JSAutoCompartment> ac;
EnterCompartment(ac, cx, value);
promise->MaybeReject(cx, value);
promise->MaybeRejectInternal(cx, value);
}
return promise.forget();
@ -319,7 +333,7 @@ Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx,
nsRefPtr<Promise> promise = new Promise(window);
Optional<JS::Handle<JS::Value> > value(aCx, aValue);
promise->MaybeResolve(aCx, value);
promise->MaybeResolveInternal(aCx, value);
return promise.forget();
}
@ -336,7 +350,7 @@ Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx,
nsRefPtr<Promise> promise = new Promise(window);
Optional<JS::Handle<JS::Value> > value(aCx, aValue);
promise->MaybeReject(aCx, value);
promise->MaybeRejectInternal(aCx, value);
return promise.forget();
}
@ -441,9 +455,9 @@ Promise::MaybeReportRejected()
}
void
Promise::MaybeResolve(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aAsynchronous)
Promise::MaybeResolveInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aAsynchronous)
{
if (mResolvePending) {
return;
@ -453,9 +467,9 @@ Promise::MaybeResolve(JSContext* aCx,
}
void
Promise::MaybeReject(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aAsynchronous)
Promise::MaybeRejectInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aAsynchronous)
{
if (mResolvePending) {
return;

View File

@ -43,6 +43,11 @@ public:
static bool PrefEnabled();
static bool EnabledForScope(JSContext* aCx, JSObject* /* unused */);
void MaybeResolve(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue);
void MaybeReject(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue);
// WebIDL
nsPIDOMWindow* GetParentObject() const
@ -114,12 +119,12 @@ private:
// report it to the error console.
void MaybeReportRejected();
void MaybeResolve(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aSync = AsyncTask);
void MaybeReject(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aSync = AsyncTask);
void MaybeResolveInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aSync = AsyncTask);
void MaybeRejectInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aSync = AsyncTask);
void ResolveInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,

View File

@ -6,14 +6,18 @@
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
#include "mozilla/dom/OwningNonNull.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Preferences.h"
#include "TabChild.h"
#include "nsContentUtils.h"
#include "nsDOMEvent.h"
#include "nsIAlertsService.h"
#include "nsIAppsService.h"
#include "nsIContentPermissionPrompt.h"
#include "nsIDocument.h"
#include "nsINotificationStorage.h"
#include "nsIPermissionManager.h"
#include "nsIUUIDGenerator.h"
#include "nsServiceManagerUtils.h"
#include "nsToolkitCompsCID.h"
#include "nsGlobalWindow.h"
@ -21,12 +25,124 @@
#include "nsIScriptSecurityManager.h"
#ifdef MOZ_B2G
#include "nsIDOMDesktopNotification.h"
#include "nsIAppsService.h"
#endif
namespace mozilla {
namespace dom {
class NotificationStorageCallback MOZ_FINAL : public nsINotificationStorageCallback
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(NotificationStorageCallback)
NotificationStorageCallback(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow, Promise* aPromise)
: mCount(0),
mGlobal(aGlobal.Get()),
mWindow(aWindow),
mPromise(aPromise)
{
MOZ_ASSERT(aWindow);
MOZ_ASSERT(aPromise);
JSContext* cx = aGlobal.GetContext();
JSAutoCompartment ac(cx, mGlobal);
mNotifications = JS_NewArrayObject(cx, 0, nullptr);
HoldData();
}
NS_IMETHOD Handle(const nsAString& aID,
const nsAString& aTitle,
const nsAString& aDir,
const nsAString& aLang,
const nsAString& aBody,
const nsAString& aTag,
const nsAString& aIcon,
JSContext* aCx)
{
MOZ_ASSERT(!aID.IsEmpty());
MOZ_ASSERT(!aTitle.IsEmpty());
NotificationOptions options;
options.mDir = Notification::StringToDirection(nsString(aDir));
options.mLang = aLang;
options.mBody = aBody;
options.mTag = aTag;
options.mIcon = aIcon;
nsRefPtr<Notification> notification = Notification::CreateInternal(mWindow,
aID,
aTitle,
options);
JSAutoCompartment ac(aCx, mGlobal);
JS::RootedObject scope(aCx, mGlobal);
JS::RootedObject element(aCx, notification->WrapObject(aCx, scope));
NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
if (!JS_DefineElement(aCx, mNotifications, mCount++,
JS::ObjectValue(*element), nullptr, nullptr, 0)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHOD Done(JSContext* aCx)
{
JSAutoCompartment ac(aCx, mGlobal);
Optional<JS::HandleValue> result(aCx, JS::ObjectValue(*mNotifications));
mPromise->MaybeResolve(aCx, result);
return NS_OK;
}
private:
~NotificationStorageCallback()
{
DropData();
}
void HoldData()
{
mozilla::HoldJSObjects(this);
}
void DropData()
{
mGlobal = nullptr;
mNotifications = nullptr;
mozilla::DropJSObjects(this);
}
uint32_t mCount;
JS::Heap<JSObject *> mGlobal;
nsCOMPtr<nsPIDOMWindow> mWindow;
nsRefPtr<Promise> mPromise;
JS::Heap<JSObject *> mNotifications;
};
NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNotifications)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
tmp->DropData();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
class NotificationPermissionRequest : public nsIContentPermissionRequest,
public PCOMContentPermissionRequestChild,
public nsIRunnable
@ -257,12 +373,15 @@ NotificationTask::Run()
{
switch (mAction) {
case eShow:
return mNotification->ShowInternal();
mNotification->ShowInternal();
break;
case eClose:
return mNotification->CloseInternal();
mNotification->CloseInternal();
break;
default:
MOZ_CRASH("Unexpected action for NotificationTask.");
}
return NS_OK;
}
NS_IMPL_ISUPPORTS1(NotificationObserver, nsIObserver)
@ -283,50 +402,103 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
Notification::Notification(const nsAString& aTitle, const nsAString& aBody,
Notification::Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
NotificationDirection aDir, const nsAString& aLang,
const nsAString& aTag, const nsAString& aIconUrl)
: mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
: mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
mTag(aTag), mIconUrl(aIconUrl), mIsClosed(false)
{
SetIsDOMBinding();
}
// static
already_AddRefed<Notification>
Notification::Constructor(const GlobalObject& aGlobal,
const nsAString& aTitle,
const NotificationOptions& aOptions,
ErrorResult& aRv)
{
nsString tag;
if (aOptions.mTag.WasPassed()) {
tag.Append(NS_LITERAL_STRING("tag:"));
tag.Append(aOptions.mTag.Value());
} else {
tag.Append(NS_LITERAL_STRING("notag:"));
tag.AppendInt(sCount++);
}
nsRefPtr<Notification> notification = new Notification(aTitle,
aOptions.mBody,
aOptions.mDir,
aOptions.mLang,
tag,
aOptions.mIcon);
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
MOZ_ASSERT(window, "Window should not be null.");
notification->BindToOwner(window);
nsRefPtr<Notification> notification = CreateInternal(window,
EmptyString(),
aTitle,
aOptions);
// Queue a task to show the notification.
nsCOMPtr<nsIRunnable> showNotificationTask =
new NotificationTask(notification, NotificationTask::eShow);
NS_DispatchToMainThread(showNotificationTask);
NS_DispatchToCurrentThread(showNotificationTask);
// Persist the notification.
nsresult rv;
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
nsString origin;
aRv = GetOrigin(window, origin);
if (aRv.Failed()) {
return nullptr;
}
nsString id;
notification->GetID(id);
aRv = notificationStorage->Put(origin,
id,
aTitle,
DirectionToString(aOptions.mDir),
aOptions.mLang,
aOptions.mBody,
aOptions.mTag,
aOptions.mIcon);
if (aRv.Failed()) {
return nullptr;
}
return notification.forget();
}
nsresult
already_AddRefed<Notification>
Notification::CreateInternal(nsPIDOMWindow* aWindow,
const nsAString& aID,
const nsAString& aTitle,
const NotificationOptions& aOptions)
{
nsString id;
if (!aID.IsEmpty()) {
id = aID;
} else {
nsCOMPtr<nsIUUIDGenerator> uuidgen =
do_GetService("@mozilla.org/uuid-generator;1");
NS_ENSURE_TRUE(uuidgen, nullptr);
nsID uuid;
nsresult rv = uuidgen->GenerateUUIDInPlace(&uuid);
NS_ENSURE_SUCCESS(rv, nullptr);
char buffer[NSID_LENGTH];
uuid.ToProvidedString(buffer);
NS_ConvertASCIItoUTF16 convertedID(buffer);
id = convertedID;
}
nsRefPtr<Notification> notification = new Notification(id,
aTitle,
aOptions.mBody,
aOptions.mDir,
aOptions.mLang,
aOptions.mTag,
aOptions.mIcon);
notification->BindToOwner(aWindow);
return notification.forget();
}
void
Notification::ShowInternal()
{
nsCOMPtr<nsIAlertsService> alertService =
@ -337,7 +509,8 @@ Notification::ShowInternal()
NotificationPermission::Granted || !alertService) {
// We do not have permission to show a notification or alert service
// is not available.
return DispatchTrustedEvent(NS_LITERAL_STRING("error"));
DispatchTrustedEvent(NS_LITERAL_STRING("error"));
return;
}
nsresult rv;
@ -345,17 +518,19 @@ Notification::ShowInternal()
if (mIconUrl.Length() > 0) {
// Resolve image URL against document base URI.
nsIDocument* doc = GetOwner()->GetExtantDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
NS_ENSURE_TRUE(baseUri, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIURI> srcUri;
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
mIconUrl, doc, baseUri);
NS_ENSURE_SUCCESS(rv, rv);
if (srcUri) {
nsAutoCString src;
srcUri->GetSpec(src);
absoluteUrl = NS_ConvertUTF8toUTF16(src);
if (doc) {
nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
if (baseUri) {
nsCOMPtr<nsIURI> srcUri;
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
mIconUrl, doc, baseUri);
if (NS_SUCCEEDED(rv)) {
nsAutoCString src;
srcUri->GetSpec(src);
absoluteUrl = NS_ConvertUTF8toUTF16(src);
}
}
}
}
@ -363,7 +538,7 @@ Notification::ShowInternal()
nsString alertName;
rv = GetAlertName(alertName);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS_VOID(rv);
#ifdef MOZ_B2G
nsCOMPtr<nsIAppNotificationService> appNotifier =
@ -375,23 +550,26 @@ Notification::ShowInternal()
if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1");
nsString manifestUrl = EmptyString();
appsService->GetManifestURLByLocalId(appId, manifestUrl);
mozilla::AutoSafeJSContext cx;
JS::RootedValue val(cx);
AppNotificationServiceOptions ops;
ops.mTextClickable = true;
ops.mManifestURL = manifestUrl;
ops.mId = alertName;
ops.mDir = DirectionToString(mDir);
ops.mLang = mLang;
rv = appsService->GetManifestURLByLocalId(appId, manifestUrl);
if (NS_SUCCEEDED(rv)) {
mozilla::AutoSafeJSContext cx;
JS::RootedValue val(cx);
AppNotificationServiceOptions ops;
ops.mTextClickable = true;
ops.mManifestURL = manifestUrl;
ops.mId = alertName;
ops.mDir = DirectionToString(mDir);
ops.mLang = mLang;
if (!ops.ToObject(cx, JS::NullPtr(), &val)) {
NS_WARNING("Converting dict to object failed!");
return NS_ERROR_FAILURE;
if (!ops.ToObject(cx, JS::NullPtr(), &val)) {
NS_WARNING("Converting dict to object failed!");
return;
}
appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody,
observer, val);
return;
}
return appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody,
observer, val);
}
}
#endif
@ -400,9 +578,9 @@ Notification::ShowInternal()
// nsIObserver. Thus the cookie must be unique to differentiate observers.
nsString uniqueCookie = NS_LITERAL_STRING("notification:");
uniqueCookie.AppendInt(sCount++);
return alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
uniqueCookie, observer, alertName,
DirectionToString(mDir), mLang);
alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
uniqueCookie, observer, alertName,
DirectionToString(mDir), mLang);
}
void
@ -490,6 +668,47 @@ Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv)
}
}
already_AddRefed<Promise>
Notification::Get(const GlobalObject& aGlobal,
const GetNotificationOptions& aFilter,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
MOZ_ASSERT(window);
nsIDocument* doc = window->GetExtantDoc();
if (!doc) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsString origin;
aRv = GetOrigin(window, origin);
if (aRv.Failed()) {
return nullptr;
}
nsresult rv;
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
nsRefPtr<Promise> promise = new Promise(window);
nsCOMPtr<nsINotificationStorageCallback> callback =
new NotificationStorageCallback(aGlobal, window, promise);
nsString tag = aFilter.mTag.WasPassed() ?
aFilter.mTag.Value() :
EmptyString();
aRv = notificationStorage->Get(origin, tag, callback);
if (aRv.Failed()) {
return nullptr;
}
return promise.forget();
}
bool
Notification::PrefEnabled()
{
@ -511,22 +730,61 @@ Notification::Close()
NS_DispatchToMainThread(showNotificationTask);
}
nsresult
void
Notification::CloseInternal()
{
if (!mIsClosed) {
nsresult rv;
// Don't bail out if notification storage fails, since we still
// want to send the close event through the alert service.
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
if (notificationStorage) {
nsString origin;
rv = GetOrigin(GetOwner(), origin);
if (NS_SUCCEEDED(rv)) {
notificationStorage->Delete(origin, mID);
}
}
nsCOMPtr<nsIAlertsService> alertService =
do_GetService(NS_ALERTSERVICE_CONTRACTID);
if (alertService) {
nsString alertName;
nsresult rv = GetAlertName(alertName);
NS_ENSURE_SUCCESS(rv, rv);
rv = alertService->CloseAlert(alertName);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetAlertName(alertName);
if (NS_SUCCEEDED(rv)) {
alertService->CloseAlert(alertName);
}
}
}
}
nsresult
Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin)
{
MOZ_ASSERT(aWindow);
nsresult rv;
nsIDocument* doc = aWindow->GetExtantDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
nsIPrincipal* principal = doc->NodePrincipal();
NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED);
uint16_t appStatus = principal->GetAppStatus();
uint32_t appId = principal->GetAppId();
if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED ||
appId == nsIScriptSecurityManager::NO_APP_ID ||
appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// If we are in "app code", use manifest URL as unique origin since
// multiple apps can share the same origin but not same notifications.
nsCOMPtr<nsIAppsService> appsService =
do_GetService("@mozilla.org/AppsService;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
appsService->GetManifestURLByLocalId(appId, aOrigin);
}
return NS_OK;
}
@ -534,20 +792,18 @@ Notification::CloseInternal()
nsresult
Notification::GetAlertName(nsString& aAlertName)
{
// Get the notification name that is unique per origin + tag.
// The name of the alert is of the form origin#tag
nsPIDOMWindow* owner = GetOwner();
NS_ENSURE_TRUE(owner, NS_ERROR_UNEXPECTED);
nsIDocument* doc = owner->GetExtantDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
nsresult rv = nsContentUtils::GetUTFOrigin(doc->NodePrincipal(),
aAlertName);
// Get the notification name that is unique per origin + tag/ID.
// The name of the alert is of the form origin#tag/ID.
nsresult rv = GetOrigin(GetOwner(), aAlertName);
NS_ENSURE_SUCCESS(rv, rv);
aAlertName.AppendLiteral("#");
aAlertName.Append(mTag);
if (!mTag.IsEmpty()) {
aAlertName.Append(NS_LITERAL_STRING("tag:"));
aAlertName.Append(mTag);
} else {
aAlertName.Append(NS_LITERAL_STRING("notag:"));
aAlertName.Append(mID);
}
return NS_OK;
}

View File

@ -10,31 +10,37 @@
#include "nsDOMEventTargetHelper.h"
#include "nsIObserver.h"
#include "nsCycleCollectionParticipant.h"
namespace mozilla {
namespace dom {
class NotificationObserver;
class Promise;
class Notification : public nsDOMEventTargetHelper
{
friend class NotificationTask;
friend class NotificationPermissionRequest;
friend class NotificationObserver;
friend class NotificationStorageCallback;
public:
IMPL_EVENT_HANDLER(click)
IMPL_EVENT_HANDLER(show)
IMPL_EVENT_HANDLER(error)
IMPL_EVENT_HANDLER(close)
Notification(const nsAString& aTitle, const nsAString& aBody,
NotificationDirection aDir, const nsAString& aLang,
const nsAString& aTag, const nsAString& aIconUrl);
static already_AddRefed<Notification> Constructor(const GlobalObject& aGlobal,
const nsAString& aTitle,
const NotificationOptions& aOption,
ErrorResult& aRv);
void GetTitle(nsString& aRetval)
void GetID(nsAString& aRetval) {
aRetval = mID;
}
void GetTitle(nsAString& aRetval)
{
aRetval = mTitle;
}
@ -44,24 +50,22 @@ public:
return mDir;
}
void GetLang(nsString& aRetval)
void GetLang(nsAString& aRetval)
{
aRetval = mLang;
}
void GetBody(nsString& aRetval)
void GetBody(nsAString& aRetval)
{
aRetval = mBody;
}
void GetTag(nsString& aRetval)
void GetTag(nsAString& aRetval)
{
if (StringBeginsWith(mTag, NS_LITERAL_STRING("tag:"))) {
aRetval = Substring(mTag, 4);
}
aRetval = mTag;
}
void GetIcon(nsString& aRetval)
void GetIcon(nsAString& aRetval)
{
aRetval = mIconUrl;
}
@ -73,6 +77,10 @@ public:
static NotificationPermission GetPermission(const GlobalObject& aGlobal,
ErrorResult& aRv);
static already_AddRefed<Promise> Get(const GlobalObject& aGlobal,
const GetNotificationOptions& aFilter,
ErrorResult& aRv);
void Close();
static bool PrefEnabled();
@ -85,8 +93,17 @@ public:
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
protected:
nsresult ShowInternal();
nsresult CloseInternal();
Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
NotificationDirection aDir, const nsAString& aLang,
const nsAString& aTag, const nsAString& aIconUrl);
static already_AddRefed<Notification> CreateInternal(nsPIDOMWindow* aWindow,
const nsAString& aID,
const nsAString& aTitle,
const NotificationOptions& aOptions);
void ShowInternal();
void CloseInternal();
static NotificationPermission GetPermissionInternal(nsISupports* aGlobal,
ErrorResult& rv);
@ -103,8 +120,22 @@ protected:
}
}
static const NotificationDirection StringToDirection(const nsAString& aDirection)
{
if (aDirection.EqualsLiteral("ltr")) {
return NotificationDirection::Ltr;
}
if (aDirection.EqualsLiteral("rtl")) {
return NotificationDirection::Rtl;
}
return NotificationDirection::Auto;
}
static nsresult GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin);
nsresult GetAlertName(nsString& aAlertName);
nsString mID;
nsString mTitle;
nsString mBody;
NotificationDirection mDir;

View File

@ -0,0 +1,270 @@
/* 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";
this.EXPORTED_SYMBOLS = [];
const DEBUG = false;
function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); }
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageListenerManager");
XPCOMUtils.defineLazyGetter(this, "gEncoder", function() {
return new TextEncoder();
});
XPCOMUtils.defineLazyGetter(this, "gDecoder", function() {
return new TextDecoder();
});
const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir;
const NOTIFICATION_STORE_PATH =
OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json");
let NotificationDB = {
init: function() {
this.notifications = {};
this.byTag = {};
this.loaded = false;
this.tasks = []; // read/write operation queue
this.runningTask = false;
ppmm.addMessageListener("Notification:Save", this);
ppmm.addMessageListener("Notification:Delete", this);
ppmm.addMessageListener("Notification:GetAll", this);
},
// Attempt to read notification file, if it's not there we will create it.
load: function(callback) {
var promise = OS.File.read(NOTIFICATION_STORE_PATH);
promise.then(
function onSuccess(data) {
try {
this.notifications = JSON.parse(gDecoder.decode(data));
} catch (e) {
if (DEBUG) { debug("Unable to parse file data " + e); }
}
this.loaded = true;
callback && callback();
}.bind(this),
// If read failed, we assume we have no notifications to load.
function onFailure(reason) {
this.loaded = true;
this.createStore(callback);
}.bind(this)
);
},
// Creates the notification directory.
createStore: function(callback) {
var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, {
ignoreExisting: true
});
promise.then(
function onSuccess() {
this.createFile(callback);
}.bind(this),
function onFailure(reason) {
if (DEBUG) { debug("Directory creation failed:" + reason); }
callback && callback();
}
);
},
// Creates the notification file once the directory is created.
createFile: function(callback) {
var promise = OS.File.open(NOTIFICATION_STORE_PATH, {create: true});
promise.then(
function onSuccess(handle) {
callback && callback();
},
function onFailure(reason) {
if (DEBUG) { debug("File creation failed:" + reason); }
callback && callback();
}
);
},
// Save current notifications to the file.
save: function(callback) {
var data = gEncoder.encode(JSON.stringify(this.notifications));
var promise = OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data);
promise.then(
function onSuccess() {
callback && callback();
},
function onFailure(reason) {
if (DEBUG) { debug("Save failed:" + reason); }
callback && callback();
}
);
},
// Helper function: callback will be called once file exists and/or is loaded.
ensureLoaded: function(callback) {
if (!this.loaded) {
this.load(callback);
} else {
callback();
}
},
receiveMessage: function(message) {
if (DEBUG) { debug("Received message:" + message.name); }
switch (message.name) {
case "Notification:GetAll":
this.queueTask("getall", message.data, function(notifications) {
message.target.sendAsyncMessage("Notification:GetAll:Return:OK", {
requestID: message.data.requestID,
notifications: notifications
});
});
break;
case "Notification:Save":
this.queueTask("save", message.data, function() {
message.target.sendAsyncMessage("Notification:Save:Return:OK", {
requestID: message.data.requestID
});
});
break;
case "Notification:Delete":
this.queueTask("delete", message.data, function() {
message.target.sendAsyncMessage("Notification:Delete:Return:OK", {
requestID: message.data.requestID
});
});
break;
default:
if (DEBUG) { debug("Invalid message name" + message.name); }
}
},
// We need to make sure any read/write operations are atomic,
// so use a queue to run each operation sequentially.
queueTask: function(operation, data, callback) {
if (DEBUG) { debug("Queueing task: " + operation); }
this.tasks.push({
operation: operation,
data: data,
callback: callback
});
// Only run immediately if we aren't currently running another task.
if (!this.runningTask) {
if (DEBUG) { dump("Task queue was not running, starting now..."); }
this.runNextTask();
}
},
runNextTask: function() {
if (this.tasks.length === 0) {
if (DEBUG) { dump("No more tasks to run, queue depleted"); }
this.runningTask = false;
return;
}
this.runningTask = true;
// Always make sure we are loaded before performing any read/write tasks.
this.ensureLoaded(function() {
var task = this.tasks.shift();
// Wrap the task callback to make sure we immediately
// run the next task after running the original callback.
var wrappedCallback = function() {
if (DEBUG) { debug("Finishing task: " + task.operation); }
task.callback.apply(this, arguments);
this.runNextTask();
}.bind(this);
switch (task.operation) {
case "getall":
this.taskGetAll(task.data, wrappedCallback);
break;
case "save":
this.taskSave(task.data, wrappedCallback);
break;
case "delete":
this.taskDelete(task.data, wrappedCallback);
break;
}
}.bind(this));
},
taskGetAll: function(data, callback) {
if (DEBUG) { debug("Task, getting all"); }
var origin = data.origin;
var notifications = [];
// Grab only the notifications for specified origin.
for (var i in this.notifications[origin]) {
notifications.push(this.notifications[origin][i]);
}
callback(notifications);
},
taskSave: function(data, callback) {
if (DEBUG) { debug("Task, saving"); }
var origin = data.origin;
var notification = data.notification;
if (!this.notifications[origin]) {
this.notifications[origin] = {};
this.byTag[origin] = {};
}
// We might have existing notification with this tag,
// if so we need to remove it before saving the new one.
if (notification.tag && this.byTag[origin][notification.tag]) {
var oldNotification = this.byTag[origin][notification.tag];
delete this.notifications[origin][oldNotification.id];
this.byTag[origin][notification.tag] = notification;
}
this.notifications[origin][notification.id] = notification;
this.save(callback);
},
taskDelete: function(data, callback) {
if (DEBUG) { debug("Task, deleting"); }
var origin = data.origin;
var id = data.id;
if (!this.notifications[origin]) {
if (DEBUG) { debug("No notifications found for origin: " + origin); }
return;
}
// Make sure we can find the notification to delete.
var oldNotification = this.notifications[origin][id];
if (!oldNotification) {
if (DEBUG) { debug("No notification found with id: " + id); }
return;
}
if (oldNotification.tag) {
delete this.byTag[origin][oldNotification.tag];
}
delete this.notifications[origin][id];
this.save(callback);
}
};
NotificationDB.init();

View File

@ -0,0 +1,174 @@
/* 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 DEBUG = false;
function debug(s) { dump("-*- NotificationStorage.js: " + s + "\n"); }
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const NOTIFICATIONSTORAGE_CID = "{37f819b0-0b5c-11e3-8ffd-0800200c9a66}";
const NOTIFICATIONSTORAGE_CONTRACTID = "@mozilla.org/notificationStorage;1";
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
function NotificationStorage() {
// cache objects
this._notifications = {};
this._byTag = {};
this._cached = false;
this._requests = {};
this._requestCount = 0;
// Register for message listeners.
cpmm.addMessageListener("Notification:GetAll:Return:OK", this);
}
NotificationStorage.prototype = {
put: function(origin, id, title, dir, lang, body, tag, icon) {
if (DEBUG) { debug("PUT: " + id + ": " + title); }
var notification = {
id: id,
title: title,
dir: dir,
lang: lang,
body: body,
tag: tag,
icon: icon
};
this._notifications[id] = notification;
if (tag) {
// We might have existing notification with this tag,
// if so we need to remove it from our cache.
if (this._byTag[tag]) {
var oldNotification = this._byTag[tag];
delete this._notifications[oldNotification.id];
}
this._byTag[tag] = notification;
};
cpmm.sendAsyncMessage("Notification:Save", {
origin: origin,
notification: notification
});
},
get: function(origin, tag, callback) {
if (DEBUG) { debug("GET: " + tag); }
if (this._cached) {
this._fetchFromCache(tag, callback);
} else {
this._fetchFromDB(origin, tag, callback);
}
},
delete: function(origin, id) {
if (DEBUG) { debug("DELETE: " + id); }
var notification = this._notifications[id];
if (notification) {
if (notification.tag) {
delete this._byTag[notification.tag];
}
delete this._notifications[id];
}
cpmm.sendAsyncMessage("Notification:Delete", {
origin: origin,
id: id
});
},
receiveMessage: function(message) {
switch (message.name) {
case "Notification:GetAll:Return:OK":
var request = this._requests[message.data.requestID];
delete this._requests[message.data.requestID];
this._populateCache(message.data.notifications);
this._fetchFromCache(request.tag, request.callback);
break;
default:
if (DEBUG) debug("Unrecognized message: " + message.name);
break;
}
},
_fetchFromDB: function(origin, tag, callback) {
var request = {
origin: origin,
tag: tag,
callback: callback
};
var requestID = this._requestCount++;
this._requests[requestID] = request;
cpmm.sendAsyncMessage("Notification:GetAll", {
origin: origin,
requestID: requestID
});
},
_fetchFromCache: function(tag, callback) {
var notifications = [];
// If a tag was specified and we have a notification
// with this tag, return that. If no tag was specified
// simple return all stored notifications.
if (tag && this._byTag[tag]) {
notifications.push(this._byTag[tag]);
} else if (!tag) {
for (var id in this._notifications) {
notifications.push(this._notifications[id]);
}
}
// Pass each notification back separately.
notifications.forEach(function(notification) {
try {
callback.handle(notification.id,
notification.title,
notification.dir,
notification.lang,
notification.body,
notification.tag,
notification.icon);
} catch (e) {
if (DEBUG) { debug("Error calling callback handle: " + e); }
}
});
try {
callback.done();
} catch (e) {
if (DEBUG) { debug("Error calling callback done: " + e); }
}
},
_populateCache: function(notifications) {
notifications.forEach(function(notification) {
this._notifications[notification.id] = notification;
if (notification.tag) {
this._byTag[notification.tag] = notification;
}
}.bind(this));
this._cached = true;
},
classID : Components.ID(NOTIFICATIONSTORAGE_CID),
contractID : NOTIFICATIONSTORAGE_CONTRACTID,
QueryInterface: XPCOMUtils.generateQI([Ci.nsINotificationStorage,
Ci.nsIMessageListener]),
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NotificationStorage]);

View File

@ -0,0 +1,3 @@
# NotificationStorage.js
component {37f819b0-0b5c-11e3-8ffd-0800200c9a66} NotificationStorage.js
contract @mozilla.org/notificationStorage;1 {37f819b0-0b5c-11e3-8ffd-0800200c9a66}

View File

@ -6,6 +6,15 @@
MODULE = 'dom'
EXTRA_COMPONENTS += [
'NotificationStorage.js',
'NotificationStorage.manifest',
]
EXTRA_JS_MODULES += [
'NotificationDB.jsm'
]
EXPORTS.mozilla.dom += [
'DesktopNotification.h',
'Notification.h',

View File

@ -29,7 +29,3 @@ DIRS += [
if CONFIG['MOZ_GAMEPAD']:
DIRS += ['gamepad']
#needs IPC support, also tests do not run successfully in Firefox for now
#if CONFIG['MOZ_BUILD_APP'] != 'mobile':
# DIRS += ['notification']

View File

@ -0,0 +1,81 @@
var MockServices = (function () {
"use strict";
const MOCK_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components)
.ID("{48068bc2-40ab-4904-8afd-4cdfb3a385f3}");
const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
const MOCK_SYSTEM_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components)
.ID("{e86d888c-e41b-4b78-9104-2f2742a532de}");
const SYSTEM_ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/system-alerts-service;1";
var registrar = SpecialPowers.wrap(SpecialPowers.Components).manager
.QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar);
var activeNotifications = Object.create(null);
var mockAlertsService = {
showAlertNotification: function(imageUrl, title, text, textClickable,
cookie, alertListener, name) {
var listener = SpecialPowers.wrap(alertListener);
activeNotifications[name] = {
listener: listener,
cookie: cookie
};
// fake async alert show event
setTimeout(function () {
listener.observe(null, "alertshow", cookie);
}, 100);
// ?? SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", cookie);
},
showAppNotification: function(imageUrl, title, text, textClickable,
manifestURL, alertListener, name) {
this.showAlertNotification(imageUrl, title, text, textClickable, "", alertListener, name);
},
closeAlert: function(name) {
var notification = activeNotifications[name];
if (notification) {
notification.listener.observe(null, "alertfinished", notification.cookie);
delete activeNotifications[name];
}
},
QueryInterface: function(aIID) {
if (SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) ||
SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIAlertsService)) {
return this;
}
throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
},
createInstance: function(aOuter, aIID) {
if (aOuter != null) {
throw SpecialPowers.Components.results.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(aIID);
}
};
mockAlertsService = SpecialPowers.wrapCallbackObject(mockAlertsService);
// MockServices API
return {
register: function () {
registrar.registerFactory(MOCK_ALERTS_CID, "alerts service",
ALERTS_SERVICE_CONTRACT_ID,
mockAlertsService);
registrar.registerFactory(MOCK_SYSTEM_ALERTS_CID, "system alerts service",
SYSTEM_ALERTS_SERVICE_CONTRACT_ID,
mockAlertsService);
},
unregister: function () {
registrar.unregisterFactory(MOCK_ALERTS_CID, mockAlertsService);
registrar.unregisterFactory(MOCK_SYSTEM_ALERTS_CID, mockAlertsService);
},
};
})();

View File

@ -0,0 +1,73 @@
var NotificationTest = (function () {
"use strict";
function info(msg, name) {
SimpleTest.info("::Notification Tests::" + (name || ""), msg);
}
function setup_testing_env() {
SimpleTest.waitForExplicitFinish();
// turn on testing pref (used by notification.cpp, and mock the alerts
SpecialPowers.setBoolPref("notification.prompt.testing", true);
}
function teardown_testing_env() {
SimpleTest.finish();
}
function executeTests(tests, callback) {
// context is `this` object in test functions
// it can be used to track data between tests
var context = {};
(function executeRemainingTests(remainingTests) {
if (!remainingTests.length) {
return callback();
}
var nextTest = remainingTests.shift();
var finishTest = executeRemainingTests.bind(null, remainingTests);
var startTest = nextTest.call.bind(nextTest, context, finishTest);
try {
startTest();
// if no callback was defined for test function,
// we must manually invoke finish to continue
if (nextTest.length === 0) {
finishTest();
}
} catch (e) {
ok(false, "Test threw exception!");
finishTest();
}
})(tests);
}
// NotificationTest API
return {
run: function (tests, callback) {
setup_testing_env();
addLoadEvent(function () {
executeTests(tests, function () {
teardown_testing_env();
callback && callback();
});
});
},
allowNotifications: function () {
SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
},
denyNotifications: function () {
SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
},
clickNotification: function (notification) {
// TODO: how??
},
info: info
};
})();

View File

@ -0,0 +1,6 @@
# -*- 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/.

View File

@ -1,10 +1,7 @@
[DEFAULT]
support-files =
create_notification.html
notification_common.js
MockServices.js
NotificationTest.js
[test_basic_notification.html]
[test_basic_notification_click.html]
[test_leak_windowClose.html]
[test_notification_tag.html]
[test_web_notifications.html]
[test_notification_basics.html]
[test_notification_storage.html]

View File

@ -0,0 +1,115 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Notification Basics</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="MockServices.js"></script>
<script type="text/javascript" src="NotificationTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script type="text/javascript">
var info = NotificationTest.info;
var steps = [
function () {
info("Test notification spec");
ok(Notification, "Notification constructor exists");
ok(Notification.permission, "Notification.permission exists");
ok(Notification.requestPermission, "Notification.requestPermission exists");
ok(Notification.get, "Notification.get exists");
},
function () {
info("Test blank requestPermission");
Notification.requestPermission();
},
function (done) {
info("Test requestPermission deny");
NotificationTest.denyNotifications();
Notification.requestPermission(function(perm) {
is(perm, "denied", "Permission should be denied.");
is(Notification.permission, "denied", "Permission should be denied.");
done();
});
},
function (done) {
info("Test requestPermission grant");
NotificationTest.allowNotifications();
Notification.requestPermission(function (perm) {
is(perm, "granted", "Permission should be granted.");
is(Notification.permission, "granted", "Permission should be granted");
done();
});
},
function () {
info("Test invalid requestPermission");
try {
Notification.requestPermission({});
ok(false, "Non callable arg to requestPermission should throw");
} catch (e) {
ok(true, "Non callable arg to requestPermission should throw");
}
},
function (done) {
info("Test create notification");
var options = {
dir: "auto",
lang: "",
body: "This is a notification body",
tag: "sometag",
icon: "icon.png"
};
var notification = new Notification("This is a title", options);
ok(notification, "Notification exists");
is(notification.onclick, null, "onclick() should be null");
is(notification.onshow, null, "onshow() should be null");
is(notification.onerror, null, "onerror() should be null");
is(notification.onclose, null, "onclose() should be null");
is(typeof notification.close, "function", "close() should exist");
is(notification.dir, options.dir, "auto should get set");
is(notification.lang, options.lang, "lang should get set");
is(notification.body, options.body, "body should get set");
is(notification.tag, options.tag, "tag should get set");
is(notification.icon, options.icon, "icon should get set");
// store notification in test context
this.notification = notification;
notification.onshow = function () {
ok(true, "onshow handler should be called");
done();
};
},
function (done) {
info("Test closing a notification");
var notification = this.notification;
notification.onclose = function () {
ok(true, "onclose handler should be called");
done();
};
notification.close();
},
];
MockServices.register();
NotificationTest.run(steps, function () {
MockServices.unregister();
});
</script>
</body>
</html>

View File

@ -0,0 +1,132 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Notification Basics</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="MockServices.js"></script>
<script type="text/javascript" src="NotificationTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script type="text/javascript">
function deleteAllNotifications() {
var promise = Notification.get();
promise.then(function (notifications) {
notifications.forEach(function(notification) {
notification.close();
});
});
}
var info = NotificationTest.info;
var steps = [
function (done) {
info("Test that Notifcation.get fulfills the promise");
var promise = Notification.get();
ok(promise.then, "should return a promise");
// Create a new notification to make sure
// Notification.get() works while creating
var notification = new Notification("this is a test");
promise.then(function () {
ok(true, "promise should be fulfilled");
done();
});
},
deleteAllNotifications,
function (done) {
info("Test adding a notification, and making sure get returns it");
NotificationTest.allowNotifications();
var options = {
dir: "auto",
lang: "",
body: "This is a notification body",
tag: "sometag",
icon: "icon.png"
};
var notification = new Notification("This is a title", options);
var promise = Notification.get();
promise.then(function (notifications) {
ok(notifications.length, "should return notifications");
for (var i = 0; i < notifications.length; i++) {
var notification = notifications[i];
if (notification.tag === options.tag) {
ok(true, "should contain newly created notification");
for (var key in options) {
is(notification[key], options[key], key + " property should match");
}
notification.close();
return;
}
}
ok(false, "should contain newly created notification");
notification.close();
});
notification.onclose = done;
},
function (done) {
info("Testing fetching notification by tag filter");
var n1 = new Notification("title1", {tag: "tag1"});
var n2 = new Notification("title2", {tag: "tag2"});
var n3 = new Notification("title3", {tag: "tag3"});
var promise = Notification.get({tag: "tag3"});
promise.then(function (notifications) {
var notification = notifications[0];
is(notifications.length, 1, "should return 1 notification");
is(notifications[0].title, "title3", "titles should match");
is(notifications[0].tag, "tag3", "tags should match");
var closeCount = 0;
var waitForAll = function () {
if (++closeCount >= 3) {
done();
}
};
n1.onclose = waitForAll;
n2.onclose = waitForAll;
n3.onclose = waitForAll;
n1.close();
n2.close();
n3.close();
});
},
deleteAllNotifications,
function (done) {
info("Testing fetching no notifications");
var promise = Notification.get();
promise.then(function (notifications) {
is(notifications.length, 0, "should return 0 notifications");
done();
});
},
function (done) {
info("Testing fetching multiple notifications");
var n1 = new Notification("title1");
var n2 = new Notification("title2");
var n3 = new Notification("title3");
var promise = Notification.get();
promise.then(function (notifications) {
is(notifications.length, 3, "should return 2 notifications");
done();
});
}
];
MockServices.register();
NotificationTest.run(steps, function () {
MockServices.unregister();
});
</script>
</body>
</html>

View File

@ -1,100 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=782211
-->
<head>
<title>Bug 782211</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="notification_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782211">Bug 782211</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="text/javascript">
if (window.Notification) {
SimpleTest.waitForExplicitFinish();
function showNotifications() {
// Make sure callback is called.
Notification.requestPermission(function(perm) {
is(perm, "granted", "Permission should be granted.");
is(Notification.permission, "granted", "Permission should be granted.");
callbackCalled();
});
// Make sure nothing bad happens when requestPermission is called without a callback.
Notification.requestPermission();
try {
Notification.requestPermission({});
ok(false, "Non callable arugment to request permission should throw exception.");
} catch (ex) {
ok(true, "Non callable arugment to request permission should throw exception.");
}
var title = "This is a title";
var notification = new Notification(title);
is(notification.title, title, "Title should be set");
is(notification.dir, "auto", "Dir should default to 'auto'");
is(notification.lang, "", "Lang should not be set");
is(notification.body, "", "Body should not be set");
is(notification.tag, "", "Tag should not be set");
var options = {
dir: "auto",
lang: "",
body: "This is a notification body",
tag: "sometag"
};
var notification = new Notification(title, options);
is(notification.title, title, "Title should be set");
is(notification.dir, options.dir, "Dir should be set");
is(notification.lang, options.lang, "Lang should be set");
is(notification.body, options.body, "Body should be set");
is(notification.tag, options.tag, "Tag should be set");
notification.onclose = function() {
ok(true, "Notification should be closed.");
callbackCalled();
};
notification.onshow = function() {
ok(true, "Notification should be shown.");
notification.close();
callbackCalled();
};
notification.onerror = function() {
ok(false, "Failed to show notification.");
reset_notifications();
SimpleTest.finish();
};
var numCallbacksCalled = 0;
function callbackCalled() {
numCallbacksCalled++;
if (numCallbacksCalled == 3) {
reset_notifications();
SimpleTest.finish();
}
}
}
setup_notifications(true, true, showNotifications);
} else {
ok(true, "Notifications are not enabled on the platform.");
}
</script>
</body>
</html>

12
dom/webidl/AppInfo.webidl Normal file
View File

@ -0,0 +1,12 @@
/* 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 dictionnary holds the parameters supporting the app:// protocol.
*/
dictionary AppInfo
{
DOMString path = "";
boolean isCoreApp = false;
};

View File

@ -28,6 +28,7 @@ interface DummyInterface : EventTarget {
void WifiOptions(optional WifiCommandOptions arg1,
optional WifiResultOptions arg2);
void AppNotificationServiceOptions(optional AppNotificationServiceOptions arg);
void AppInfo(optional AppInfo arg1);
};
interface DummyInterfaceWorkers {

View File

@ -19,6 +19,9 @@ interface Notification : EventTarget {
[Throws]
static void requestPermission(optional NotificationPermissionCallback permissionCallback);
[Throws]
static Promise get(optional GetNotificationOptions filter);
attribute EventHandler onclick;
attribute EventHandler onshow;
@ -52,10 +55,14 @@ dictionary NotificationOptions {
NotificationDirection dir = "auto";
DOMString lang = "";
DOMString body = "";
DOMString tag;
DOMString tag = "";
DOMString icon = "";
};
dictionary GetNotificationOptions {
DOMString tag;
};
enum NotificationPermission {
"default",
"denied",

View File

@ -19,6 +19,7 @@ WEBIDL_FILES = [
'AbstractWorker.webidl',
'AnalyserNode.webidl',
'AnimationEvent.webidl',
'AppInfo.webidl',
'AppNotificationServiceOptions.webidl',
'ArchiveReader.webidl',
'ArchiveRequest.webidl',

View File

@ -173,14 +173,14 @@ static float gYStationarySizeMultiplier = 2.5f;
/**
* The time period in ms that throttles mozbrowserasyncscroll event.
* Default is 100ms if there is no "apzc.asyncscroll.throttle" in preference.
* Default is 100ms if there is no "apz.asyncscroll.throttle" in preference.
*/
static int gAsyncScrollThrottleTime = 100;
/**
* The timeout in ms for mAsyncScrollTimeoutTask delay task.
* Default is 300ms if there is no "apzc.asyncscroll.timeout" in preference.
* Default is 300ms if there is no "apz.asyncscroll.timeout" in preference.
*/
static int gAsyncScrollTimeout = 300;
@ -236,21 +236,21 @@ AsyncPanZoomController::InitializeGlobalState()
return;
sInitialized = true;
Preferences::AddIntVarCache(&gPanRepaintInterval, "gfx.azpc.pan_repaint_interval", gPanRepaintInterval);
Preferences::AddIntVarCache(&gFlingRepaintInterval, "gfx.azpc.fling_repaint_interval", gFlingRepaintInterval);
Preferences::AddFloatVarCache(&gMinSkateSpeed, "gfx.azpc.min_skate_speed", gMinSkateSpeed);
Preferences::AddIntVarCache(&gTouchListenerTimeout, "gfx.azpc.touch_listener_timeout", gTouchListenerTimeout);
Preferences::AddIntVarCache(&gNumPaintDurationSamples, "gfx.azpc.num_paint_duration_samples", gNumPaintDurationSamples);
Preferences::AddFloatVarCache(&gTouchStartTolerance, "gfx.azpc.touch_start_tolerance", gTouchStartTolerance);
Preferences::AddFloatVarCache(&gXSkateSizeMultiplier, "gfx.azpc.x_skate_size_multiplier", gXSkateSizeMultiplier);
Preferences::AddFloatVarCache(&gYSkateSizeMultiplier, "gfx.azpc.y_skate_size_multiplier", gYSkateSizeMultiplier);
Preferences::AddFloatVarCache(&gXStationarySizeMultiplier, "gfx.azpc.x_stationary_size_multiplier", gXStationarySizeMultiplier);
Preferences::AddFloatVarCache(&gYStationarySizeMultiplier, "gfx.azpc.y_stationary_size_multiplier", gYStationarySizeMultiplier);
Preferences::AddIntVarCache(&gAsyncScrollThrottleTime, "apzc.asyncscroll.throttle", gAsyncScrollThrottleTime);
Preferences::AddIntVarCache(&gAsyncScrollTimeout, "apzc.asyncscroll.timeout", gAsyncScrollTimeout);
Preferences::AddBoolVarCache(&gAsyncZoomDisabled, "apzc.asynczoom.disabled", gAsyncZoomDisabled);
Preferences::AddBoolVarCache(&gCrossSlideEnabled, "apzc.cross_slide.enabled", gCrossSlideEnabled);
Preferences::AddIntVarCache(&gAxisLockMode, "apzc.axis_lock_mode", gAxisLockMode);
Preferences::AddIntVarCache(&gPanRepaintInterval, "apz.pan_repaint_interval", gPanRepaintInterval);
Preferences::AddIntVarCache(&gFlingRepaintInterval, "apz.fling_repaint_interval", gFlingRepaintInterval);
Preferences::AddFloatVarCache(&gMinSkateSpeed, "apz.min_skate_speed", gMinSkateSpeed);
Preferences::AddIntVarCache(&gTouchListenerTimeout, "apz.touch_listener_timeout", gTouchListenerTimeout);
Preferences::AddIntVarCache(&gNumPaintDurationSamples, "apz.num_paint_duration_samples", gNumPaintDurationSamples);
Preferences::AddFloatVarCache(&gTouchStartTolerance, "apz.touch_start_tolerance", gTouchStartTolerance);
Preferences::AddFloatVarCache(&gXSkateSizeMultiplier, "apz.x_skate_size_multiplier", gXSkateSizeMultiplier);
Preferences::AddFloatVarCache(&gYSkateSizeMultiplier, "apz.y_skate_size_multiplier", gYSkateSizeMultiplier);
Preferences::AddFloatVarCache(&gXStationarySizeMultiplier, "apz.x_stationary_size_multiplier", gXStationarySizeMultiplier);
Preferences::AddFloatVarCache(&gYStationarySizeMultiplier, "apz.y_stationary_size_multiplier", gYStationarySizeMultiplier);
Preferences::AddIntVarCache(&gAsyncScrollThrottleTime, "apz.asyncscroll.throttle", gAsyncScrollThrottleTime);
Preferences::AddIntVarCache(&gAsyncScrollTimeout, "apz.asyncscroll.timeout", gAsyncScrollTimeout);
Preferences::AddBoolVarCache(&gAsyncZoomDisabled, "apz.asynczoom.disabled", gAsyncZoomDisabled);
Preferences::AddBoolVarCache(&gCrossSlideEnabled, "apz.cross_slide.enabled", gCrossSlideEnabled);
Preferences::AddIntVarCache(&gAxisLockMode, "apz.axis_lock_mode", gAxisLockMode);
gComputedTimingFunction = new ComputedTimingFunction();
gComputedTimingFunction->Init(

View File

@ -67,12 +67,12 @@ static int gMaxVelocityQueueSize = 5;
static void ReadAxisPrefs()
{
Preferences::AddFloatVarCache(&gMaxEventAcceleration, "gfx.axis.max_event_acceleration", gMaxEventAcceleration);
Preferences::AddFloatVarCache(&gFlingFriction, "gfx.axis.fling_friction", gFlingFriction);
Preferences::AddFloatVarCache(&gVelocityThreshold, "gfx.axis.velocity_threshold", gVelocityThreshold);
Preferences::AddFloatVarCache(&gAccelerationMultiplier, "gfx.axis.acceleration_multiplier", gAccelerationMultiplier);
Preferences::AddFloatVarCache(&gFlingStoppedThreshold, "gfx.axis.fling_stopped_threshold", gFlingStoppedThreshold);
Preferences::AddIntVarCache(&gMaxVelocityQueueSize, "gfx.axis.max_velocity_queue_size", gMaxVelocityQueueSize);
Preferences::AddFloatVarCache(&gMaxEventAcceleration, "apz.max_event_acceleration", gMaxEventAcceleration);
Preferences::AddFloatVarCache(&gFlingFriction, "apz.fling_friction", gFlingFriction);
Preferences::AddFloatVarCache(&gVelocityThreshold, "apz.velocity_threshold", gVelocityThreshold);
Preferences::AddFloatVarCache(&gAccelerationMultiplier, "apz.acceleration_multiplier", gAccelerationMultiplier);
Preferences::AddFloatVarCache(&gFlingStoppedThreshold, "apz.fling_stopped_threshold", gFlingStoppedThreshold);
Preferences::AddIntVarCache(&gMaxVelocityQueueSize, "apz.max_velocity_queue_size", gMaxVelocityQueueSize);
}
class ReadAxisPref MOZ_FINAL : public nsRunnable {

View File

@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/JNI.jsm");
Cu.import('resource://gre/modules/Payment.jsm');
Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
Cu.import("resource://gre/modules/ContactService.jsm");
Cu.import("resource://gre/modules/NotificationDB.jsm");
Cu.import("resource://gre/modules/SpatialNavigation.jsm");
#ifdef ACCESSIBILITY

View File

@ -282,6 +282,8 @@
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/PhoneNumberService.js
@BINPATH@/components/PhoneNumberService.manifest
@BINPATH@/components/NotificationStorage.js
@BINPATH@/components/NotificationStorage.manifest
@BINPATH@/components/SettingsManager.js
@BINPATH@/components/SettingsManager.manifest
@BINPATH@/components/SettingsService.js
@ -385,8 +387,6 @@
@BINPATH@/components/Webapps.manifest
@BINPATH@/components/AppsService.js
@BINPATH@/components/AppsService.manifest
@BINPATH@/components/AppProtocolHandler.js
@BINPATH@/components/AppProtocolHandler.manifest
@BINPATH@/components/Push.js
@BINPATH@/components/Push.manifest

View File

@ -287,7 +287,7 @@ pref("media.audio_data.enabled", true);
// 0 = FREE (No locking at all)
// 1 = STANDARD (Once locked, remain locked until scrolling ends)
// 2 = STICKY (Allow lock to be broken, with hysteresis)
pref("apzc.axis_lock_mode", 0);
pref("apz.axis_lock_mode", 0);
#ifdef XP_MACOSX
// Whether to run in native HiDPI mode on machines with "Retina"/HiDPI display;

View File

@ -69,6 +69,7 @@ LOCAL_INCLUDES = \
-I$(srcdir)/../mime \
-I$(srcdir)/../cache \
-I$(srcdir)/../protocol/about \
-I$(srcdir)/../protocol/app \
-I../dns \
$(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \
-I$(srcdir)/../protocol/$(d)) \

View File

@ -602,6 +602,18 @@
{0x93, 0x44, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \
}
/******************************************************************************
* netwerk/protocol/app/ classes
*/
#define NS_APPPROTOCOLHANDLER_CID \
{ /* {B6ED3030-9999-11d3-A178-0050041CAF44} */ \
0xb6ed3030, \
0x9999, \
0x11d3, \
{0xa1, 0x78, 0x00, 0x50, 0x04, 0x1c, 0xaf, 0x44} \
}
/******************************************************************************
* netwerk/protocol/data/ classes
*/

View File

@ -243,6 +243,7 @@ namespace net {
NS_GENERIC_FACTORY_CONSTRUCTOR(Dashboard)
}
}
#include "AppProtocolHandler.h"
#ifdef NECKO_PROTOCOL_res
// resource
@ -772,6 +773,7 @@ NS_DEFINE_NAMED_CID(NS_CACHESERVICE_CID);
NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHESERVICE_CID);
NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHENAMESPACE_CID);
NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHE_CID);
NS_DEFINE_NAMED_CID(NS_APPPROTOCOLHANDLER_CID);
#ifdef NECKO_COOKIES
NS_DEFINE_NAMED_CID(NS_COOKIEMANAGER_CID);
NS_DEFINE_NAMED_CID(NS_COOKIESERVICE_CID);
@ -910,6 +912,7 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
{ &kNS_APPLICATIONCACHESERVICE_CID, false, nullptr, nsApplicationCacheServiceConstructor },
{ &kNS_APPLICATIONCACHENAMESPACE_CID, false, nullptr, nsApplicationCacheNamespaceConstructor },
{ &kNS_APPLICATIONCACHE_CID, false, nullptr, nsApplicationCacheConstructor },
{ &kNS_APPPROTOCOLHANDLER_CID, false, nullptr, AppProtocolHandler::Create },
#ifdef NECKO_COOKIES
{ &kNS_COOKIEMANAGER_CID, false, nullptr, nsICookieServiceConstructor },
{ &kNS_COOKIESERVICE_CID, false, nullptr, nsICookieServiceConstructor },
@ -1055,6 +1058,7 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
{ NS_APPLICATIONCACHESERVICE_CONTRACTID, &kNS_APPLICATIONCACHESERVICE_CID },
{ NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &kNS_APPLICATIONCACHENAMESPACE_CID },
{ NS_APPLICATIONCACHE_CONTRACTID, &kNS_APPLICATIONCACHE_CID },
{ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "app", &kNS_APPPROTOCOLHANDLER_CID },
#ifdef NECKO_COOKIES
{ NS_COOKIEMANAGER_CONTRACTID, &kNS_COOKIEMANAGER_CID },
{ NS_COOKIESERVICE_CONTRACTID, &kNS_COOKIESERVICE_CID },

View File

@ -0,0 +1,424 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
/* 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 "AppProtocolHandler.h"
#include "nsBaseChannel.h"
#include "nsJARChannel.h"
#include "nsNetCID.h"
#include "nsIAppsService.h"
#include "nsCxPusher.h"
#include "nsXULAppAPI.h"
/**
* This dummy channel implementation only provides enough functionality
* to return a fake 404 error when the caller asks for an app:// URL
* containing an unknown appId.
*/
class DummyChannel : public nsIJARChannel
, nsRunnable
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUEST
NS_DECL_NSICHANNEL
NS_DECL_NSIJARCHANNEL
DummyChannel();
NS_IMETHODIMP Run();
private:
bool mPending;
uint32_t mSuspendCount;
nsCOMPtr<nsISupports> mListenerContext;
nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsILoadGroup> mLoadGroup;
nsLoadFlags mLoadFlags;
};
NS_IMPL_ISUPPORTS3(DummyChannel, nsIRequest, nsIChannel, nsIJARChannel)
DummyChannel::DummyChannel() : mPending(false)
, mSuspendCount(0)
, mLoadFlags(LOAD_NORMAL)
{
}
NS_IMETHODIMP DummyChannel::GetName(nsACString &result)
{
result = "dummy_app_channel";
return NS_OK;
}
NS_IMETHODIMP DummyChannel::GetStatus(nsresult *aStatus)
{
*aStatus = NS_ERROR_FILE_NOT_FOUND;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::IsPending(bool *aResult)
{
*aResult = mPending;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::Suspend()
{
mSuspendCount++;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::Resume()
{
if (mSuspendCount <= 0) {
return NS_ERROR_UNEXPECTED;
}
if (--mSuspendCount == 0) {
NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
}
return NS_OK;
}
NS_IMETHODIMP DummyChannel::Open(nsIInputStream**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* aContext)
{
mListener = aListener;
mListenerContext = aContext;
mPending = true;
if (mLoadGroup) {
mLoadGroup->AddRequest(this, aContext);
}
if (mSuspendCount == 0) {
NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
}
return NS_OK;
}
// nsIJarChannel, needed for XHR to turn NS_ERROR_FILE_NOT_FOUND into
// a 404 error.
NS_IMETHODIMP DummyChannel::GetIsUnsafe(bool *aResult)
{
*aResult = false;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::SetAppURI(nsIURI *aURI)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::Run()
{
nsresult rv = mListener->OnStartRequest(this, mListenerContext);
NS_ENSURE_SUCCESS(rv, rv);
mPending = false;
rv = mListener->OnStopRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND);
NS_ENSURE_SUCCESS(rv, rv);
if (mLoadGroup) {
mLoadGroup->RemoveRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND);
}
mListener = nullptr;
mListenerContext = nullptr;
rv = SetNotificationCallbacks(nullptr);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP DummyChannel::Cancel(nsresult)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
{
*aLoadGroup = mLoadGroup;
NS_IF_ADDREF(*aLoadGroup);
return NS_OK;
}
NS_IMETHODIMP DummyChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
{
mLoadGroup = aLoadGroup;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
{
*aLoadFlags = mLoadFlags;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
{
mLoadFlags = aLoadFlags;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::GetOriginalURI(nsIURI**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetOriginalURI(nsIURI*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetOwner(nsISupports**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetOwner(nsISupports*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetNotificationCallbacks(nsIInterfaceRequestor**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetNotificationCallbacks(nsIInterfaceRequestor*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetSecurityInfo(nsISupports**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentType(nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentType(const nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentCharset(nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentCharset(const nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentLength(int64_t*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentLength(int64_t)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentDisposition(uint32_t*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentDisposition(uint32_t)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetURI(nsIURI**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentDispositionFilename(nsAString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentDispositionFilename(nsAString const &)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentDispositionHeader(nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/**
* app:// protocol implementation.
*/
AppProtocolHandler::AppProtocolHandler() {
}
AppProtocolHandler::~AppProtocolHandler() {
mAppInfoCache.Clear();
}
NS_IMPL_ISUPPORTS1(AppProtocolHandler, nsIProtocolHandler)
/* static */
nsresult
AppProtocolHandler::Create(nsISupports* aOuter,
const nsIID& aIID,
void* *aResult)
{
AppProtocolHandler* ph = new AppProtocolHandler();
if (ph == nullptr) {
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(ph);
nsresult rv = ph->QueryInterface(aIID, aResult);
NS_RELEASE(ph);
return rv;
}
NS_IMETHODIMP
AppProtocolHandler::GetScheme(nsACString &aResult)
{
aResult.AssignLiteral("app");
return NS_OK;
}
NS_IMETHODIMP
AppProtocolHandler::GetDefaultPort(int32_t *aResult)
{
// No ports for the app protocol.
*aResult = -1;
return NS_OK;
}
NS_IMETHODIMP
AppProtocolHandler::GetProtocolFlags(uint32_t *aResult)
{
*aResult = URI_NOAUTH |
URI_DANGEROUS_TO_LOAD |
URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM;
return NS_OK;
}
NS_IMETHODIMP
AppProtocolHandler::NewURI(const nsACString &aSpec,
const char *aCharset, // ignore charset info
nsIURI *aBaseURI,
nsIURI **result)
{
nsresult rv;
nsCOMPtr<nsIStandardURL> surl(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec, aCharset, aBaseURI);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURL> url(do_QueryInterface(surl, &rv));
NS_ENSURE_SUCCESS(rv, rv);
surl->SetMutable(false);
NS_ADDREF(*result = url);
return NS_OK;
}
// We map app://ABCDEF/path/to/file.ext to
// jar:file:///path/to/profile/webapps/ABCDEF/application.zip!/path/to/file.ext
NS_IMETHODIMP
AppProtocolHandler::NewChannel(nsIURI* aUri, nsIChannel* *aResult)
{
NS_ENSURE_ARG_POINTER(aUri);
nsJARChannel* channel = new nsJARChannel();
if (!channel) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsAutoCString host;
nsresult rv = aUri->GetHost(host);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString fileSpec;
nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
rv = url->GetFilePath(fileSpec);
NS_ENSURE_SUCCESS(rv, rv);
mozilla::dom::AppInfo appInfo;
if (!mAppInfoCache.Get(host, &appInfo)) {
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
if (!appsService) {
return NS_ERROR_FAILURE;
}
JS::Value jsInfo;
rv = appsService->GetAppInfo(NS_ConvertUTF8toUTF16(host), &jsInfo);
if (NS_FAILED(rv)) {
// Return a DummyChannel.
delete channel;
NS_IF_ADDREF(*aResult = new DummyChannel());
return NS_OK;
}
mozilla::AutoSafeJSContext cx;
if (!appInfo.Init(cx, JS::Handle<JS::Value>::fromMarkedLocation(&jsInfo)) ||
appInfo.mPath.IsEmpty()) {
printf_stderr("!! No appInfo for %s\n", host.get());
// Return a DummyChannel.
delete channel;
NS_IF_ADDREF(*aResult = new DummyChannel());
return NS_OK;
}
mAppInfoCache.Put(host, appInfo);
}
bool noRemote = (appInfo.mIsCoreApp ||
XRE_GetProcessType() == GeckoProcessType_Default);
// In-parent and CoreApps can directly access files, so use jar:file://
nsAutoCString jarSpec(noRemote ? "jar:file://"
: "jar:remoteopenfile://");
jarSpec += NS_ConvertUTF16toUTF8(appInfo.mPath) +
NS_LITERAL_CSTRING("/application.zip!") +
fileSpec;
nsCOMPtr<nsIURI> jarURI;
rv = NS_NewURI(getter_AddRefs(jarURI),
jarSpec, nullptr, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->Init(jarURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->SetAppURI(aUri);
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->SetOriginalURI(aUri);
NS_ENSURE_SUCCESS(rv, rv);
NS_ADDREF(*aResult = channel);
return NS_OK;
}
NS_IMETHODIMP
AppProtocolHandler::AllowPort(int32_t aPort, const char *aScheme, bool *aRetval)
{
// No port allowed for this scheme.
*aRetval = false;
return NS_OK;
}

View File

@ -0,0 +1,35 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
/* 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/. */
#ifndef AppProtocolHandler_
#define AppProtocolHandler_
#include "nsIProtocolHandler.h"
#include "nsDataHashtable.h"
#include "mozilla/dom/AppInfoBinding.h"
class AppProtocolHandler : public nsIProtocolHandler
{
public:
NS_DECL_ISUPPORTS
// nsIProtocolHandler methods:
NS_DECL_NSIPROTOCOLHANDLER
// AppProtocolHandler methods:
AppProtocolHandler();
virtual ~AppProtocolHandler();
// Define a Create method to be used with a factory:
static nsresult Create(nsISupports* aOuter,
const nsIID& aIID,
void* *aResult);
private:
nsDataHashtable<nsCStringHashKey, mozilla::dom::AppInfo> mAppInfoCache;
};
#endif /* AppProtocolHandler_ */

View File

@ -1,197 +0,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/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
function AppProtocolHandler() {
this._appInfo = [];
this._runningInParent = Cc["@mozilla.org/xre/runtime;1"]
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
}
AppProtocolHandler.prototype = {
classID: Components.ID("{b7ad6144-d344-4687-b2d0-b6b9dce1f07f}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
scheme: "app",
defaultPort: -1,
// Don't allow loading from other protocols, and only from app:// if webapps is granted
protocolFlags: Ci.nsIProtocolHandler.URI_NOAUTH |
Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
Ci.nsIProtocolHandler.URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM,
getAppInfo: function app_phGetAppInfo(aId) {
if (!this._appInfo[aId]) {
this._appInfo[aId] = appsService.getAppInfo(aId);
}
return this._appInfo[aId];
},
newURI: function app_phNewURI(aSpec, aOriginCharset, aBaseURI) {
let uri = Cc["@mozilla.org/network/standard-url;1"]
.createInstance(Ci.nsIStandardURL);
uri.init(Ci.nsIStandardURL.URLTYPE_STANDARD, -1, aSpec, aOriginCharset,
aBaseURI);
return uri.QueryInterface(Ci.nsIURI);
},
newChannel: function app_phNewChannel(aURI) {
// We map app://ABCDEF/path/to/file.ext to
// jar:file:///path/to/profile/webapps/ABCDEF/application.zip!/path/to/file.ext
let url = aURI.QueryInterface(Ci.nsIURL);
let appId = aURI.host;
let fileSpec = url.filePath;
// Build a jar channel and masquerade as an app:// URI.
let appInfo = this.getAppInfo(appId);
if (!appInfo) {
// That should not happen, so dump() inconditionnally.
// We create a dummy channel instead of throwing to let the
// downstream user get a 404 error.
dump("!! got no appInfo for " + appId + "\n");
return new DummyChannel();
}
let uri;
if (this._runningInParent || appInfo.isCoreApp) {
// In-parent and CoreApps can directly access files, so use jar:file://
uri = "jar:file://" + appInfo.path + "/application.zip!" + fileSpec;
} else {
// non-CoreApps in child need to ask parent for file handle, use jar:ipcfile://
uri = "jar:remoteopenfile://" + appInfo.path + "/application.zip!" + fileSpec;
}
let channel = Services.io.newChannel(uri, null, null);
channel.QueryInterface(Ci.nsIJARChannel).setAppURI(aURI);
channel.QueryInterface(Ci.nsIChannel).originalURI = aURI;
return channel;
},
allowPort: function app_phAllowPort(aPort, aScheme) {
return false;
}
};
/**
* This dummy channel implementation only provides enough functionality
* to return a fake 404 error when the caller asks for an app:// URL
* containing an unknown appId.
*/
function DummyChannel() {
this.originalURI = Services.io.newURI("app://unknown/nothing.html", null, null);
this.URI = Services.io.newURI("app://unknown/nothing.html", null, null);
}
DummyChannel.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequest,
Ci.nsIChannel,
Ci.nsIJARChannel]),
// nsIRequest
name: "dummy_app_channel",
isPending: function dc_isPending() {
return this._pending;
},
status: Cr.NS_ERROR_FILE_NOT_FOUND,
cancel: function dc_cancel() {
},
suspend: function dc_suspend() {
this._suspendCount++;
},
resume: function dc_resume() {
if (this._suspendCount <= 0)
throw Cr.NS_ERROR_UNEXPECTED;
if (--this._suspendCount == 0 && this._pending) {
this._dispatch();
}
},
loadGroup: null,
loadFlags: Ci.nsIRequest.LOAD_NORMAL,
// nsIChannel
owner: null,
notificationCallbacks: null,
securityInfo: null,
contentType: null,
contentCharset: null,
contentLength: 0,
contentDisposition: Ci.nsIChannel.DISPOSITION_INLINE,
contentDispositionFilename: "",
_pending: false,
_suspendCount: 0,
_listener: null,
_context: null,
open: function dc_open() {
return Cr.NS_ERROR_NOT_IMPLEMENTED;
},
_dispatch: function dc_dispatch() {
let request = this;
Services.tm.currentThread.dispatch(
{
run: function dc_run() {
request._listener.onStartRequest(request, request._context);
request._listener.onStopRequest(request, request._context,
Cr.NS_ERROR_FILE_NOT_FOUND);
if (request.loadGroup) {
request.loadGroup.removeRequest(request, request._context,
Cr.NS_ERROR_FILE_NOT_FOUND);
}
request._pending = false;
request.notificationCallbacks = null;
request._listener = null;
request._context = null;
}
},
Ci.nsIThread.DISPATCH_NORMAL);
},
asyncOpen: function dc_asyncopenfunction(aListener, aContext) {
if (this.loadGroup) {
this.loadGroup.addRequest(this, aContext);
}
this._listener = aListener;
this._context = aContext;
this._pending = true;
if (!this._suspended) {
this._dispatch();
}
},
// nsIJarChannel, needed for XHR to turn NS_ERROR_FILE_NOT_FOUND into
// a 404 error.
isUnsafe: false,
setAppURI: function(aURI) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AppProtocolHandler]);

View File

@ -1,3 +0,0 @@
# AppProtocolHander.js
component {b7ad6144-d344-4687-b2d0-b6b9dce1f07f} AppProtocolHandler.js
contract @mozilla.org/network/protocol;1?name=app {b7ad6144-d344-4687-b2d0-b6b9dce1f07f}

View File

@ -0,0 +1,9 @@
#
# 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/.
LOCAL_INCLUDES = \
-I$(srcdir)/../../base/src \
-I$(srcdir)/../../../modules/libjar \
$(NULL)

View File

@ -4,7 +4,15 @@
# 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/.
EXTRA_COMPONENTS += [
'AppProtocolHandler.js',
'AppProtocolHandler.manifest',
MODULE = 'necko'
CPP_SOURCES += [
'AppProtocolHandler.cpp',
]
LIBRARY_NAME = 'nkapp_s'
FAIL_ON_WARNINGS = True
LIBXUL_LIBRARY = True