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:
James Willcox 2019-03-20 14:44:22 +00:00
Родитель c54e1232f7
Коммит cdb58ae6cf
12 изменённых файлов: 136 добавлений и 3 удалений

Просмотреть файл

@ -31,6 +31,7 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import org.json.JSONObject;
import org.mozilla.gecko.ActivityHandlerHelper;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.Clipboard;

Просмотреть файл

@ -21,6 +21,7 @@ import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
import org.json.JSONObject;
import org.mozilla.gecko.ActivityHandlerHelper;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.BrowserApp;

Просмотреть файл

@ -21,6 +21,7 @@ const SCROLL_BEHAVIOR_AUTO = 1;
XPCOMUtils.defineLazyModuleGetters(this, {
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
GeckoViewAutoFill: "resource://gre/modules/GeckoViewAutoFill.jsm",
ManifestObtainer: "resource://gre/modules/ManifestObtainer.jsm",
PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
});
@ -77,6 +78,7 @@ class GeckoViewContentChild extends GeckoViewChildModule {
addEventListener("MozDOMFullscreen:Exited", this, false);
addEventListener("MozDOMFullscreen:Request", this, false);
addEventListener("contextmenu", this, { capture: true });
addEventListener("DOMContentLoaded", this, false);
}
onDisable() {
@ -90,6 +92,7 @@ class GeckoViewContentChild extends GeckoViewChildModule {
removeEventListener("MozDOMFullscreen:Exited", this);
removeEventListener("MozDOMFullscreen:Request", this);
removeEventListener("contextmenu", this, { capture: true });
removeEventListener("DOMContentLoaded", this);
}
collectSessionState() {
@ -425,6 +428,24 @@ class GeckoViewContentChild extends GeckoViewChildModule {
});
}
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 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 onWebAppManifest(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.NonNull org.json.JSONObject);
}
public static class GeckoSession.ContentDelegate.ContextElement {

Просмотреть файл

@ -1,5 +1,8 @@
<html>
<head><title>Hello, world!</title></head>
<head>
<title>Hello, world!</title>
<link rel="manifest" href="manifest.webmanifest">
</head>
<body>
<p>Hello, world!</p>
</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.graphics.SurfaceTexture
import android.net.Uri
import android.os.Build
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoDisplay
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
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 android.os.Looper
import android.support.test.InstrumentationRegistry
import android.support.test.filters.MediumTest
import android.support.test.filters.SdkSuppress
import android.support.test.runner.AndroidJUnit4
@ -31,15 +32,22 @@ import android.view.View
import android.view.ViewStructure
import android.widget.EditText
import org.hamcrest.Matchers.*
import org.json.JSONObject
import org.junit.Assume.assumeThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.test.util.HttpBin
import java.net.URI
import kotlin.concurrent.thread
@RunWith(AndroidJUnit4::class)
@MediumTest
class ContentDelegateTest : BaseSessionTest() {
companion object {
val TEST_ENDPOINT: String = "http://localhost:4243"
}
@Test fun titleChange() {
sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
@ -580,4 +588,44 @@ class ContentDelegateTest : BaseSessionTest() {
display.surfaceDestroyed()
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;
import org.json.JSONObject;
import org.mozilla.geckoview.AllowOrDeny;
import org.mozilla.geckoview.GeckoDisplay;
import org.mozilla.geckoview.GeckoResult;
@ -113,6 +114,10 @@ public class TestRunnerActivity extends Activity {
@Override
public void onFirstComposite(final GeckoSession session) {
}
@Override
public void onWebAppManifest(final GeckoSession session, final JSONObject manifest) {
}
};
private GeckoSession createSession() {

Просмотреть файл

@ -18,6 +18,7 @@ import org.mozilla.geckoview.WebRequestError
import android.view.inputmethod.CursorAnchorInfo
import android.view.inputmethod.ExtractedText
import android.view.inputmethod.ExtractedTextRequest
import org.json.JSONObject
class Callbacks private constructor() {
object Default : All

Просмотреть файл

@ -11,6 +11,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.UUID;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
@ -348,6 +350,7 @@ public class GeckoSession implements Parcelable {
"GeckoView:ExternalResponse",
"GeckoView:FullScreenEnter",
"GeckoView:FullScreenExit",
"GeckoView:WebAppManifest",
}
) {
@Override
@ -387,6 +390,17 @@ public class GeckoSession implements Parcelable {
delegate.onFullScreen(GeckoSession.this, false);
} else if ("GeckoView:ExternalResponse".equals(event)) {
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
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 {

Просмотреть файл

@ -96,6 +96,11 @@ exclude: true
- 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
- Removed redundant field `trackingMode` from [`SecurityInformation`][66.6].
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.25]: ../GeckoResult.html
[api-version]: e1330c0e7cfa08420041813f07f24a9389020564
[api-version]: 07af02921c277f9461d7532f2a6a78c527c9cb47

Просмотреть файл

@ -5,6 +5,7 @@
package org.mozilla.geckoview_example;
import org.json.JSONObject;
import org.mozilla.geckoview.AllowOrDeny;
import org.mozilla.geckoview.BasicSelectionActionDelegate;
import org.mozilla.geckoview.ContentBlocking;
@ -533,6 +534,11 @@ public class GeckoViewActivity extends AppCompatActivity {
public void onFirstComposite(final GeckoSession session) {
Log.d(LOGTAG, "onFirstComposite");
}
@Override
public void onWebAppManifest(final GeckoSession session, JSONObject manifest) {
Log.d(LOGTAG, "onWebAppManifest: " + manifest);
}
}
private class ExampleProgressDelegate implements GeckoSession.ProgressDelegate {