mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge b2g-inbound to Mozilla-Central
This commit is contained in:
commit
417825f57d
2
CLOBBER
2
CLOBBER
@ -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
|
||||
|
@ -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");
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "a57a913f1dd723afa191124f27b8d9fc4b0cb1c0",
|
||||
"revision": "20e3f42ccb6073c6d9bc9741de3a19a939a8a7d8",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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=
|
||||
|
@ -166,7 +166,8 @@ this.OperatorAppsRegistry = {
|
||||
},
|
||||
appId: undefined,
|
||||
isBrowser: false,
|
||||
isPackage: isPackage
|
||||
isPackage: isPackage,
|
||||
forceSuccessAck: true
|
||||
};
|
||||
|
||||
if (isPackage) {
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIDOMDesktopNotification.idl',
|
||||
'nsINotificationStorage.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'dom_notification'
|
||||
|
92
dom/interfaces/notification/nsINotificationStorage.idl
Normal file
92
dom/interfaces/notification/nsINotificationStorage.idl
Normal 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"
|
||||
%}
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
270
dom/src/notification/NotificationDB.jsm
Normal file
270
dom/src/notification/NotificationDB.jsm
Normal 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();
|
174
dom/src/notification/NotificationStorage.js
Normal file
174
dom/src/notification/NotificationStorage.js
Normal 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]);
|
3
dom/src/notification/NotificationStorage.manifest
Normal file
3
dom/src/notification/NotificationStorage.manifest
Normal 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}
|
@ -6,6 +6,15 @@
|
||||
|
||||
MODULE = 'dom'
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'NotificationStorage.js',
|
||||
'NotificationStorage.manifest',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'NotificationDB.jsm'
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'DesktopNotification.h',
|
||||
'Notification.h',
|
||||
|
@ -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']
|
||||
|
||||
|
81
dom/tests/mochitest/notification/MockServices.js
Normal file
81
dom/tests/mochitest/notification/MockServices.js
Normal 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);
|
||||
},
|
||||
};
|
||||
})();
|
73
dom/tests/mochitest/notification/NotificationTest.js
Normal file
73
dom/tests/mochitest/notification/NotificationTest.js
Normal 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
|
||||
};
|
||||
})();
|
@ -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/.
|
||||
|
@ -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]
|
||||
|
115
dom/tests/mochitest/notification/test_notification_basics.html
Normal file
115
dom/tests/mochitest/notification/test_notification_basics.html
Normal 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>
|
132
dom/tests/mochitest/notification/test_notification_storage.html
Normal file
132
dom/tests/mochitest/notification/test_notification_storage.html
Normal 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>
|
@ -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
12
dom/webidl/AppInfo.webidl
Normal 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;
|
||||
};
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -19,6 +19,7 @@ WEBIDL_FILES = [
|
||||
'AbstractWorker.webidl',
|
||||
'AnalyserNode.webidl',
|
||||
'AnimationEvent.webidl',
|
||||
'AppInfo.webidl',
|
||||
'AppNotificationServiceOptions.webidl',
|
||||
'ArchiveReader.webidl',
|
||||
'ArchiveRequest.webidl',
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)) \
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 },
|
||||
|
424
netwerk/protocol/app/AppProtocolHandler.cpp
Normal file
424
netwerk/protocol/app/AppProtocolHandler.cpp
Normal 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;
|
||||
}
|
||||
|
35
netwerk/protocol/app/AppProtocolHandler.h
Normal file
35
netwerk/protocol/app/AppProtocolHandler.h
Normal 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_ */
|
@ -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]);
|
@ -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}
|
9
netwerk/protocol/app/Makefile.in
Normal file
9
netwerk/protocol/app/Makefile.in
Normal 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)
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user