Bug 1360068 - Overhaul WebAppActivity to use GeckoView rather than GeckoApp. r=jchen,daleharvey
authorDylan Roeh <droeh@mozilla.com>
Wed, 31 May 2017 16:59:50 -0500
changeset 409789 3e9ceef9b2802d2d2353b8389e6f09c77f991577
parent 409788 5eb1cfd59d0017e714ac7eafc78561e346d5ed5f
child 409790 2df9b5a84a80ba7259e6175c126727b87b4ce808
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen, daleharvey
bugs1360068
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1360068 - Overhaul WebAppActivity to use GeckoView rather than GeckoApp. r=jchen,daleharvey
mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -2,39 +2,43 @@
  * 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.gecko.webapps;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
 
 import android.app.ActivityManager;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.Toolbar;
-import android.support.v7.app.ActionBar;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
 import org.json.JSONObject;
 import org.json.JSONException;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoView;
+import org.mozilla.gecko.GeckoViewSettings;
 import org.mozilla.gecko.SingleTabActivity;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.icons.decoders.FaviconDecoder;
 import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
@@ -43,188 +47,81 @@ import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.AnchoredPopup;
 
 import static org.mozilla.gecko.Tabs.TabEvents;
 
-public class WebAppActivity extends SingleTabActivity {
+public class WebAppActivity extends AppCompatActivity
+                            implements GeckoView.NavigationListener {
     private static final String LOGTAG = "WebAppActivity";
 
     public static final String MANIFEST_PATH = "MANIFEST_PATH";
     private static final String SAVED_INTENT = "savedIntent";
 
     private TextView mUrlView;
-    private View doorhangerOverlay;
+    private GeckoView mGeckoView;
 
+    private Uri mScope;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0 &&
-        savedInstanceState != null) {
+            savedInstanceState != null) {
             // Even though we're a single task activity, Android's task switcher has the
             // annoying habit of never updating its stored intent after our initial creation,
             // even if we've been subsequently started with a new intent.
 
             // This below is needed if we should ever decide to store a custom class as intent extra.
             savedInstanceState.setClassLoader(getClass().getClassLoader());
 
             Intent lastLaunchIntent = savedInstanceState.getParcelable(SAVED_INTENT);
             setIntent(lastLaunchIntent);
         }
 
         super.onCreate(savedInstanceState);
 
+        setContentView(R.layout.customtabs_activity);
+
         final Toolbar toolbar = (Toolbar) findViewById(R.id.actionbar);
         setSupportActionBar(toolbar);
 
-        final ProgressBar progressBar = (ProgressBar) findViewById(R.id.page_progress);
-        progressBar.setVisibility(View.GONE);
-
         final ActionBar actionBar = getSupportActionBar();
         actionBar.setCustomView(R.layout.webapps_action_bar_custom_view);
         actionBar.setDisplayShowCustomEnabled(true);
         actionBar.setDisplayShowTitleEnabled(false);
         actionBar.hide();
 
-        doorhangerOverlay = findViewById(R.id.custom_tabs_doorhanger_overlay);
-
         final View customView = actionBar.getCustomView();
         mUrlView = (TextView) customView.findViewById(R.id.webapps_action_bar_url);
 
-        EventDispatcher.getInstance().registerUiThreadListener(this,
-                "Website:AppEntered",
-                "Website:AppLeft",
-                null);
-    }
+        mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
 
-    @Override
-    public View getDoorhangerOverlay() {
-        return doorhangerOverlay;
-    }
+        mGeckoView.setNavigationListener(this);
 
-    @Override
-    public int getLayout() {
-        return R.layout.customtabs_activity;
-    }
+        final GeckoViewSettings settings = mGeckoView.getSettings();
+        settings.setBoolean(GeckoViewSettings.USE_MULTIPROCESS, false);
 
-    @Override
-    public void handleMessage(final String event, final GeckoBundle message,
-                              final EventCallback callback) {
-        super.handleMessage(event, message, callback);
-
-        if (message == null ||
-                !message.containsKey("tabId") || message.getInt("tabId") != mLastSelectedTabId) {
-            return;
+        final Uri u = getIntent().getData();
+        if (u != null) {
+            mGeckoView.loadUri(u.toString());
         }
 
-        switch (event) {
-            case "Website:AppEntered":
-                getSupportActionBar().hide();
-                break;
-
-            case "Website:AppLeft":
-                getSupportActionBar().show();
-                break;
-        }
-    }
-
-    @Override
-    public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
-        super.onTabChanged(tab, msg, data);
-
-        if (tab == null || !Tabs.getInstance().isSelectedTab(tab) ||
-                tab.getType() != Tab.TabType.WEBAPP) {
-            return;
-        }
-
-        if (msg == TabEvents.LOCATION_CHANGE ||
-                msg == TabEvents.SELECTED) {
-            mUrlView.setText(tab.getURL());
-        }
+        loadManifest(getIntent().getStringExtra(MANIFEST_PATH));
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
         outState.putParcelable(SAVED_INTENT, getIntent());
     }
 
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        EventDispatcher.getInstance().unregisterUiThreadListener(this,
-                "Website:AppEntered",
-                "Website:AppLeft",
-                null);
-    }
-
-    @Override
-    protected int getNewTabFlags() {
-        return Tabs.LOADURL_WEBAPP | super.getNewTabFlags();
-    }
-
-    @Override
-    protected void onTabOpenFromIntent(Tab tab) {
-        super.onTabOpenFromIntent(tab);
-        Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "webapp");
-        loadManifest(tab.getManifestPath());
-    }
-
-    /**
-     * In case this activity and its tab are reused (the user has opened
-     *  > 10 current web apps), we check that app launched is still within
-     * the same host as the intent has set.
-     * If it isn't, we reload the intent URL.
-     */
-    @Override
-    protected void onTabSelectFromIntent(Tab tab) {
-        super.onTabSelectFromIntent(tab);
-
-        SafeIntent intent = new SafeIntent(getIntent());
-
-        final String launchUrl = intent.getDataString();
-        final String currentUrl = tab.getURL();
-        final boolean isSameDomain = Uri.parse(currentUrl).getHost()
-                .equals(Uri.parse(launchUrl).getHost());
-
-        final String manifestPath;
-        if (!isSameDomain) {
-            Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "webapp");
-            manifestPath = intent.getStringExtra(MANIFEST_PATH);
-            tab.setManifestUrl(manifestPath);
-            Tabs.getInstance().loadUrl(launchUrl);
-        } else {
-            manifestPath = tab.getManifestPath();
-        }
-        loadManifest(manifestPath);
-    }
-
-    @Override
-    protected ActionModePresenter getTextSelectPresenter() {
-        return new ActionModePresenter() {
-            private ActionMode mMode;
-
-            @Override
-            public void startActionMode(ActionMode.Callback callback) {
-                mMode = startSupportActionMode(callback);
-            }
-
-            @Override
-            public void endActionMode() {
-                if (mMode != null) {
-                    mMode.finish();
-                }
-            }
-        };
-    }
-
     private void loadManifest(String manifestPath) {
         if (TextUtils.isEmpty(manifestPath)) {
             Log.e(LOGTAG, "Missing manifest");
             return;
         }
         // The customisations defined in the manifest only work on Android API 21+
         if (AppConstants.Versions.preLollipop) {
             return;
@@ -232,16 +129,17 @@ public class WebAppActivity extends Sing
 
         try {
             final File manifestFile = new File(manifestPath);
             final JSONObject manifest = FileUtils.readJSONObjectFromFile(manifestFile);
             final JSONObject manifestField = manifest.getJSONObject("manifest");
             final Integer color = readColorFromManifest(manifestField);
             final String name = readNameFromManifest(manifestField);
             final Bitmap icon = readIconFromManifest(manifest);
+            mScope = readScopeFromManifest(manifest, manifestPath);
             final ActivityManager.TaskDescription taskDescription = (color == null)
                     ? new ActivityManager.TaskDescription(name, icon)
                     : new ActivityManager.TaskDescription(name, icon, color);
 
             updateStatusBarColor(color);
             setTaskDescription(taskDescription);
 
         } catch (IOException | JSONException e) {
@@ -277,15 +175,81 @@ public class WebAppActivity extends Sing
     }
 
     private Bitmap readIconFromManifest(JSONObject manifest) {
         final String iconStr = manifest.optString("cached_icon", null);
         if (iconStr == null) {
             return null;
         }
         final LoadFaviconResult loadIconResult = FaviconDecoder
-            .decodeDataURI(getContext(), iconStr);
+            .decodeDataURI(this, iconStr);
         if (loadIconResult == null) {
             return null;
         }
         return loadIconResult.getBestBitmap(GeckoAppShell.getPreferredIconSize());
     }
+
+    private Uri readScopeFromManifest(JSONObject manifest, String manifestPath) {
+        final String scopeStr = manifest.optString("scope", null);
+        if (scopeStr == null) {
+            return null;
+        }
+
+        Uri res = Uri.parse(scopeStr);
+        if (res.isRelative()) {
+            // TODO: Handle this more correctly.
+            return null;
+        }
+
+        return res;
+    }
+
+    private boolean isInScope(String url) {
+        if (mScope == null) {
+            return true;
+        }
+
+        final Uri uri = Uri.parse(url);
+
+        if (!uri.getScheme().equals(mScope.getScheme())) {
+            return false;
+        }
+
+        if (!uri.getHost().equals(mScope.getHost())) {
+            return false;
+        }
+
+        final List<String> scopeSegments = mScope.getPathSegments();
+        final List<String> urlSegments = uri.getPathSegments();
+
+        if (scopeSegments.size() > urlSegments.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < scopeSegments.size(); i++) {
+            if (!scopeSegments.get(i).equals(urlSegments.get(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /* GeckoView.NavigationListener */
+    @Override
+    public void onLocationChange(GeckoView view, String url) {
+        if (isInScope(url)) {
+            getSupportActionBar().hide();
+        } else {
+            getSupportActionBar().show();
+        }
+
+        mUrlView.setText(url);
+    }
+
+    @Override
+    public void onCanGoBack(GeckoView view, boolean canGoBack) {
+    }
+
+    @Override
+    public void onCanGoForward(GeckoView view, boolean canGoForward) {
+    }
 }