1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.omnibox;
6 
7 import android.net.Uri;
8 import android.text.Spanned;
9 import android.text.TextUtils;
10 
11 import androidx.annotation.Nullable;
12 
13 import org.chromium.base.CollectionUtil;
14 import org.chromium.components.embedder_support.util.UrlConstants;
15 import org.chromium.content_public.common.ContentUrlConstants;
16 
17 import java.util.HashSet;
18 
19 /**
20  * Encapsulates all data that is necessary for the URL bar to display its contents.
21  */
22 public class UrlBarData {
23     /**
24      * The URL schemes that should be displayed complete with path.
25      */
26     public static final HashSet<String> UNSUPPORTED_SCHEMES_TO_SPLIT =
27             CollectionUtil.newHashSet(ContentUrlConstants.ABOUT_SCHEME, UrlConstants.DATA_SCHEME,
28                     UrlConstants.FILE_SCHEME, UrlConstants.FTP_SCHEME, UrlConstants.INLINE_SCHEME,
29                     UrlConstants.JAVASCRIPT_SCHEME, UrlConstants.CHROME_SCHEME);
30     /**
31      * URI schemes that ContentView can handle.
32      *
33      * Copied from UrlUtilities.java.  UrlUtilities uses a URI to check for schemes, which
34      * is more strict than Uri and causes the path stripping to fail.
35      *
36      * The following additions have been made: "chrome", "ftp".
37      */
38     private static final HashSet<String> ACCEPTED_SCHEMES = CollectionUtil.newHashSet(
39             ContentUrlConstants.ABOUT_SCHEME, UrlConstants.DATA_SCHEME, UrlConstants.FILE_SCHEME,
40             UrlConstants.FTP_SCHEME, UrlConstants.HTTP_SCHEME, UrlConstants.HTTPS_SCHEME,
41             UrlConstants.INLINE_SCHEME, UrlConstants.JAVASCRIPT_SCHEME, UrlConstants.CHROME_SCHEME);
42     // Unicode "Left-To-Right Mark" (LRM) character.
43     private static final char LRM = '\u200E';
44 
45     /**
46      * Represents an empty URL bar.
47      */
48     public static final UrlBarData EMPTY = forNonUrlText("");
49 
forUrl(String url)50     public static UrlBarData forUrl(String url) {
51         return forUrlAndText(url, url, null);
52     }
53 
forNonUrlText(String displayText)54     public static UrlBarData forNonUrlText(String displayText) {
55         return create(null, displayText, 0, 0, null);
56     }
57 
forUrlAndText(String url, String displayText)58     public static UrlBarData forUrlAndText(String url, String displayText) {
59         return forUrlAndText(url, displayText, null);
60     }
61 
forUrlAndText( String url, CharSequence displayText, @Nullable String editingText)62     public static UrlBarData forUrlAndText(
63             String url, CharSequence displayText, @Nullable String editingText) {
64         int pathSearchOffset = 0;
65         String displayTextStr = displayText.toString();
66         String scheme = Uri.parse(displayTextStr).getScheme();
67 
68         if (!TextUtils.isEmpty(scheme)) {
69             if (UNSUPPORTED_SCHEMES_TO_SPLIT.contains(scheme)) {
70                 return create(url, displayText, 0, displayText.length(), editingText);
71             }
72             if (UrlConstants.BLOB_SCHEME.equals(scheme)) {
73                 int innerSchemeSearchOffset =
74                         findFirstIndexAfterSchemeSeparator(displayText, scheme.length());
75                 Uri innerUri = Uri.parse(displayTextStr.substring(innerSchemeSearchOffset));
76                 String innerScheme = innerUri.getScheme();
77                 // Substitute the scheme to allow for proper display of end of inner origin.
78                 if (!TextUtils.isEmpty(innerScheme)) {
79                     scheme = innerScheme;
80                 }
81             }
82             if (ACCEPTED_SCHEMES.contains(scheme)) {
83                 pathSearchOffset = findFirstIndexAfterSchemeSeparator(
84                         displayText, displayTextStr.indexOf(scheme) + scheme.length());
85             }
86         }
87         int pathOffset = -1;
88         if (pathSearchOffset < displayText.length()) {
89             pathOffset = displayTextStr.indexOf('/', pathSearchOffset);
90         }
91         if (pathOffset == -1) return create(url, displayText, 0, displayText.length(), editingText);
92 
93         // If the '/' is the last character and the beginning of the path, then just drop
94         // the path entirely.
95         if (pathOffset == displayText.length() - 1) {
96             return create(
97                     url, displayTextStr.subSequence(0, pathOffset), 0, pathOffset, editingText);
98         }
99 
100         return create(url, displayText, 0, pathOffset, editingText);
101     }
102 
create(@ullable String url, CharSequence displayText, int originStartIndex, int originEndIndex, @Nullable String editingText)103     public static UrlBarData create(@Nullable String url, CharSequence displayText,
104             int originStartIndex, int originEndIndex, @Nullable String editingText) {
105         return new UrlBarData(url, displayText, originStartIndex, originEndIndex, editingText);
106     }
107 
findFirstIndexAfterSchemeSeparator( CharSequence input, int searchStartIndex)108     private static int findFirstIndexAfterSchemeSeparator(
109             CharSequence input, int searchStartIndex) {
110         for (int index = searchStartIndex; index < input.length(); index++) {
111             char c = input.charAt(index);
112             if (c != ':' && c != '/') return index;
113         }
114         return input.length();
115     }
116 
117     /**
118      * The canonical URL that is shown in the URL bar, or null if it currently does not correspond
119      * to a URL (for example, showing suggestion text).
120      */
121     public final @Nullable String url;
122 
123     /**
124      * The text that should be shown in the URL bar. This can be a {@link Spanned} that contains
125      * formatting to highlight parts of the display text.
126      */
127     public final CharSequence displayText;
128 
129     /**
130      * The text that should replace the display text when editing the contents of the URL bar,
131      * or null to use the {@link #displayText} when editing.
132      */
133     public final @Nullable String editingText;
134 
135     /**
136      * The character index in {@link #displayText} where the origin starts. This is required to
137      * ensure that the end of the origin is not scrolled out of view for long hostnames.
138      */
139     public final int originStartIndex;
140 
141     /**
142      * The character index in {@link #displayText} where the origin ends. This is required to
143      * ensure that the end of the origin is not scrolled out of view for long hostnames.
144      */
145     public final int originEndIndex;
146 
147     /**
148      * @return The text for editing, falling back to the display text if the former is null.
149      */
getEditingOrDisplayText()150     public CharSequence getEditingOrDisplayText() {
151         return editingText != null ? editingText : displayText;
152     }
153 
UrlBarData(@ullable String url, CharSequence displayText, int originStartIndex, int originEndIndex, @Nullable String editingText)154     private UrlBarData(@Nullable String url, CharSequence displayText, int originStartIndex,
155             int originEndIndex, @Nullable String editingText) {
156         this.url = url;
157         this.displayText = displayText;
158         this.originStartIndex = originStartIndex;
159         this.originEndIndex = originEndIndex;
160         this.editingText = editingText;
161     }
162 }
163