зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 5 changesets (bug 1343678) for causing multiple web-platform failures CLOSED TREE
Backed out changeset 4b77646bc788 (bug 1343678) Backed out changeset 7396789341b1 (bug 1343678) Backed out changeset 85526faefe6d (bug 1343678) Backed out changeset 07b757a21222 (bug 1343678) Backed out changeset 3551cc55620d (bug 1343678)
This commit is contained in:
Родитель
257da8f0d3
Коммит
478b1751c7
|
@ -209,15 +209,15 @@ var interfaceNamesInGlobalScope = [
|
|||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"PromiseRejectionEvent",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushEvent" },
|
||||
{ name: "PushEvent", fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushManager" },
|
||||
{ name: "PushManager", fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushMessageData" },
|
||||
{ name: "PushMessageData", fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushSubscription" },
|
||||
{ name: "PushSubscription", fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushSubscriptionOptions" },
|
||||
{ name: "PushSubscriptionOptions", fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"Request",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
|
|
@ -918,13 +918,14 @@ var interfaceNamesInGlobalScope = [
|
|||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PublicKeyCredential" },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushManager", insecureContext: true },
|
||||
{ name: "PushManager", insecureContext: true, fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushSubscription", insecureContext: true },
|
||||
{ name: "PushSubscription", insecureContext: true, fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{
|
||||
name: "PushSubscriptionOptions",
|
||||
insecureContext: true,
|
||||
fennecOrDesktop: true,
|
||||
},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "RadioNodeList", insecureContext: true },
|
||||
|
|
|
@ -213,13 +213,14 @@ var interfaceNamesInGlobalScope = [
|
|||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PromiseRejectionEvent", insecureContext: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushManager", insecureContext: true },
|
||||
{ name: "PushManager", insecureContext: true, fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "PushSubscription", insecureContext: true },
|
||||
{ name: "PushSubscription", insecureContext: true, fennecOrDesktop: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{
|
||||
name: "PushSubscriptionOptions",
|
||||
insecureContext: true,
|
||||
fennecOrDesktop: true,
|
||||
},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "Request", insecureContext: true },
|
||||
|
|
|
@ -42,7 +42,7 @@ pref("geckoview.console.enabled", false);
|
|||
pref("dom.capture.enabled", true);
|
||||
|
||||
// Disable Web Push until we get it working
|
||||
pref("dom.push.enabled", true);
|
||||
pref("dom.push.enabled", false);
|
||||
|
||||
// enable external storage API
|
||||
pref("dom.storageManager.enabled", true);
|
||||
|
|
|
@ -23,7 +23,3 @@ contract @mozilla.org/filepicker;1 {e4565e36-f101-4bf5-950b-4be0887785a9} proces
|
|||
# GeckoViewExternalAppService.js
|
||||
component {a89eeec6-6608-42ee-a4f8-04d425992f45} GeckoViewExternalAppService.js
|
||||
contract @mozilla.org/uriloader/external-helper-app-service;1 {a89eeec6-6608-42ee-a4f8-04d425992f45}
|
||||
|
||||
# GeckoViewPush.js
|
||||
component {a54d84d7-98a4-4fec-b664-e42e512ae9cc} GeckoViewPush.js
|
||||
contract @mozilla.org/push/Service;1 {a54d84d7-98a4-4fec-b664-e42e512ae9cc}
|
||||
|
|
|
@ -1,253 +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 { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { GeckoViewUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||
);
|
||||
|
||||
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPush"); // eslint-disable-line no-unused-vars
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"EventDispatcher",
|
||||
"resource://gre/modules/Messaging.jsm"
|
||||
);
|
||||
|
||||
// Observer notification topics for push messages and subscription status
|
||||
// changes. These are duplicated and used in `nsIPushNotifier`. They're exposed
|
||||
// on `nsIPushService` so that JS callers only need to import this service.
|
||||
const OBSERVER_TOPIC_PUSH = "push-message";
|
||||
const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change";
|
||||
const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified";
|
||||
|
||||
function createSubscription({
|
||||
scope,
|
||||
principal,
|
||||
browserPublicKey,
|
||||
authSecret,
|
||||
endpoint,
|
||||
appServerKey,
|
||||
}) {
|
||||
const decodedBrowserKey = ChromeUtils.base64URLDecode(browserPublicKey, {
|
||||
padding: "ignore",
|
||||
});
|
||||
const decodedAuthSecret = ChromeUtils.base64URLDecode(authSecret, {
|
||||
padding: "ignore",
|
||||
});
|
||||
|
||||
return new PushSubscription({
|
||||
endpoint: endpoint,
|
||||
scope,
|
||||
p256dhKey: decodedBrowserKey,
|
||||
authenticationSecret: decodedAuthSecret,
|
||||
appServerKey,
|
||||
});
|
||||
}
|
||||
|
||||
function scopeWithAttrs(scope, attrs) {
|
||||
return scope + ChromeUtils.originAttributesToSuffix(attrs);
|
||||
}
|
||||
|
||||
function PushService() {
|
||||
this.wrappedJSObject = this;
|
||||
}
|
||||
|
||||
PushService.prototype = {
|
||||
classID: Components.ID("{a54d84d7-98a4-4fec-b664-e42e512ae9cc}"),
|
||||
contractID: "@mozilla.org/push/Service;1",
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsIPushService,
|
||||
Ci.nsIPushQuotaManager,
|
||||
Ci.nsIPushErrorReporter,
|
||||
]),
|
||||
|
||||
pushTopic: OBSERVER_TOPIC_PUSH,
|
||||
subscriptionChangeTopic: OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
|
||||
subscriptionModifiedTopic: OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,
|
||||
|
||||
// nsIObserver methods
|
||||
|
||||
observe(subject, topic, data) {},
|
||||
|
||||
// nsIPushService methods
|
||||
|
||||
subscribe(scope, principal, callback) {
|
||||
this.subscribeWithKey(scope, principal, null, callback);
|
||||
},
|
||||
|
||||
async subscribeWithKey(scope, principal, appServerKey, callback) {
|
||||
try {
|
||||
const response = await EventDispatcher.instance.sendRequestForResult({
|
||||
type: "GeckoView:PushSubscribe",
|
||||
scope: scopeWithAttrs(scope, principal.originAttributes),
|
||||
appServerKey: appServerKey
|
||||
? ChromeUtils.base64URLEncode(new Uint8Array(appServerKey), {
|
||||
pad: true,
|
||||
})
|
||||
: null,
|
||||
});
|
||||
|
||||
let subscription = null;
|
||||
if (response) {
|
||||
subscription = createSubscription({
|
||||
...response,
|
||||
scope,
|
||||
principal,
|
||||
appServerKey,
|
||||
});
|
||||
}
|
||||
|
||||
callback.onPushSubscription(Cr.NS_OK, subscription);
|
||||
} catch (e) {
|
||||
callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
|
||||
}
|
||||
},
|
||||
|
||||
async unsubscribe(scope, principal, callback) {
|
||||
try {
|
||||
await EventDispatcher.instance.sendRequestForResult({
|
||||
type: "GeckoView:PushUnsubscribe",
|
||||
scope: scopeWithAttrs(scope, principal.originAttributes),
|
||||
});
|
||||
|
||||
callback.onUnsubscribe(Cr.NS_OK, true);
|
||||
} catch (e) {
|
||||
callback.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
|
||||
}
|
||||
},
|
||||
|
||||
async getSubscription(scope, principal, callback) {
|
||||
try {
|
||||
const response = await EventDispatcher.instance.sendRequestForResult({
|
||||
type: "GeckoView:PushGetSubscription",
|
||||
scope: scopeWithAttrs(scope, principal.originAttributes),
|
||||
});
|
||||
|
||||
let subscription = null;
|
||||
if (response) {
|
||||
subscription = createSubscription({
|
||||
...response,
|
||||
scope,
|
||||
principal,
|
||||
});
|
||||
}
|
||||
|
||||
callback.onPushSubscription(Cr.NS_OK, subscription);
|
||||
} catch (e) {
|
||||
callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
|
||||
}
|
||||
},
|
||||
|
||||
clearForDomain(domain, callback) {
|
||||
callback.onClear(Cr.NS_OK);
|
||||
},
|
||||
|
||||
// nsIPushQuotaManager methods
|
||||
|
||||
notificationForOriginShown(origin) {},
|
||||
|
||||
notificationForOriginClosed(origin) {},
|
||||
|
||||
// nsIPushErrorReporter methods
|
||||
|
||||
reportDeliveryError(messageId, reason) {},
|
||||
};
|
||||
|
||||
/** `PushSubscription` instances are passed to all subscription callbacks. */
|
||||
function PushSubscription(props) {
|
||||
this._props = props;
|
||||
}
|
||||
|
||||
PushSubscription.prototype = {
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIPushSubscription]),
|
||||
|
||||
/** The URL for sending messages to this subscription. */
|
||||
get endpoint() {
|
||||
return this._props.endpoint;
|
||||
},
|
||||
|
||||
/** The last time a message was sent to this subscription. */
|
||||
get lastPush() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
/** The total number of messages sent to this subscription. */
|
||||
get pushCount() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
/**
|
||||
* The app will take care of throttling, so we don't
|
||||
* care about the quota stuff here.
|
||||
*/
|
||||
get quota() {
|
||||
return -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates whether this subscription was created with the system principal.
|
||||
* System subscriptions are exempt from the background message quota and
|
||||
* permission checks.
|
||||
*/
|
||||
get isSystemSubscription() {
|
||||
return false;
|
||||
},
|
||||
|
||||
/** The private key used to decrypt incoming push messages, in JWK format */
|
||||
get p256dhPrivateKey() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates whether this subscription is subject to the background message
|
||||
* quota.
|
||||
*/
|
||||
quotaApplies() {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates whether this subscription exceeded the background message quota,
|
||||
* or the user revoked the notification permission. The caller must request a
|
||||
* new subscription to continue receiving push messages.
|
||||
*/
|
||||
isExpired() {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a key for encrypting messages sent to this subscription. JS
|
||||
* callers receive the key buffer as a return value, while C++ callers
|
||||
* receive the key size and buffer as out parameters.
|
||||
*/
|
||||
getKey(name) {
|
||||
switch (name) {
|
||||
case "p256dh":
|
||||
return this._getRawKey(this._props.p256dhKey);
|
||||
|
||||
case "auth":
|
||||
return this._getRawKey(this._props.authenticationSecret);
|
||||
|
||||
case "appServer":
|
||||
return this._getRawKey(this._props.appServerKey);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
_getRawKey(key) {
|
||||
if (!key) {
|
||||
return [];
|
||||
}
|
||||
return new Uint8Array(key);
|
||||
},
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PushService]);
|
|
@ -72,11 +72,6 @@ GeckoViewStartup.prototype = {
|
|||
],
|
||||
});
|
||||
|
||||
GeckoViewUtils.addLazyGetter(this, "GeckoViewPushController", {
|
||||
module: "resource://gre/modules/GeckoViewPushController.jsm",
|
||||
ged: ["GeckoView:PushEvent", "GeckoView:PushSubscriptionChanged"],
|
||||
});
|
||||
|
||||
GeckoViewUtils.addLazyPrefObserver(
|
||||
{
|
||||
name: "geckoview.console.enabled",
|
||||
|
|
|
@ -24,7 +24,6 @@ EXTRA_COMPONENTS += [
|
|||
'GeckoViewExternalAppService.js',
|
||||
'GeckoViewPermission.js',
|
||||
'GeckoViewPrompt.js',
|
||||
'GeckoViewPush.js',
|
||||
'GeckoViewStartup.js',
|
||||
]
|
||||
|
||||
|
|
|
@ -85,9 +85,6 @@ import org.mozilla.geckoview.WebExtensionEventDispatcher;
|
|||
import org.mozilla.geckoview.WebMessage;
|
||||
import org.mozilla.geckoview.WebNotification;
|
||||
import org.mozilla.geckoview.WebNotificationDelegate;
|
||||
import org.mozilla.geckoview.WebPushController;
|
||||
import org.mozilla.geckoview.WebPushDelegate;
|
||||
import org.mozilla.geckoview.WebPushSubscription;
|
||||
import org.mozilla.geckoview.WebRequest;
|
||||
import org.mozilla.geckoview.WebRequestError;
|
||||
import org.mozilla.geckoview.WebResponse;
|
||||
|
@ -318,7 +315,6 @@ package org.mozilla.geckoview {
|
|||
method @UiThread @NonNull public RuntimeTelemetry getTelemetry();
|
||||
method @UiThread @NonNull public WebExtensionController getWebExtensionController();
|
||||
method @UiThread @Nullable public WebNotificationDelegate getWebNotificationDelegate();
|
||||
method @UiThread @NonNull public WebPushController getWebPushController();
|
||||
method @UiThread public void orientationChanged();
|
||||
method @UiThread public void orientationChanged(int);
|
||||
method @AnyThread public void readFromParcel(@NonNull Parcel);
|
||||
|
@ -1277,29 +1273,6 @@ package org.mozilla.geckoview {
|
|||
method @AnyThread default public void onShowNotification(@NonNull WebNotification);
|
||||
}
|
||||
|
||||
public class WebPushController {
|
||||
method @UiThread public void onPushEvent(@NonNull WebPushSubscription);
|
||||
method @UiThread public void onPushEvent(@NonNull WebPushSubscription, @Nullable byte[]);
|
||||
method @UiThread public void onSubscriptionChanged(@NonNull WebPushSubscription);
|
||||
method @UiThread public void setDelegate(@Nullable WebPushDelegate);
|
||||
}
|
||||
|
||||
public interface WebPushDelegate {
|
||||
method @UiThread @Nullable default public GeckoResult<WebPushSubscription> onGetSubscription(@NonNull String);
|
||||
method @UiThread @Nullable default public GeckoResult<WebPushSubscription> onSubscribe(@NonNull String, @Nullable byte[]);
|
||||
method @UiThread @Nullable default public GeckoResult<Void> onUnsubscribe(@NonNull String);
|
||||
}
|
||||
|
||||
public class WebPushSubscription implements Parcelable {
|
||||
ctor public WebPushSubscription(@NonNull String, @NonNull String, @Nullable byte[], @NonNull byte[], @NonNull byte[]);
|
||||
field public static final Parcelable.Creator<WebPushSubscription> CREATOR;
|
||||
field @Nullable public final byte[] appServerKey;
|
||||
field @NonNull public final byte[] authSecret;
|
||||
field @NonNull public final byte[] browserPublicKey;
|
||||
field @NonNull public final String endpoint;
|
||||
field @NonNull public final String scope;
|
||||
}
|
||||
|
||||
@AnyThread public class WebRequest extends WebMessage {
|
||||
ctor public WebRequest(@NonNull String);
|
||||
field public static final int CACHE_MODE_DEFAULT = 1;
|
||||
|
|
|
@ -12,7 +12,7 @@ window.addEventListener("pageshow", () => {
|
|||
try {
|
||||
// Using eval here is the whole point of this WebExtension so we can
|
||||
// safely ignore the eslint warning.
|
||||
const response = window.eval(message.eval); // eslint-disable-line no-eval
|
||||
const response = eval(message.eval); // eslint-disable-line no-eval
|
||||
sendResponse(message.id, response);
|
||||
} catch (ex) {
|
||||
sendSyncResponse(message.id, null, ex);
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Push API test</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Hello, world!</p>
|
||||
<script src="push.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,44 +0,0 @@
|
|||
window.doSubscribe = async function(applicationServerKey) {
|
||||
const registration = await navigator.serviceWorker.register("./sw.js");
|
||||
const sub = await registration.pushManager.subscribe({
|
||||
applicationServerKey,
|
||||
});
|
||||
return sub.toJSON();
|
||||
};
|
||||
|
||||
window.doGetSubscription = async function() {
|
||||
const registration = await navigator.serviceWorker.register("./sw.js");
|
||||
const sub = await registration.pushManager.getSubscription();
|
||||
if (sub) {
|
||||
return sub.toJSON();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
window.doUnsubscribe = async function() {
|
||||
const registration = await navigator.serviceWorker.register("./sw.js");
|
||||
const sub = await registration.pushManager.getSubscription();
|
||||
sub.unsubscribe();
|
||||
return {};
|
||||
};
|
||||
|
||||
window.doWaitForPushEvent = function() {
|
||||
return new Promise(resolve => {
|
||||
navigator.serviceWorker.addEventListener("message", function(e) {
|
||||
if (e.data.type === "push") {
|
||||
resolve(e.data.payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.doWaitForSubscriptionChange = function() {
|
||||
return new Promise(resolve => {
|
||||
navigator.serviceWorker.addEventListener("message", function(e) {
|
||||
if (e.data.type === "pushsubscriptionchange") {
|
||||
resolve(e.data.type);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
self.addEventListener("install", function() {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", function(e) {
|
||||
e.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener("push", async function(e) {
|
||||
const clients = await self.clients.matchAll();
|
||||
clients.forEach(function(client) {
|
||||
client.postMessage({ type: "push", payload: e.data.text() });
|
||||
});
|
||||
});
|
||||
|
||||
self.addEventListener("pushsubscriptionchange", async function(e) {
|
||||
const clients = await self.clients.matchAll();
|
||||
clients.forEach(function(client) {
|
||||
client.postMessage({ type: "pushsubscriptionchange" });
|
||||
});
|
||||
});
|
|
@ -60,7 +60,6 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
|
|||
const val FIXED_BOTTOM = "/assets/www/fixedbottom.html"
|
||||
const val STORAGE_TITLE_HTML_PATH = "/assets/www/reflect_local_storage_into_title.html"
|
||||
const val HUNG_SCRIPT = "/assets/www/hungScript.html"
|
||||
const val PUSH_HTML_PATH = "/assets/www/push/push.html"
|
||||
}
|
||||
|
||||
@get:Rule val sessionRule = GeckoSessionTestRule()
|
||||
|
|
|
@ -1,212 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.geckoview.test
|
||||
|
||||
import android.os.Parcel
|
||||
import android.support.test.filters.MediumTest
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import android.util.Base64
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.json.JSONObject
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.geckoview.*
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.RejectedPromiseException
|
||||
import org.mozilla.geckoview.test.util.Callbacks
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.SecureRandom
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@MediumTest
|
||||
class WebPushTest : BaseSessionTest() {
|
||||
companion object {
|
||||
val PUSH_ENDPOINT: String = "https://test.endpoint"
|
||||
val APP_SERVER_KEY_PAIR: KeyPair = generateKeyPair()
|
||||
val AUTH_SECRET: ByteArray = generateAuthSecret()
|
||||
val BROWSER_KEY_PAIR: KeyPair = generateKeyPair()
|
||||
|
||||
private fun generateKeyPair(): KeyPair {
|
||||
try {
|
||||
val spec = ECGenParameterSpec("secp256r1")
|
||||
val generator = KeyPairGenerator.getInstance("EC")
|
||||
generator.initialize(spec)
|
||||
return generator.generateKeyPair()
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateAuthSecret(): ByteArray {
|
||||
val bytes = BigInteger(128, SecureRandom()).toByteArray()
|
||||
if (bytes.size > 16) {
|
||||
return bytes.copyOfRange(bytes.size - 16, bytes.size)
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
|
||||
var delegate: TestPushDelegate? = null
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// Grant "desktop notification" permission
|
||||
mainSession.delegateUntilTestEnd(object : Callbacks.PermissionDelegate {
|
||||
override fun onContentPermissionRequest(session: GeckoSession, uri: String?, type: Int, callback: GeckoSession.PermissionDelegate.Callback) {
|
||||
assertThat("Should grant DESKTOP_NOTIFICATIONS permission", type, equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION))
|
||||
callback.grant()
|
||||
}
|
||||
})
|
||||
|
||||
delegate = TestPushDelegate()
|
||||
|
||||
sessionRule.addExternalDelegateUntilTestEnd(WebPushDelegate::class,
|
||||
{ d -> sessionRule.runtime.webPushController.setDelegate(d) },
|
||||
{ sessionRule.runtime.webPushController.setDelegate(null) }, delegate!!)
|
||||
|
||||
|
||||
mainSession.loadTestPath(PUSH_HTML_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
sessionRule.runtime.webPushController.setDelegate(null)
|
||||
delegate = null
|
||||
}
|
||||
|
||||
private fun verifySubscription(subscription: JSONObject) {
|
||||
assertThat("Push endpoint should match", subscription.getString("endpoint"), equalTo(PUSH_ENDPOINT))
|
||||
|
||||
val keys = subscription.getJSONObject("keys")
|
||||
val authSecret = Base64.decode(keys.getString("auth"), Base64.URL_SAFE)
|
||||
val encryptionKey = WebPushUtils.keyFromString(keys.getString("p256dh"))
|
||||
|
||||
assertThat("Auth secret should match", authSecret, equalTo(AUTH_SECRET))
|
||||
assertThat("Encryption key should match", encryptionKey, equalTo(BROWSER_KEY_PAIR.public))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscribe() {
|
||||
// PushManager.subscribe()
|
||||
val appServerKey = WebPushUtils.keyToString(APP_SERVER_KEY_PAIR.public as ECPublicKey)
|
||||
var pushSubscription = mainSession.evaluatePromiseJS("window.doSubscribe(\"$appServerKey\")").value as JSONObject
|
||||
assertThat("Should have a stored subscription", delegate!!.storedSubscription, notNullValue())
|
||||
verifySubscription(pushSubscription)
|
||||
|
||||
// PushManager.getSubscription()
|
||||
pushSubscription = mainSession.evaluatePromiseJS("window.doGetSubscription()").value as JSONObject
|
||||
verifySubscription(pushSubscription)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscribeNoAppServerKey() {
|
||||
// PushManager.subscribe()
|
||||
var pushSubscription = mainSession.evaluatePromiseJS("window.doSubscribe()").value as JSONObject
|
||||
assertThat("Should have a stored subscription", delegate!!.storedSubscription, notNullValue())
|
||||
verifySubscription(pushSubscription)
|
||||
|
||||
// PushManager.getSubscription()
|
||||
pushSubscription = mainSession.evaluatePromiseJS("window.doGetSubscription()").value as JSONObject
|
||||
verifySubscription(pushSubscription)
|
||||
}
|
||||
|
||||
@Test(expected = RejectedPromiseException::class)
|
||||
fun subscribeNullDelegate() {
|
||||
sessionRule.runtime.webPushController.setDelegate(null)
|
||||
mainSession.evaluatePromiseJS("window.doSubscribe()").value as JSONObject
|
||||
}
|
||||
|
||||
@Test(expected = RejectedPromiseException::class)
|
||||
fun getSubscriptionNullDelegate() {
|
||||
sessionRule.runtime.webPushController.setDelegate(null)
|
||||
mainSession.evaluatePromiseJS("window.doGetSubscription()").value as JSONObject
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unsubscribe() {
|
||||
subscribe()
|
||||
|
||||
// PushManager.unsubscribe()
|
||||
val unsubResult = mainSession.evaluatePromiseJS("window.doUnsubscribe()").value as JSONObject
|
||||
assertThat("Unsubscribe result should be non-null", unsubResult, notNullValue())
|
||||
assertThat("Should not have a stored subscription", delegate!!.storedSubscription, nullValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pushEvent() {
|
||||
subscribe()
|
||||
|
||||
val p = mainSession.evaluatePromiseJS("window.doWaitForPushEvent()")
|
||||
|
||||
val testPayload = "The Payload";
|
||||
sessionRule.runtime.webPushController.onPushEvent(delegate!!.storedSubscription!!, testPayload.toByteArray(Charsets.UTF_8))
|
||||
|
||||
assertThat("Push data should match", p.value as String, equalTo(testPayload))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionChanged() {
|
||||
subscribe()
|
||||
|
||||
val p = mainSession.evaluatePromiseJS("window.doWaitForSubscriptionChange()")
|
||||
|
||||
sessionRule.runtime.webPushController.onSubscriptionChanged(delegate!!.storedSubscription!!)
|
||||
|
||||
assertThat("Result should not be null", p.value, notNullValue())
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun invalidDuplicateKeys() {
|
||||
WebPushSubscription("https://scope", PUSH_ENDPOINT,
|
||||
WebPushUtils.keyToBytes(APP_SERVER_KEY_PAIR.public as ECPublicKey),
|
||||
WebPushUtils.keyToBytes(APP_SERVER_KEY_PAIR.public as ECPublicKey)!!, AUTH_SECRET)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parceling() {
|
||||
val testScope = "https://test.scope";
|
||||
val sub = WebPushSubscription(testScope, PUSH_ENDPOINT,
|
||||
WebPushUtils.keyToBytes(APP_SERVER_KEY_PAIR.public as ECPublicKey),
|
||||
WebPushUtils.keyToBytes(BROWSER_KEY_PAIR.public as ECPublicKey)!!, AUTH_SECRET)
|
||||
|
||||
val parcel = Parcel.obtain()
|
||||
sub.writeToParcel(parcel, 0)
|
||||
parcel.setDataPosition(0)
|
||||
|
||||
val sub2 = WebPushSubscription.CREATOR.createFromParcel(parcel)
|
||||
assertThat("Scope should match", sub.scope, equalTo(sub2.scope))
|
||||
assertThat("Endpoint should match", sub.endpoint, equalTo(sub2.endpoint))
|
||||
assertThat("App server key should match", sub.appServerKey, equalTo(sub2.appServerKey))
|
||||
assertThat("Encryption key should match", sub.browserPublicKey, equalTo(sub2.browserPublicKey))
|
||||
assertThat("Auth secret should match", sub.authSecret, equalTo(sub2.authSecret))
|
||||
}
|
||||
|
||||
class TestPushDelegate : WebPushDelegate {
|
||||
var storedSubscription: WebPushSubscription? = null
|
||||
|
||||
override fun onGetSubscription(scope: String): GeckoResult<WebPushSubscription>? {
|
||||
return GeckoResult.fromValue(storedSubscription)
|
||||
}
|
||||
|
||||
override fun onUnsubscribe(scope: String): GeckoResult<Void>? {
|
||||
storedSubscription = null
|
||||
return GeckoResult.fromValue(null)
|
||||
}
|
||||
|
||||
override fun onSubscribe(scope: String, appServerKey: ByteArray?): GeckoResult<WebPushSubscription>? {
|
||||
appServerKey?.let { assertThat("Application server key should match", it, equalTo(WebPushUtils.keyToBytes(APP_SERVER_KEY_PAIR.public as ECPublicKey))) }
|
||||
storedSubscription = WebPushSubscription(scope, PUSH_ENDPOINT, appServerKey, WebPushUtils.keyToBytes(BROWSER_KEY_PAIR.public as ECPublicKey)!!, AUTH_SECRET)
|
||||
return GeckoResult.fromValue(storedSubscription)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* vim: ts=4 sw=4 expandtab:
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.geckoview.test;
|
||||
|
||||
import android.support.annotation.AnyThread;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Base64;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.ECFieldFp;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.security.spec.ECParameterSpec;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.security.spec.EllipticCurve;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
/**
|
||||
* Utilities for converting {@link ECPublicKey} to/from X9.62 encoding.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc8291">Message Encryption for Web Push</a>
|
||||
*/
|
||||
/* package */ class WebPushUtils {
|
||||
public static final int P256_PUBLIC_KEY_LENGTH = 65; // 1 + 32 + 32
|
||||
private static final byte NIST_HEADER = 0x04; // uncompressed format
|
||||
|
||||
private static ECParameterSpec sSpec;
|
||||
|
||||
private WebPushUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an {@link ECPublicKey} into X9.62 format as required
|
||||
* by Web Push.
|
||||
*
|
||||
* @param key the {@link ECPublicKey} to encode
|
||||
* @return the encoded {@link ECPublicKey}
|
||||
*/
|
||||
@AnyThread
|
||||
public static @Nullable byte[] keyToBytes(final @Nullable ECPublicKey key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ByteBuffer buffer = ByteBuffer.allocate(P256_PUBLIC_KEY_LENGTH);
|
||||
buffer.put(NIST_HEADER);
|
||||
|
||||
putUnsignedBigInteger(buffer, key.getW().getAffineX());
|
||||
putUnsignedBigInteger(buffer, key.getW().getAffineY());
|
||||
|
||||
if (buffer.position() != P256_PUBLIC_KEY_LENGTH) {
|
||||
throw new RuntimeException("Unexpected key length " + buffer.position());
|
||||
}
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private static void putUnsignedBigInteger(final ByteBuffer buffer, final BigInteger value) {
|
||||
final byte[] bytes = value.toByteArray();
|
||||
if (bytes.length < 32) {
|
||||
buffer.put(new byte[32 - bytes.length]);
|
||||
buffer.put(bytes);
|
||||
} else {
|
||||
buffer.put(bytes, bytes.length - 32, 32);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an {@link ECPublicKey} into X9.62 format as required
|
||||
* by Web Push, further encoded into Base64.
|
||||
*
|
||||
* @param key the {@link ECPublicKey} to encode
|
||||
* @return the encoded {@link ECPublicKey}
|
||||
*/
|
||||
@AnyThread
|
||||
public static @Nullable String keyToString(final @Nullable ECPublicKey key) {
|
||||
return Base64.encodeToString(keyToBytes(key), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link ECParameterSpec} for P-256 (secp256r1).
|
||||
*/
|
||||
public static ECParameterSpec getP256Spec() {
|
||||
if (sSpec == null) {
|
||||
try {
|
||||
final KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
|
||||
final ECGenParameterSpec genSpec = new ECGenParameterSpec("secp256r1");
|
||||
gen.initialize(genSpec);
|
||||
sSpec = ((ECPublicKey) gen.generateKeyPair().getPublic()).getParams();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return sSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Base64 X9.62 encoded Web Push key into a {@link ECPublicKey}.
|
||||
*
|
||||
* @param base64Bytes the X9.62 data as Base64
|
||||
* @return a {@link ECPublicKey}
|
||||
*/
|
||||
@AnyThread
|
||||
public static @Nullable ECPublicKey keyFromString(final @Nullable String base64Bytes) {
|
||||
if (base64Bytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return keyFromBytes(Base64.decode(base64Bytes, Base64.URL_SAFE));
|
||||
}
|
||||
|
||||
private static BigInteger readUnsignedBigInteger(final byte[] bytes, final int offset, final int length) {
|
||||
byte[] mag = bytes;
|
||||
if (offset != 0 || length != bytes.length) {
|
||||
mag = new byte[length];
|
||||
System.arraycopy(bytes, offset, mag, 0, length);
|
||||
}
|
||||
return new BigInteger(1, mag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a X9.62 encoded Web Push key into a {@link ECPublicKey}.
|
||||
*
|
||||
* @param bytes the X9.62 data
|
||||
* @return a {@link ECPublicKey}
|
||||
*/
|
||||
@AnyThread
|
||||
public static @Nullable ECPublicKey keyFromBytes(final @Nullable byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (bytes.length != P256_PUBLIC_KEY_LENGTH) {
|
||||
throw new IllegalArgumentException(String.format("Expected exactly %d bytes", P256_PUBLIC_KEY_LENGTH));
|
||||
}
|
||||
|
||||
if (bytes[0] != NIST_HEADER) {
|
||||
throw new IllegalArgumentException("Expected uncompressed NIST format");
|
||||
}
|
||||
|
||||
try {
|
||||
final BigInteger x = readUnsignedBigInteger(bytes, 1, 32);
|
||||
final BigInteger y = readUnsignedBigInteger(bytes, 33, 32);
|
||||
|
||||
final ECPoint point = new ECPoint(x, y);
|
||||
final ECPublicKeySpec spec = new ECPublicKeySpec(point, getP256Spec());
|
||||
final KeyFactory factory = KeyFactory.getInstance("EC");
|
||||
final ECPublicKey key = (ECPublicKey) factory.generatePublic(spec);
|
||||
|
||||
return key;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -318,12 +318,7 @@ public final class EventDispatcher extends JNIObject {
|
|||
return true;
|
||||
}
|
||||
|
||||
final String error = "No listener for " + type;
|
||||
if (callback != null) {
|
||||
callback.sendError(error);
|
||||
}
|
||||
|
||||
Log.w(LOGTAG, error);
|
||||
Log.w(LOGTAG, "No listener for " + type);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package org.mozilla.geckoview;
|
||||
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
|
||||
/**
|
||||
* This class exposes the Base64 URL encode/decode functions from Gecko. They are different
|
||||
* from android.util.Base64 in that they always use URL encoding, no padding, and are
|
||||
* constant time. The last bit is important when dealing with values that might be secret
|
||||
* as we do with Web Push.
|
||||
*/
|
||||
/* package */ class Base64Utils {
|
||||
@WrapForJNI public static native byte[] decode(final String data);
|
||||
@WrapForJNI public static native String encode(final byte[] data);
|
||||
}
|
|
@ -165,7 +165,6 @@ public final class GeckoRuntime implements Parcelable {
|
|||
private final WebExtensionEventDispatcher mWebExtensionDispatcher;
|
||||
private StorageController mStorageController;
|
||||
private final WebExtensionController mWebExtensionController;
|
||||
private WebPushController mPushController;
|
||||
|
||||
private GeckoRuntime() {
|
||||
mWebExtensionDispatcher = new WebExtensionEventDispatcher();
|
||||
|
@ -649,24 +648,6 @@ public final class GeckoRuntime implements Parcelable {
|
|||
return mStorageController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Web Push controller for this runtime.
|
||||
* The Web Push controller can be used to allow content
|
||||
* to use the Web Push API.
|
||||
*
|
||||
* @return The {@link WebPushController} for this instance.
|
||||
*/
|
||||
@UiThread
|
||||
public @NonNull WebPushController getWebPushController() {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
||||
if (mPushController == null) {
|
||||
mPushController = new WebPushController();
|
||||
}
|
||||
|
||||
return mPushController;
|
||||
}
|
||||
|
||||
@Override // Parcelable
|
||||
@AnyThread
|
||||
public int describeContents() {
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* vim: ts=4 sw=4 expandtab:
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
public class WebPushController {
|
||||
private static final String LOGTAG = "WebPushController";
|
||||
|
||||
private WebPushDelegate mDelegate;
|
||||
private BundleEventListener mEventListener;
|
||||
|
||||
/* package */ WebPushController() {
|
||||
mEventListener = new EventListener();
|
||||
EventDispatcher.getInstance().registerUiThreadListener(mEventListener,
|
||||
"GeckoView:PushSubscribe",
|
||||
"GeckoView:PushUnsubscribe",
|
||||
"GeckoView:PushGetSubscription");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link WebPushDelegate} for this instance.
|
||||
*
|
||||
* @param delegate The {@link WebPushDelegate} instance.
|
||||
*/
|
||||
@UiThread
|
||||
public void setDelegate(final @Nullable WebPushDelegate delegate) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a push event for a given subscription.
|
||||
*
|
||||
* @param subscription The WebPushSubscription that the event belongs to.
|
||||
*/
|
||||
@UiThread
|
||||
public void onPushEvent(final @NonNull WebPushSubscription subscription) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
onPushEvent(subscription, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a push event with a payload for a given subscription.
|
||||
*
|
||||
* @param subscription The WebPushSubscription that the event belongs to.
|
||||
* @param data The unencrypted payload.
|
||||
*/
|
||||
@UiThread
|
||||
public void onPushEvent(final @NonNull WebPushSubscription subscription, final @Nullable byte[] data) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
||||
final GeckoBundle msg = new GeckoBundle(2);
|
||||
msg.putBundle("subscription", subscription.toBundle());
|
||||
msg.putString("data", Base64Utils.encode(data));
|
||||
EventDispatcher.getInstance().dispatch("GeckoView:PushEvent", msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that a given subscription has changed. This is normally a signal to the content
|
||||
* that it needs to re-subscribe.
|
||||
*
|
||||
* @param subscription The subscription that changed.
|
||||
*/
|
||||
@UiThread
|
||||
public void onSubscriptionChanged(final @NonNull WebPushSubscription subscription) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
||||
final GeckoBundle msg = new GeckoBundle(1);
|
||||
msg.putBundle("subscription", subscription.toBundle());
|
||||
EventDispatcher.getInstance().dispatch("GeckoView:PushSubscriptionChanged", msg);
|
||||
}
|
||||
|
||||
private class EventListener implements BundleEventListener {
|
||||
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message, final EventCallback callback) {
|
||||
if (mDelegate == null) {
|
||||
callback.sendError("Not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case "GeckoView:PushSubscribe": {
|
||||
byte[] appServerKey = null;
|
||||
if (message.containsKey("appServerKey")) {
|
||||
appServerKey = Base64Utils.decode(message.getString("appServerKey"));
|
||||
}
|
||||
|
||||
final GeckoResult<WebPushSubscription> result =
|
||||
mDelegate.onSubscribe(message.getString("scope"), appServerKey);
|
||||
|
||||
if (result == null) {
|
||||
callback.sendSuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
result.accept(subscription -> callback.sendSuccess(subscription != null ? subscription.toBundle() : null),
|
||||
error -> callback.sendSuccess(null));
|
||||
break;
|
||||
}
|
||||
case "GeckoView:PushUnsubscribe": {
|
||||
final GeckoResult<Void> result = mDelegate.onUnsubscribe(message.getString("scope"));
|
||||
if (result == null) {
|
||||
callback.sendSuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
result.accept(val -> callback.sendSuccess(null), err -> callback.sendError(err.getMessage()));
|
||||
break;
|
||||
}
|
||||
case "GeckoView:PushGetSubscription": {
|
||||
final GeckoResult<WebPushSubscription> result = mDelegate.onGetSubscription(message.getString("scope"));
|
||||
if (result == null) {
|
||||
callback.sendSuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
result.accept(subscription -> callback.sendSuccess(subscription != null ? subscription.toBundle() : null),
|
||||
err -> callback.sendError(err.getMessage()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* vim: ts=4 sw=4 expandtab:
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
public interface WebPushDelegate {
|
||||
/**
|
||||
* Creates a push subscription for the given service worker scope. A scope
|
||||
* uniquely identifies a service worker. `appServerKey` optionally
|
||||
* creates a restricted subscription.
|
||||
*
|
||||
* Applications will likely want to persist the returned {@link WebPushSubscription} in order
|
||||
* to support {@link #onGetSubscription(String)}.
|
||||
*
|
||||
* @param scope The Service Worker scope.
|
||||
* @param appServerKey An optional application server key.
|
||||
* @return A {@link GeckoResult} which resolves to a {@link WebPushSubscription}
|
||||
*
|
||||
* @see <a href="http://w3c.github.io/push-api/#dom-pushmanager-subscribe">subscribe()</a>
|
||||
* @see <a href="http://w3c.github.io/push-api/#dom-pushsubscriptionoptionsinit-applicationserverkey">Application server key</a>
|
||||
*/
|
||||
@UiThread
|
||||
default @Nullable GeckoResult<WebPushSubscription> onSubscribe(@NonNull String scope,
|
||||
@Nullable byte[] appServerKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a subscription for the given service worker scope.
|
||||
*
|
||||
* @param scope The scope for the requested {@link WebPushSubscription}.
|
||||
* @return A {@link GeckoResult} which resolves to a {@link WebPushSubscription}
|
||||
*
|
||||
* @see <a href="http://w3c.github.io/push-api/#dom-pushmanager-getsubscription">getSubscription()</a>
|
||||
*/
|
||||
@UiThread
|
||||
default @Nullable GeckoResult<WebPushSubscription> onGetSubscription(@NonNull String scope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a push subscription. If this fails, apps should resolve the
|
||||
* returned {@link GeckoResult} with an exception.
|
||||
*
|
||||
* @param scope The Service Worker scope for the subscription.
|
||||
* @return A {@link GeckoResult}, which if non-exceptional indicates successfully unsubscribing.
|
||||
*
|
||||
* @see <a href="http://w3c.github.io/push-api/#dom-pushsubscription-unsubscribe">unsubscribe()</a>
|
||||
*/
|
||||
@UiThread
|
||||
default @Nullable GeckoResult<Void> onUnsubscribe(@NonNull String scope) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* vim: ts=4 sw=4 expandtab:
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.AnyThread;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This class represents a single Web Push subscription, as described in
|
||||
* the <a href="https://www.w3.org/TR/push-api/">Web Push API</a> specification.
|
||||
*
|
||||
* This is a low-level interface, allowing applications to do all of the heavy lifting
|
||||
* themselves. It is recommended that consumers have a thorough understanding of the
|
||||
* Web Push API, especially <a href="https://tools.ietf.org/html/rfc8291">RFC 8291</a>.
|
||||
*
|
||||
* Only trivial sanity checks are performed on the values held here. The application must
|
||||
* ensure it is generating compliant keys/secrets itself.
|
||||
*/
|
||||
public class WebPushSubscription implements Parcelable {
|
||||
private static final int P256_PUBLIC_KEY_LENGTH = 65;
|
||||
|
||||
/**
|
||||
* The Service Worker scope associated with this subscription.
|
||||
*
|
||||
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register">ServiceWorker registration</a>
|
||||
*/
|
||||
@NonNull
|
||||
public final String scope;
|
||||
|
||||
/**
|
||||
* The Web Push endpoint for this subscription. This is the URL of a web service which
|
||||
* implements the Web Push protocol.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc8030#section-5">RFC 8030</a>
|
||||
*/
|
||||
@NonNull
|
||||
public final String endpoint;
|
||||
|
||||
/**
|
||||
* This is an optional public key provided by the application server to authenticate
|
||||
* itself with the endpoint, formatted according to X9.62.
|
||||
*
|
||||
* This key is used for VAPID, the Voluntary Application Server Identification (VAPID)
|
||||
* for Web Push, from <a href="https://tools.ietf.org/html/rfc8292">RFC 8292</a>.
|
||||
*
|
||||
* @see <a href="https://www.w3.org/TR/push-api/#dom-pushsubscriptionoptions-applicationserverkey">applicationServerKey</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc8291">Message Encryption for Web Push</a>
|
||||
*/
|
||||
@Nullable
|
||||
public final byte[] appServerKey;
|
||||
|
||||
/**
|
||||
* The P-256 EC public key, formatted as X9.62, generated by the embedder, to be provided
|
||||
* to the app server for message encryption.
|
||||
*
|
||||
* @see <a href="https://www.w3.org/TR/push-api/#dom-pushencryptionkeyname-p256dh">PushEncryptionKeyName - p256dh</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc8291#section-3.1">RFC 8291 section 3.1</a>
|
||||
*/
|
||||
@NonNull
|
||||
public final byte[] browserPublicKey;
|
||||
|
||||
/**
|
||||
* 16 byte secret key, generated by the embedder, to be provided to the app server for use
|
||||
* in encrypting and authenticating messages sent to the {@link #endpoint}.
|
||||
*
|
||||
* @see <a href="https://www.w3.org/TR/push-api/#dom-pushencryptionkeyname-auth">PushEncryptionKeyName - auth</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc8291#section-3.2">RFC 8291, section 3.2</a>
|
||||
*/
|
||||
@NonNull
|
||||
public final byte[] authSecret;
|
||||
|
||||
public WebPushSubscription(final @NonNull String scope,
|
||||
final @NonNull String endpoint,
|
||||
final @Nullable byte[] appServerKey,
|
||||
final @NonNull byte[] browserPublicKey,
|
||||
final @NonNull byte[] authSecret) {
|
||||
this.scope = scope;
|
||||
this.endpoint = endpoint;
|
||||
this.appServerKey = appServerKey;
|
||||
this.browserPublicKey = browserPublicKey;
|
||||
this.authSecret = authSecret;
|
||||
|
||||
if (appServerKey != null) {
|
||||
if (appServerKey.length != P256_PUBLIC_KEY_LENGTH) {
|
||||
throw new IllegalArgumentException(String.format("appServerKey should be %d bytes", P256_PUBLIC_KEY_LENGTH));
|
||||
}
|
||||
|
||||
if (Arrays.equals(appServerKey, browserPublicKey)) {
|
||||
throw new IllegalArgumentException("appServerKey and browserPublicKey must differ");
|
||||
}
|
||||
}
|
||||
|
||||
if (browserPublicKey.length != P256_PUBLIC_KEY_LENGTH) {
|
||||
throw new IllegalArgumentException(String.format("browserPublicKey should be %d bytes", P256_PUBLIC_KEY_LENGTH));
|
||||
}
|
||||
|
||||
if (authSecret.length != 16) {
|
||||
throw new IllegalArgumentException("authSecret must be 128 bits");
|
||||
}
|
||||
}
|
||||
|
||||
private WebPushSubscription(final Parcel in) {
|
||||
this.scope = in.readString();
|
||||
this.endpoint = in.readString();
|
||||
|
||||
if (ParcelableUtils.readBoolean(in)) {
|
||||
this.appServerKey = new byte[P256_PUBLIC_KEY_LENGTH];
|
||||
in.readByteArray(this.appServerKey);
|
||||
} else {
|
||||
appServerKey = null;
|
||||
}
|
||||
|
||||
this.browserPublicKey = new byte[P256_PUBLIC_KEY_LENGTH];
|
||||
in.readByteArray(this.browserPublicKey);
|
||||
|
||||
this.authSecret = new byte[16];
|
||||
in.readByteArray(this.authSecret);
|
||||
}
|
||||
|
||||
/* package */ GeckoBundle toBundle() {
|
||||
final GeckoBundle bundle = new GeckoBundle(5);
|
||||
bundle.putString("scope", scope);
|
||||
bundle.putString("endpoint", endpoint);
|
||||
if (appServerKey != null) {
|
||||
bundle.putString("appServerKey", Base64Utils.encode(appServerKey));
|
||||
}
|
||||
bundle.putString("browserPublicKey", Base64Utils.encode(browserPublicKey));
|
||||
bundle.putString("authSecret", Base64Utils.encode(authSecret));
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel out, final int flags) {
|
||||
out.writeString(scope);
|
||||
out.writeString(endpoint);
|
||||
|
||||
ParcelableUtils.writeBoolean(out, appServerKey != null);
|
||||
if (appServerKey != null) {
|
||||
out.writeByteArray(appServerKey);
|
||||
}
|
||||
|
||||
out.writeByteArray(browserPublicKey);
|
||||
out.writeByteArray(authSecret);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<WebPushSubscription> CREATOR = new Parcelable.Creator<WebPushSubscription>() {
|
||||
@Override
|
||||
@AnyThread
|
||||
public WebPushSubscription createFromParcel(final Parcel parcel) {
|
||||
return new WebPushSubscription(parcel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@AnyThread
|
||||
public WebPushSubscription[] newArray(final int size) {
|
||||
return new WebPushSubscription[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -56,8 +56,6 @@ exclude: true
|
|||
([bug 1565782]({{bugzilla}}1565782))
|
||||
- Added onSlowScript to [`ContentDelegate`][70.23] which allows handling of slow and hung scripts.
|
||||
([bug 1621094]({{bugzilla}}1621094))
|
||||
- Added support for Web Push via [`WebPushController`][70.24], [`WebPushDelegate`][70.25], and
|
||||
[`WebPushSubscription`][70.26].
|
||||
|
||||
[70.1]: {{javadoc_uri}}/GeckoSessionSettings.Builder.html#contextId-java.lang.String-
|
||||
[70.2]: {{javadoc_uri}}/StorageController.html#clearDataForSessionContext-java.lang.String-
|
||||
|
@ -82,9 +80,6 @@ exclude: true
|
|||
[70.21]: {{javadoc_uri}}/WebExtensionController.TabDelegate.html#onCloseTab-org.mozilla.geckoview.WebExtension-org.mozilla.geckoview.GeckoSession-
|
||||
[70.22]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/remove
|
||||
[70.23]: {{javadoc_uri}}/GeckoSession.ContentDelegate.html
|
||||
[70.24]: {{javadoc_uri}}/WebPushController.html
|
||||
[70.25]: {{javadoc_uri}}/WebPushDelegate.html
|
||||
[70.26]: {{javadoc_uri}}/WebPushSubscription.html
|
||||
|
||||
## v69
|
||||
- Modified behavior of ['setAutomaticFontSizeAdjustment'][69.1] so that it no
|
||||
|
@ -328,4 +323,4 @@ exclude: true
|
|||
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
|
||||
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
||||
|
||||
[api-version]: 2ef7c30341c4cae55cdce87f62c19ae606435430
|
||||
[api-version]: 4199a7a889674d4402926366a7f4fb2f93de5c46
|
||||
|
|
|
@ -44,9 +44,7 @@ import android.net.Uri;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
|
@ -67,11 +65,6 @@ import java.io.BufferedReader;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
|
|
@ -241,7 +241,6 @@
|
|||
@BINPATH@/components/GeckoView.manifest
|
||||
@BINPATH@/components/GeckoViewExternalAppService.js
|
||||
@BINPATH@/components/GeckoViewPrompt.js
|
||||
@BINPATH@/components/GeckoViewPush.js
|
||||
@BINPATH@/components/GeckoViewPermission.js
|
||||
@BINPATH@/components/GeckoViewStartup.js
|
||||
#else
|
||||
|
|
|
@ -1,77 +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";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["GeckoViewPushController"];
|
||||
|
||||
const { GeckoViewUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||
);
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"PushNotifier",
|
||||
"@mozilla.org/push/Notifier;1",
|
||||
"nsIPushNotifier"
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPushController");
|
||||
|
||||
function createScopeAndPrincipal(scopeAndAttrs) {
|
||||
const [scope, attrs] = scopeAndAttrs.split("^");
|
||||
const uri = Services.io.newURI(scope);
|
||||
|
||||
return [
|
||||
scope,
|
||||
Services.scriptSecurityManager.createContentPrincipal(
|
||||
uri,
|
||||
ChromeUtils.createOriginAttributesFromOrigin(attrs)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
const GeckoViewPushController = {
|
||||
onEvent(aEvent, aData, aCallback) {
|
||||
debug`onEvent ${aEvent} ${aData}`;
|
||||
|
||||
switch (aEvent) {
|
||||
case "GeckoView:PushEvent": {
|
||||
const {
|
||||
subscription: { scope },
|
||||
data,
|
||||
} = aData;
|
||||
|
||||
const [url, principal] = createScopeAndPrincipal(scope);
|
||||
|
||||
if (!data) {
|
||||
PushNotifier.notifyPush(url, principal);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = new Uint8Array(
|
||||
ChromeUtils.base64URLDecode(data, { padding: "ignore" })
|
||||
);
|
||||
|
||||
PushNotifier.notifyPushWithData(url, principal, "", payload);
|
||||
break;
|
||||
}
|
||||
case "GeckoView:PushSubscriptionChanged": {
|
||||
const {
|
||||
subscription: { scope },
|
||||
} = aData;
|
||||
|
||||
const [url, principal] = createScopeAndPrincipal(scope);
|
||||
|
||||
PushNotifier.notifySubscriptionChange(url, principal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
|
@ -18,7 +18,6 @@ EXTRA_JS_MODULES += [
|
|||
'GeckoViewNavigation.jsm',
|
||||
'GeckoViewProcessHangMonitor.jsm',
|
||||
'GeckoViewProgress.jsm',
|
||||
'GeckoViewPushController.jsm',
|
||||
'GeckoViewRemoteDebugger.jsm',
|
||||
'GeckoViewSettings.jsm',
|
||||
'GeckoViewStorageController.jsm',
|
||||
|
|
|
@ -14,6 +14,146 @@
|
|||
[PushManager interface: attribute supportedContentEncodings]
|
||||
expected: FAIL
|
||||
|
||||
[PushManager must be primary interface of registration.pushManager]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: attribute options]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: registration.pushManager must inherit property "supportedContentEncodings" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: attribute endpoint]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation permissionState(PushSubscriptionOptionsInit)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation getSubscription()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[Stringification of registration.pushManager]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: registration.pushManager must inherit property "getSubscription()" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation getKey(PushEncryptionKeyName)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: calling permissionState(PushSubscriptionOptionsInit) on registration.pushManager with too few arguments must throw TypeError]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[ServiceWorkerRegistration interface: registration must inherit property "pushManager" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation subscribe(PushSubscriptionOptionsInit)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation toJSON()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation unsubscribe()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: attribute applicationServerKey]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: calling subscribe(PushSubscriptionOptionsInit) on registration.pushManager with too few arguments must throw TypeError]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: registration.pushManager must inherit property "subscribe(PushSubscriptionOptionsInit)" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: registration.pushManager must inherit property "permissionState(PushSubscriptionOptionsInit)" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface object length]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[ServiceWorkerRegistration interface: attribute pushManager]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
|
||||
[idlharness.https.any.worker.html]
|
||||
[PushManager interface object length]
|
||||
|
@ -31,6 +171,111 @@
|
|||
[PushManager interface: attribute supportedContentEncodings]
|
||||
expected: FAIL
|
||||
|
||||
[PushSubscription interface: attribute options]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation getKey(PushEncryptionKeyName)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation permissionState(PushSubscriptionOptionsInit)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation getSubscription()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation subscribe(PushSubscriptionOptionsInit)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation toJSON()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation unsubscribe()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: attribute applicationServerKey]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: attribute endpoint]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface object length]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[ServiceWorkerRegistration interface: attribute pushManager]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
|
||||
[idlharness.https.any.serviceworker.html]
|
||||
[PushSubscriptionChangeEvent must be primary interface of new PushSubscriptionChangeEvent("pushsubscriptionchange")]
|
||||
expected: FAIL
|
||||
|
@ -83,6 +328,227 @@
|
|||
[PushSubscriptionChangeEvent interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[PushManager must be primary interface of registration.pushManager]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: attribute options]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: registration.pushManager must inherit property "supportedContentEncodings" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface: operation text()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface object length]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: attribute endpoint]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent interface object length]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation permissionState(PushSubscriptionOptionsInit)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation getSubscription()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[Stringification of registration.pushManager]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: registration.pushManager must inherit property "getSubscription()" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation getKey(PushEncryptionKeyName)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: calling permissionState(PushSubscriptionOptionsInit) on registration.pushManager with too few arguments must throw TypeError]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent interface: new PushEvent("type") must inherit property "data" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[ServiceWorkerRegistration interface: registration must inherit property "pushManager" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation subscribe(PushSubscriptionOptionsInit)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent must be primary interface of new PushEvent("type")]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent interface: attribute data]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface: operation arrayBuffer()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation toJSON()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface: operation json()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation unsubscribe()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: attribute applicationServerKey]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: calling subscribe(PushSubscriptionOptionsInit) on registration.pushManager with too few arguments must throw TypeError]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: registration.pushManager must inherit property "subscribe(PushSubscriptionOptionsInit)" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: registration.pushManager must inherit property "permissionState(PushSubscriptionOptionsInit)" with the proper type]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface object length]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[Stringification of new PushEvent("type")]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushMessageData interface: operation blob()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushEvent interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[ServiceWorkerRegistration interface: attribute pushManager]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
|
||||
[idlharness.https.any.sharedworker.html]
|
||||
[PushManager interface object length]
|
||||
expected: FAIL
|
||||
|
@ -98,3 +564,108 @@
|
|||
|
||||
[PushManager interface: attribute supportedContentEncodings]
|
||||
expected: FAIL
|
||||
|
||||
[PushSubscription interface: attribute options]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation getKey(PushEncryptionKeyName)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation permissionState(PushSubscriptionOptionsInit)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation getSubscription()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: operation subscribe(PushSubscriptionOptionsInit)]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation toJSON()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: operation unsubscribe()]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: attribute applicationServerKey]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: attribute endpoint]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscription interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface object length]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushManager interface: existence and properties of interface prototype object]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[PushSubscriptionOptions interface object name]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
[ServiceWorkerRegistration interface: attribute pushManager]
|
||||
expected:
|
||||
if (os == "android") and e10s: FAIL
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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 Base64UtilsSupport_h__
|
||||
#define Base64UtilsSupport_h__
|
||||
|
||||
#include "GeneratedJNINatives.h"
|
||||
#include "mozilla/Base64.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace widget {
|
||||
|
||||
class Base64UtilsSupport final
|
||||
: public java::Base64Utils::Natives<Base64UtilsSupport> {
|
||||
public:
|
||||
static jni::ByteArray::LocalRef Decode(jni::String::Param data) {
|
||||
FallibleTArray<uint8_t> bytes;
|
||||
if (NS_FAILED(Base64URLDecode(
|
||||
data->ToCString(), Base64URLDecodePaddingPolicy::Ignore, bytes))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return jni::ByteArray::New((const signed char*)bytes.Elements(),
|
||||
bytes.Length());
|
||||
}
|
||||
|
||||
static jni::String::LocalRef Encode(jni::ByteArray::Param data) {
|
||||
nsTArray<int8_t> bytes = data->GetElements();
|
||||
nsCString result;
|
||||
if (NS_FAILED(
|
||||
Base64URLEncode(data->Length(), (const uint8_t*)bytes.Elements(),
|
||||
Base64URLEncodePaddingPolicy::Omit, result))) {
|
||||
return nullptr;
|
||||
}
|
||||
return jni::StringParam(result);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace widget
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // Base64UtilsSupport_h__
|
|
@ -72,7 +72,6 @@
|
|||
#include "fennec/MemoryMonitor.h"
|
||||
#include "fennec/ThumbnailHelper.h"
|
||||
#include "WebExecutorSupport.h"
|
||||
#include "Base64UtilsSupport.h"
|
||||
|
||||
#ifdef DEBUG_ANDROID_EVENTS
|
||||
# define EVLOG(args...) ALOG(args)
|
||||
|
@ -413,7 +412,6 @@ nsAppShell::nsAppShell()
|
|||
mozilla::PrefsHelper::Init();
|
||||
mozilla::widget::Telemetry::Init();
|
||||
mozilla::widget::WebExecutorSupport::Init();
|
||||
mozilla::widget::Base64UtilsSupport::Init();
|
||||
nsWindow::InitNatives();
|
||||
mozilla::gl::AndroidSurfaceTexture::Init();
|
||||
mozilla::WebAuthnTokenManager::Init();
|
||||
|
|
Загрузка…
Ссылка в новой задаче