зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1522451 - Add ContentDelegate.onWebAppManifest() r=geckoview-reviewers,agi,droeh
This delivers a parsed and validated Web App Manifest to the application, if present, during the page load process. Differential Revision: https://phabricator.services.mozilla.com/D22612 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
c54e1232f7
Коммит
cdb58ae6cf
|
@ -31,6 +31,7 @@ import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.mozilla.gecko.ActivityHandlerHelper;
|
import org.mozilla.gecko.ActivityHandlerHelper;
|
||||||
import org.mozilla.gecko.BrowserApp;
|
import org.mozilla.gecko.BrowserApp;
|
||||||
import org.mozilla.gecko.Clipboard;
|
import org.mozilla.gecko.Clipboard;
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.mozilla.gecko.ActivityHandlerHelper;
|
import org.mozilla.gecko.ActivityHandlerHelper;
|
||||||
import org.mozilla.gecko.AppConstants;
|
import org.mozilla.gecko.AppConstants;
|
||||||
import org.mozilla.gecko.BrowserApp;
|
import org.mozilla.gecko.BrowserApp;
|
||||||
|
|
|
@ -21,6 +21,7 @@ const SCROLL_BEHAVIOR_AUTO = 1;
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
|
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
|
||||||
GeckoViewAutoFill: "resource://gre/modules/GeckoViewAutoFill.jsm",
|
GeckoViewAutoFill: "resource://gre/modules/GeckoViewAutoFill.jsm",
|
||||||
|
ManifestObtainer: "resource://gre/modules/ManifestObtainer.jsm",
|
||||||
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
|
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
|
||||||
SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
|
SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
|
||||||
});
|
});
|
||||||
|
@ -77,6 +78,7 @@ class GeckoViewContentChild extends GeckoViewChildModule {
|
||||||
addEventListener("MozDOMFullscreen:Exited", this, false);
|
addEventListener("MozDOMFullscreen:Exited", this, false);
|
||||||
addEventListener("MozDOMFullscreen:Request", this, false);
|
addEventListener("MozDOMFullscreen:Request", this, false);
|
||||||
addEventListener("contextmenu", this, { capture: true });
|
addEventListener("contextmenu", this, { capture: true });
|
||||||
|
addEventListener("DOMContentLoaded", this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisable() {
|
onDisable() {
|
||||||
|
@ -90,6 +92,7 @@ class GeckoViewContentChild extends GeckoViewChildModule {
|
||||||
removeEventListener("MozDOMFullscreen:Exited", this);
|
removeEventListener("MozDOMFullscreen:Exited", this);
|
||||||
removeEventListener("MozDOMFullscreen:Request", this);
|
removeEventListener("MozDOMFullscreen:Request", this);
|
||||||
removeEventListener("contextmenu", this, { capture: true });
|
removeEventListener("contextmenu", this, { capture: true });
|
||||||
|
removeEventListener("DOMContentLoaded", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
collectSessionState() {
|
collectSessionState() {
|
||||||
|
@ -425,6 +428,24 @@ class GeckoViewContentChild extends GeckoViewChildModule {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "DOMContentLoaded": {
|
||||||
|
content.requestIdleCallback(async () => {
|
||||||
|
let manifest = null;
|
||||||
|
try {
|
||||||
|
manifest = await ManifestObtainer.contentObtainManifest(content);
|
||||||
|
} catch (e) {
|
||||||
|
// Unfortunately, this throws if there is no manifest present, so we
|
||||||
|
// probably don't want to log anything here. Bug 1534756.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest) {
|
||||||
|
this.eventDispatcher.sendRequest({
|
||||||
|
type: "GeckoView:WebAppManifest",
|
||||||
|
manifest,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -354,6 +354,7 @@ package org.mozilla.geckoview {
|
||||||
method @android.support.annotation.UiThread default public void onFocusRequest(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
|
method @android.support.annotation.UiThread default public void onFocusRequest(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
|
||||||
method @android.support.annotation.UiThread default public void onFullScreen(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, boolean);
|
method @android.support.annotation.UiThread default public void onFullScreen(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, boolean);
|
||||||
method @android.support.annotation.UiThread default public void onTitleChange(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String);
|
method @android.support.annotation.UiThread default public void onTitleChange(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String);
|
||||||
|
method @android.support.annotation.UiThread default public void onWebAppManifest(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.NonNull org.json.JSONObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GeckoSession.ContentDelegate.ContextElement {
|
public static class GeckoSession.ContentDelegate.ContextElement {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<html>
|
<html>
|
||||||
<head><title>Hello, world!</title></head>
|
<head>
|
||||||
|
<title>Hello, world!</title>
|
||||||
|
<link rel="manifest" href="manifest.webmanifest">
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Hello, world!</p>
|
<p>Hello, world!</p>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "App",
|
||||||
|
"short_name": "app",
|
||||||
|
"start_url": "./start/index.html",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#c0feee",
|
||||||
|
"theme_color": "cadetblue",
|
||||||
|
"icons": [{
|
||||||
|
"src": "images/test.gif",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/gif"
|
||||||
|
}],
|
||||||
|
"related_applications": [{
|
||||||
|
"platform": "play",
|
||||||
|
"url": "https://play.google.com/store/apps/details?id=my.first.webapp"
|
||||||
|
}]
|
||||||
|
}
|
|
@ -6,9 +6,9 @@ package org.mozilla.geckoview.test
|
||||||
|
|
||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
import android.graphics.SurfaceTexture
|
import android.graphics.SurfaceTexture
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import org.mozilla.geckoview.AllowOrDeny
|
import org.mozilla.geckoview.AllowOrDeny
|
||||||
import org.mozilla.geckoview.GeckoDisplay
|
|
||||||
import org.mozilla.geckoview.GeckoResult
|
import org.mozilla.geckoview.GeckoResult
|
||||||
import org.mozilla.geckoview.GeckoSession
|
import org.mozilla.geckoview.GeckoSession
|
||||||
import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
|
import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
|
||||||
|
@ -21,6 +21,7 @@ import org.mozilla.geckoview.test.util.Callbacks
|
||||||
import org.mozilla.geckoview.test.util.UiThreadUtils
|
import org.mozilla.geckoview.test.util.UiThreadUtils
|
||||||
|
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.support.test.InstrumentationRegistry
|
||||||
import android.support.test.filters.MediumTest
|
import android.support.test.filters.MediumTest
|
||||||
import android.support.test.filters.SdkSuppress
|
import android.support.test.filters.SdkSuppress
|
||||||
import android.support.test.runner.AndroidJUnit4
|
import android.support.test.runner.AndroidJUnit4
|
||||||
|
@ -31,15 +32,22 @@ import android.view.View
|
||||||
import android.view.ViewStructure
|
import android.view.ViewStructure
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import org.hamcrest.Matchers.*
|
import org.hamcrest.Matchers.*
|
||||||
|
import org.json.JSONObject
|
||||||
import org.junit.Assume.assumeThat
|
import org.junit.Assume.assumeThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.geckoview.test.util.HttpBin
|
||||||
|
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
class ContentDelegateTest : BaseSessionTest() {
|
class ContentDelegateTest : BaseSessionTest() {
|
||||||
|
companion object {
|
||||||
|
val TEST_ENDPOINT: String = "http://localhost:4243"
|
||||||
|
}
|
||||||
|
|
||||||
@Test fun titleChange() {
|
@Test fun titleChange() {
|
||||||
sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
|
sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
|
||||||
|
@ -580,4 +588,44 @@ class ContentDelegateTest : BaseSessionTest() {
|
||||||
display.surfaceDestroyed()
|
display.surfaceDestroyed()
|
||||||
mainSession.releaseDisplay(display)
|
mainSession.releaseDisplay(display)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun webAppManifest() {
|
||||||
|
val httpBin = HttpBin(InstrumentationRegistry.getTargetContext(), URI.create(TEST_ENDPOINT))
|
||||||
|
|
||||||
|
try {
|
||||||
|
httpBin.start()
|
||||||
|
|
||||||
|
mainSession.loadUri("$TEST_ENDPOINT$HELLO_HTML_PATH")
|
||||||
|
mainSession.waitUntilCalled(object : Callbacks.All {
|
||||||
|
|
||||||
|
@AssertCalled(count = 1)
|
||||||
|
override fun onPageStop(session: GeckoSession, success: Boolean) {
|
||||||
|
assertThat("Page load should succeed", success, equalTo(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssertCalled(count = 1)
|
||||||
|
override fun onWebAppManifest(session: GeckoSession, manifest: JSONObject) {
|
||||||
|
// These values come from the manifest at assets/www/manifest.webmanifest
|
||||||
|
assertThat("name should match", manifest.getString("name"), equalTo("App"))
|
||||||
|
assertThat("short_name should match", manifest.getString("short_name"), equalTo("app"))
|
||||||
|
assertThat("display should match", manifest.getString("display"), equalTo("standalone"))
|
||||||
|
|
||||||
|
// The color here is "cadetblue" converted to hex.
|
||||||
|
assertThat("theme_color should match", manifest.getString("theme_color"), equalTo("#5f9ea0"))
|
||||||
|
assertThat("background_color should match", manifest.getString("background_color"), equalTo("#c0feee"))
|
||||||
|
assertThat("start_url should match", manifest.getString("start_url"), equalTo("$TEST_ENDPOINT/assets/www/start/index.html"))
|
||||||
|
|
||||||
|
val icon = manifest.getJSONArray("icons").getJSONObject(0);
|
||||||
|
|
||||||
|
val iconSrc = Uri.parse(icon.getString("src"))
|
||||||
|
assertThat("icon should have a valid src", iconSrc, notNullValue())
|
||||||
|
assertThat("icon src should be absolute", iconSrc.isAbsolute, equalTo(true))
|
||||||
|
assertThat("icon should have sizes", icon.getString("sizes"), not(isEmptyOrNullString()))
|
||||||
|
assertThat("icon type should match", icon.getString("type"), equalTo("image/gif"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
httpBin.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package org.mozilla.geckoview.test;
|
package org.mozilla.geckoview.test;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.mozilla.geckoview.AllowOrDeny;
|
import org.mozilla.geckoview.AllowOrDeny;
|
||||||
import org.mozilla.geckoview.GeckoDisplay;
|
import org.mozilla.geckoview.GeckoDisplay;
|
||||||
import org.mozilla.geckoview.GeckoResult;
|
import org.mozilla.geckoview.GeckoResult;
|
||||||
|
@ -113,6 +114,10 @@ public class TestRunnerActivity extends Activity {
|
||||||
@Override
|
@Override
|
||||||
public void onFirstComposite(final GeckoSession session) {
|
public void onFirstComposite(final GeckoSession session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWebAppManifest(final GeckoSession session, final JSONObject manifest) {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private GeckoSession createSession() {
|
private GeckoSession createSession() {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.mozilla.geckoview.WebRequestError
|
||||||
import android.view.inputmethod.CursorAnchorInfo
|
import android.view.inputmethod.CursorAnchorInfo
|
||||||
import android.view.inputmethod.ExtractedText
|
import android.view.inputmethod.ExtractedText
|
||||||
import android.view.inputmethod.ExtractedTextRequest
|
import android.view.inputmethod.ExtractedTextRequest
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
class Callbacks private constructor() {
|
class Callbacks private constructor() {
|
||||||
object Default : All
|
object Default : All
|
||||||
|
|
|
@ -11,6 +11,8 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||||
import org.mozilla.gecko.EventDispatcher;
|
import org.mozilla.gecko.EventDispatcher;
|
||||||
import org.mozilla.gecko.GeckoAppShell;
|
import org.mozilla.gecko.GeckoAppShell;
|
||||||
|
@ -348,6 +350,7 @@ public class GeckoSession implements Parcelable {
|
||||||
"GeckoView:ExternalResponse",
|
"GeckoView:ExternalResponse",
|
||||||
"GeckoView:FullScreenEnter",
|
"GeckoView:FullScreenEnter",
|
||||||
"GeckoView:FullScreenExit",
|
"GeckoView:FullScreenExit",
|
||||||
|
"GeckoView:WebAppManifest",
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -387,6 +390,17 @@ public class GeckoSession implements Parcelable {
|
||||||
delegate.onFullScreen(GeckoSession.this, false);
|
delegate.onFullScreen(GeckoSession.this, false);
|
||||||
} else if ("GeckoView:ExternalResponse".equals(event)) {
|
} else if ("GeckoView:ExternalResponse".equals(event)) {
|
||||||
delegate.onExternalResponse(GeckoSession.this, new WebResponseInfo(message));
|
delegate.onExternalResponse(GeckoSession.this, new WebResponseInfo(message));
|
||||||
|
} else if ("GeckoView:WebAppManifest".equals(event)) {
|
||||||
|
final GeckoBundle manifest = message.getBundle("manifest");
|
||||||
|
if (manifest == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
delegate.onWebAppManifest(GeckoSession.this, manifest.toJSONObject());
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(LOGTAG, "Failed to convert web app manifest to JSON", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2769,6 +2783,16 @@ public class GeckoSession implements Parcelable {
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
default void onFirstComposite(@NonNull GeckoSession session) {}
|
default void onFirstComposite(@NonNull GeckoSession session) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is fired when the loaded document has a valid Web App Manifest present.
|
||||||
|
*
|
||||||
|
* @param session The GeckoSession that contains the Web App Manifest
|
||||||
|
* @param manifest A parsed and validated {@link JSONObject} containing the manifest contents.
|
||||||
|
* @see <a href="https://www.w3.org/TR/appmanifest/">Web App Manifest specification</a>
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
default void onWebAppManifest(@NonNull GeckoSession session, @NonNull JSONObject manifest) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface SelectionActionDelegate {
|
public interface SelectionActionDelegate {
|
||||||
|
|
|
@ -96,6 +96,11 @@ exclude: true
|
||||||
|
|
||||||
- Added `default` implementations for all non-functional `interface`s.
|
- Added `default` implementations for all non-functional `interface`s.
|
||||||
|
|
||||||
|
- Added [`ContentDelegate.onWebAppManifest`][67.22], which will deliver the contents of a parsed
|
||||||
|
and validated Web App Manifest on pages that contain one.
|
||||||
|
|
||||||
|
[67.22]: ../GeckoSession.ContentDelegate.html#onWebAppManifest-org.mozilla.geckoview.GeckoSession-org.json.JSONObject
|
||||||
|
|
||||||
## v66
|
## v66
|
||||||
- Removed redundant field `trackingMode` from [`SecurityInformation`][66.6].
|
- Removed redundant field `trackingMode` from [`SecurityInformation`][66.6].
|
||||||
Use `TrackingProtectionDelegate.onTrackerBlocked` for notification of blocked
|
Use `TrackingProtectionDelegate.onTrackerBlocked` for notification of blocked
|
||||||
|
@ -215,4 +220,4 @@ exclude: true
|
||||||
[65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
|
[65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
|
||||||
[65.25]: ../GeckoResult.html
|
[65.25]: ../GeckoResult.html
|
||||||
|
|
||||||
[api-version]: e1330c0e7cfa08420041813f07f24a9389020564
|
[api-version]: 07af02921c277f9461d7532f2a6a78c527c9cb47
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package org.mozilla.geckoview_example;
|
package org.mozilla.geckoview_example;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.mozilla.geckoview.AllowOrDeny;
|
import org.mozilla.geckoview.AllowOrDeny;
|
||||||
import org.mozilla.geckoview.BasicSelectionActionDelegate;
|
import org.mozilla.geckoview.BasicSelectionActionDelegate;
|
||||||
import org.mozilla.geckoview.ContentBlocking;
|
import org.mozilla.geckoview.ContentBlocking;
|
||||||
|
@ -533,6 +534,11 @@ public class GeckoViewActivity extends AppCompatActivity {
|
||||||
public void onFirstComposite(final GeckoSession session) {
|
public void onFirstComposite(final GeckoSession session) {
|
||||||
Log.d(LOGTAG, "onFirstComposite");
|
Log.d(LOGTAG, "onFirstComposite");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWebAppManifest(final GeckoSession session, JSONObject manifest) {
|
||||||
|
Log.d(LOGTAG, "onWebAppManifest: " + manifest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ExampleProgressDelegate implements GeckoSession.ProgressDelegate {
|
private class ExampleProgressDelegate implements GeckoSession.ProgressDelegate {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче