diff --git a/mobile/android/examples/extensions/messaging_example/app/.gitignore b/mobile/android/examples/extensions/messaging_example/app/.gitignore
new file mode 100644
--- /dev/null
+++ b/mobile/android/examples/extensions/messaging_example/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/mobile/android/examples/extensions/messaging_example/app/build.gradle b/mobile/android/examples/extensions/messaging_example/app/build.gradle
new file mode 100644
--- /dev/null
+++ b/mobile/android/examples/extensions/messaging_example/app/build.gradle
@@ -0,0 +1,51 @@
+buildDir "${topobjdir}/gradle/build/mobile/android/examples/messaging_example"
+
+apply plugin: 'com.android.application'
+
+apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle"
+
+android {
+ compileSdkVersion project.ext.compileSdkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ defaultConfig {
+ applicationId "org.mozilla.geckoview.example.messaging"
+ targetSdkVersion project.ext.targetSdkVersion
+ minSdkVersion project.ext.minSdkVersion
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ // By default the android plugins ignores folders that start with `_`, but
+ // we need those in web extensions.
+ // See also:
+ // - https://issuetracker.google.com/issues/36911326
+ // - https://stackoverflow.com/questions/9206117/how-to-workaround-autoomitting-fiiles-folders-starting-with-underscore-in
+ aaptOptions {
+ ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
+ }
+
+ project.configureProductFlavors.delegate = it
+ project.configureProductFlavors()
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "com.android.support:support-annotations:$support_library_version"
+ implementation "com.android.support:appcompat-v7:$support_library_version"
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation project(path: ':geckoview')
+}
diff --git a/mobile/android/examples/extensions/messaging_example/app/src/main/AndroidManifest.xml b/mobile/android/examples/extensions/messaging_example/app/src/main/AndroidManifest.xml
new file mode 100644
--- /dev/null
+++ b/mobile/android/examples/extensions/messaging_example/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/android/examples/extensions/messaging_example/app/src/main/assets/messaging/manifest.json b/mobile/android/examples/extensions/messaging_example/app/src/main/assets/messaging/manifest.json
new file mode 100644
--- /dev/null
+++ b/mobile/android/examples/extensions/messaging_example/app/src/main/assets/messaging/manifest.json
@@ -0,0 +1,16 @@
+{
+ "manifest_version": 2,
+ "name": "messaging",
+ "version": "1.0",
+ "description": "Example messaging web extension.",
+ "content_scripts": [
+ {
+ "matches": ["*://*.twitter.com/*"],
+ "js": ["messaging.js"]
+ }
+ ],
+ "permissions": [
+ "nativeMessaging",
+ "geckoViewAddons"
+ ]
+}
diff --git a/mobile/android/examples/extensions/messaging_example/app/src/main/assets/messaging/messaging.js b/mobile/android/examples/extensions/messaging_example/app/src/main/assets/messaging/messaging.js
new file mode 100644
--- /dev/null
+++ b/mobile/android/examples/extensions/messaging_example/app/src/main/assets/messaging/messaging.js
@@ -0,0 +1,14 @@
+/* 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/. */
+
+let manifest = document.querySelector("head > link[rel=manifest]");
+let browser = window.docShell.chromeEventHandler;
+if (manifest) {
+ fetch(manifest.href)
+ .then(response => response.json())
+ .then(json => {
+ let message = { type: "WPAManifest", manifest: json };
+ browser.runtime.sendNativeMessage("browser", message);
+ });
+}
diff --git a/mobile/android/examples/extensions/messaging_example/app/src/main/java/org/mozilla/geckoview/example/messaging/MainActivity.java b/mobile/android/examples/extensions/messaging_example/app/src/main/java/org/mozilla/geckoview/example/messaging/MainActivity.java
new file mode 100644
--- /dev/null
+++ b/mobile/android/examples/extensions/messaging_example/app/src/main/java/org/mozilla/geckoview/example/messaging/MainActivity.java
@@ -0,0 +1,67 @@
+package org.mozilla.geckoview.example.messaging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.geckoview.GeckoResult;
+import org.mozilla.geckoview.GeckoRuntime;
+import org.mozilla.geckoview.GeckoSession;
+import org.mozilla.geckoview.GeckoView;
+import org.mozilla.geckoview.WebExtension;
+
+public class MainActivity extends AppCompatActivity {
+ private static GeckoRuntime sRuntime;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ GeckoView view = findViewById(R.id.geckoview);
+ GeckoSession session = new GeckoSession();
+
+ if (sRuntime == null) {
+ sRuntime = GeckoRuntime.create(this);
+ }
+
+ WebExtension.MessageDelegate messageDelegate = new WebExtension.MessageDelegate() {
+ @Nullable
+ public GeckoResult