Bug 1271998 - Part 2 - Make our URL bar scrollable. r?walkingice,jwu draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Sun, 27 Aug 2017 17:31:13 +0200
changeset 659241 9e97442cdf91a20486f5d09841a90b6e45eab83b
parent 659240 79a5527aa40d1da45e25dc15843bf47cc76457b7
child 659242 b89f3e004db350d5b740f36c708482bb8ce52c18
push id78062
push usermozilla@buttercookie.de
push dateTue, 05 Sep 2017 18:20:38 +0000
reviewerswalkingice, jwu
bugs1271998
milestone57.0a1
Bug 1271998 - Part 2 - Make our URL bar scrollable. r?walkingice,jwu Limited space for URLs on mobile browsers has given rise to a class of phishing attacks that rely on a carefully crafted URL with a long subdomain being cut off such as to give the impression of another, legitimate URL [1]. We've experimented in the past with avoiding this by showing only the base domain or the EV certificate owner, but had to revert to the old behaviour because of users complaining about not being able to see as much of the URL as formerly possible. Making the displayed URL scrollable is therefore a nice solution: It allows us to choose the initial scroll position such as to put the focus on the base domain, while giving users the freedom to easily view all the rest of the URL without having to enter editing mode. To make the URL scrollable, we wrap the TextView with a HorizontalScrollView. Alternatively, it would have been possible to use a ScrollingMovementMethod with the TextView, however that way - flinging the text doesn't work out of the box - dragging the text around is still detected as a normal long-press as well and triggers the context menu [1]. E.g. https://manage-myaccount.paypal.com-webapps.verifcheck.com/signin/ (see https://twitter.com/ericlaw/status/900429796240277504 for an example screenshot). MozReview-Commit-ID: LPEXQA2kBvD
mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
mobile/android/app/src/photon/res/layout/toolbar_display_layout.xml
mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
--- a/mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/app/src/photon/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
@@ -35,16 +35,17 @@ import android.text.SpannableStringBuild
 import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.Button;
 
 import org.mozilla.gecko.switchboard.SwitchBoard;
+import org.mozilla.gecko.widget.themed.ThemedView;
 
 /**
 * {@code ToolbarDisplayLayout} is the UI for when the toolbar is in
 * display state. It's used to display the state of the currently selected
 * tab. It should always be updated through a single entry point
 * (updateFromTab) and should never track any tab events or gecko messages
 * on its own to keep it as dumb as possible.
 *
@@ -91,16 +92,17 @@ public class ToolbarDisplayLayout extend
 
     private final BrowserApp mActivity;
 
     private UIMode mUiMode;
 
     private boolean mIsAttached;
 
     private final ThemedTextView mTitle;
+    private final ThemedView mTitleBackground;
     private final int mTitlePadding;
     private ToolbarPrefs mPrefs;
     private OnTitleChangeListener mTitleChangeListener;
 
     private final ThemedImageButton mSiteSecurity;
     private final ThemedImageButton mStop;
     private OnStopListener mStopListener;
 
@@ -135,16 +137,17 @@ public class ToolbarDisplayLayout extend
         super(context, attrs);
         setOrientation(HORIZONTAL);
 
         mActivity = (BrowserApp) context;
 
         LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this);
 
         mTitle = (ThemedTextView) findViewById(R.id.url_bar_title);
+        mTitleBackground = (ThemedView) findViewById(R.id.url_bar_title_bg);
         mTitlePadding = mTitle.getPaddingRight();
 
         mUrlColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_urltext));
         mPrivateUrlColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_urltext_private));
         mBlockedColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_blockedtext));
         mPrivateBlockedColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_blockedtext_private));
         mDomainColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_domaintext));
         mPrivateDomainColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_domaintext_private));
@@ -162,16 +165,18 @@ public class ToolbarDisplayLayout extend
     }
 
     @Override
     public void setPrivateMode(boolean isPrivate) {
         super.setPrivateMode(isPrivate);
         mSiteSecurity.setPrivateMode(isPrivate);
         mStop.setPrivateMode(isPrivate);
         mPageActionLayout.setPrivateMode(isPrivate);
+        mTitle.setPrivateMode(isPrivate);
+        mTitleBackground.setPrivateMode(isPrivate);
     }
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
         mIsAttached = true;
 
@@ -239,16 +244,17 @@ public class ToolbarDisplayLayout extend
         }
 
         if (flags.contains(UpdateFlags.PROGRESS)) {
             updateProgress(tab);
         }
 
         if (flags.contains(UpdateFlags.PRIVATE_MODE)) {
             mTitle.setPrivateMode(tab.isPrivate());
+            mTitleBackground.setPrivateMode(tab.isPrivate());
         }
     }
 
     void setTitle(CharSequence title) {
         mTitle.setText(title);
 
         if (TextUtils.isEmpty(title)) {
             //  Reset TextDirection to Locale in order to reveal text hint in correct direction
--- a/mobile/android/app/src/photon/res/layout/toolbar_display_layout.xml
+++ b/mobile/android/app/src/photon/res/layout/toolbar_display_layout.xml
@@ -21,25 +21,50 @@
         android:id="@+id/site_security"
         style="@style/UrlBar.SiteIdentity"
         android:layout_gravity="center_vertical"
         android:background="@drawable/url_bar_title_bg"
         android:contentDescription="@string/site_security"
         android:src="@drawable/security_mode_icon"
         tools:src="@drawable/ic_lock"/>
 
-    <org.mozilla.gecko.widget.FadedMultiColorTextView
-        android:id="@+id/url_bar_title"
-        style="@style/UrlBar.Title"
+    <FrameLayout
         android:layout_width="match_parent"
-        android:layout_gravity="center_vertical"
-        android:layout_weight="1.0"
-        android:background="@drawable/url_bar_title_bg"
-        gecko:fadeBackgroundColor="@android:color/transparent"
-        gecko:fadeWidth="40dip"/>
+        android:layout_height="match_parent"
+        android:layout_weight="1.0">
+
+        <org.mozilla.gecko.widget.themed.ThemedView
+            android:id="@+id/url_bar_title_bg"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/browser_toolbar_url_height"
+            android:layout_gravity="center_vertical"
+            android:background="@drawable/url_bar_title_bg"/>
+
+        <!-- We need this on a separate layer to avoid fading out the toolbar background as well
+     and we can't use a hardware layer because that causes problems with the snapshot
+     for our toolbar animation.-->
+        <org.mozilla.gecko.widget.FadedHorizontalScrollView
+            android:id="@+id/url_bar_title_scroll_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:fillViewport="true"
+            android:scrollbars="none"
+            android:overScrollMode="never"
+            android:layerType="software"
+            gecko:fadeWidth="25dp">
+
+            <org.mozilla.gecko.widget.themed.ThemedTextView
+                android:id="@+id/url_bar_title"
+                style="@style/UrlBar.Title"
+                android:layout_width="wrap_content"
+                android:layout_gravity="center_vertical"/>
+
+        </org.mozilla.gecko.widget.FadedHorizontalScrollView>
+
+    </FrameLayout>
 
     <org.mozilla.gecko.toolbar.PageActionLayout
         android:id="@+id/page_action_layout"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:orientation="horizontal"
         android:visibility="gone"
         tools:visibility="visible"/>
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
@@ -51,16 +51,17 @@ import android.util.Log;
 import android.view.ContextMenu;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
+import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 import android.support.annotation.NonNull;
 
 /**
 * {@code BrowserToolbar} is single entry point for users of the toolbar
 * subsystem i.e. this should be the only import outside the 'toolbar'
 * package.
@@ -111,16 +112,17 @@ public abstract class BrowserToolbar ext
     }
 
     protected enum UIMode {
         EDIT,
         DISPLAY
     }
 
     protected final ToolbarDisplayLayout urlDisplayLayout;
+    protected final HorizontalScrollView urlDisplayScroll;
     protected final ToolbarEditLayout urlEditLayout;
     protected final View urlBarEntry;
     protected boolean isSwitchingTabs;
     protected final ThemedImageButton tabsButton;
 
     private AnimatedProgressBar progressBar;
     protected final TabCounter tabsCounter;
     protected final View menuButton;
@@ -181,16 +183,17 @@ public abstract class BrowserToolbar ext
         activity = (BrowserApp) context;
 
         LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this);
 
         Tabs.registerOnTabsChangedListener(this);
         isSwitchingTabs = true;
 
         urlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
+        urlDisplayScroll = (HorizontalScrollView) findViewById(R.id.url_bar_title_scroll_view);
         urlBarEntry = findViewById(R.id.url_bar_entry);
         urlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
 
         tabsButton = (ThemedImageButton) findViewById(R.id.tabs);
         tabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
         tabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
 
         menuButton = findViewById(R.id.menu);
@@ -208,17 +211,20 @@ public abstract class BrowserToolbar ext
         shadowPaint.setStrokeWidth(0.0f);
 
         setUIMode(UIMode.DISPLAY);
 
         prefs = new ToolbarPrefs();
         urlDisplayLayout.setToolbarPrefs(prefs);
         urlEditLayout.setToolbarPrefs(prefs);
 
-        setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
+        // ScrollViews are allowed to have only one child.
+        final View scrollChild = urlDisplayScroll.getChildAt(0);
+
+        scrollChild.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
             @Override
             public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
                 // Do not show the context menu while editing
                 if (isEditing()) {
                     return;
                 }
 
                 // NOTE: Use MenuUtils.safeSetVisible because some actions might
@@ -251,17 +257,17 @@ public abstract class BrowserToolbar ext
                     menu.findItem(R.id.add_to_launcher).setVisible(false);
                     menu.findItem(R.id.set_as_homepage).setVisible(false);
                     MenuUtils.safeSetVisible(menu, R.id.subscribe, false);
                     MenuUtils.safeSetVisible(menu, R.id.add_search_engine, false);
                 }
             }
         });
 
-        setOnClickListener(new OnClickListener() {
+        scrollChild.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
                 if (activateListener != null) {
                     activateListener.onActivate();
                 }
             }
         });
     }