1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/Tab.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,845 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko; 1.10 + 1.11 +import java.util.ArrayList; 1.12 +import java.util.Collection; 1.13 +import java.util.HashMap; 1.14 +import java.util.regex.Matcher; 1.15 +import java.util.regex.Pattern; 1.16 + 1.17 +import org.json.JSONException; 1.18 +import org.json.JSONObject; 1.19 +import org.mozilla.gecko.db.BrowserContract.Bookmarks; 1.20 +import org.mozilla.gecko.db.BrowserDB; 1.21 +import org.mozilla.gecko.gfx.Layer; 1.22 +import org.mozilla.gecko.util.ThreadUtils; 1.23 + 1.24 +import android.content.ContentResolver; 1.25 +import android.content.Context; 1.26 +import android.graphics.Bitmap; 1.27 +import android.graphics.Color; 1.28 +import android.graphics.drawable.BitmapDrawable; 1.29 +import android.os.Build; 1.30 +import android.text.TextUtils; 1.31 +import android.util.Log; 1.32 +import android.view.View; 1.33 + 1.34 +public class Tab { 1.35 + private static final String LOGTAG = "GeckoTab"; 1.36 + 1.37 + private static Pattern sColorPattern; 1.38 + private final int mId; 1.39 + private long mLastUsed; 1.40 + private String mUrl; 1.41 + private String mBaseDomain; 1.42 + private String mUserSearch; 1.43 + private String mTitle; 1.44 + private Bitmap mFavicon; 1.45 + private String mFaviconUrl; 1.46 + private int mFaviconSize; 1.47 + private boolean mHasFeeds; 1.48 + private boolean mHasOpenSearch; 1.49 + private SiteIdentity mSiteIdentity; 1.50 + private boolean mReaderEnabled; 1.51 + private BitmapDrawable mThumbnail; 1.52 + private int mHistoryIndex; 1.53 + private int mHistorySize; 1.54 + private int mParentId; 1.55 + private boolean mExternal; 1.56 + private boolean mBookmark; 1.57 + private boolean mReadingListItem; 1.58 + private int mFaviconLoadId; 1.59 + private String mContentType; 1.60 + private boolean mHasTouchListeners; 1.61 + private ZoomConstraints mZoomConstraints; 1.62 + private boolean mIsRTL; 1.63 + private ArrayList<View> mPluginViews; 1.64 + private HashMap<Object, Layer> mPluginLayers; 1.65 + private int mBackgroundColor; 1.66 + private int mState; 1.67 + private Bitmap mThumbnailBitmap; 1.68 + private boolean mDesktopMode; 1.69 + private boolean mEnteringReaderMode; 1.70 + private Context mAppContext; 1.71 + private ErrorType mErrorType = ErrorType.NONE; 1.72 + private static final int MAX_HISTORY_LIST_SIZE = 50; 1.73 + private volatile int mLoadProgress; 1.74 + private volatile int mRecordingCount = 0; 1.75 + private String mMostRecentHomePanel; 1.76 + 1.77 + public static final int STATE_DELAYED = 0; 1.78 + public static final int STATE_LOADING = 1; 1.79 + public static final int STATE_SUCCESS = 2; 1.80 + public static final int STATE_ERROR = 3; 1.81 + 1.82 + public static final int LOAD_PROGRESS_INIT = 10; 1.83 + public static final int LOAD_PROGRESS_START = 20; 1.84 + public static final int LOAD_PROGRESS_LOCATION_CHANGE = 60; 1.85 + public static final int LOAD_PROGRESS_LOADED = 80; 1.86 + public static final int LOAD_PROGRESS_STOP = 100; 1.87 + 1.88 + private static final int DEFAULT_BACKGROUND_COLOR = Color.WHITE; 1.89 + 1.90 + public enum ErrorType { 1.91 + CERT_ERROR, // Pages with certificate problems 1.92 + BLOCKED, // Pages blocked for phishing or malware warnings 1.93 + NET_ERROR, // All other types of error 1.94 + NONE // Non error pages 1.95 + } 1.96 + 1.97 + public Tab(Context context, int id, String url, boolean external, int parentId, String title) { 1.98 + mAppContext = context.getApplicationContext(); 1.99 + mId = id; 1.100 + mLastUsed = 0; 1.101 + mUrl = url; 1.102 + mBaseDomain = ""; 1.103 + mUserSearch = ""; 1.104 + mExternal = external; 1.105 + mParentId = parentId; 1.106 + mTitle = title == null ? "" : title; 1.107 + mFavicon = null; 1.108 + mFaviconUrl = null; 1.109 + mFaviconSize = 0; 1.110 + mHasFeeds = false; 1.111 + mHasOpenSearch = false; 1.112 + mSiteIdentity = new SiteIdentity(); 1.113 + mReaderEnabled = false; 1.114 + mEnteringReaderMode = false; 1.115 + mThumbnail = null; 1.116 + mHistoryIndex = -1; 1.117 + mHistorySize = 0; 1.118 + mBookmark = false; 1.119 + mReadingListItem = false; 1.120 + mFaviconLoadId = 0; 1.121 + mContentType = ""; 1.122 + mZoomConstraints = new ZoomConstraints(false); 1.123 + mPluginViews = new ArrayList<View>(); 1.124 + mPluginLayers = new HashMap<Object, Layer>(); 1.125 + mState = shouldShowProgress(url) ? STATE_LOADING : STATE_SUCCESS; 1.126 + mLoadProgress = LOAD_PROGRESS_INIT; 1.127 + 1.128 + // At startup, the background is set to a color specified by LayerView 1.129 + // when the LayerView is created. Shortly after, this background color 1.130 + // will be used before the tab's content is shown. 1.131 + mBackgroundColor = DEFAULT_BACKGROUND_COLOR; 1.132 + 1.133 + updateBookmark(); 1.134 + } 1.135 + 1.136 + private ContentResolver getContentResolver() { 1.137 + return mAppContext.getContentResolver(); 1.138 + } 1.139 + 1.140 + public void onDestroy() { 1.141 + Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED); 1.142 + } 1.143 + 1.144 + public int getId() { 1.145 + return mId; 1.146 + } 1.147 + 1.148 + public synchronized void onChange() { 1.149 + mLastUsed = System.currentTimeMillis(); 1.150 + } 1.151 + 1.152 + public synchronized long getLastUsed() { 1.153 + return mLastUsed; 1.154 + } 1.155 + 1.156 + public int getParentId() { 1.157 + return mParentId; 1.158 + } 1.159 + 1.160 + // may be null if user-entered query hasn't yet been resolved to a URI 1.161 + public synchronized String getURL() { 1.162 + return mUrl; 1.163 + } 1.164 + 1.165 + // mUserSearch should never be null, but it may be an empty string 1.166 + public synchronized String getUserSearch() { 1.167 + return mUserSearch; 1.168 + } 1.169 + 1.170 + // mTitle should never be null, but it may be an empty string 1.171 + public synchronized String getTitle() { 1.172 + return mTitle; 1.173 + } 1.174 + 1.175 + public String getDisplayTitle() { 1.176 + if (mTitle != null && mTitle.length() > 0) { 1.177 + return mTitle; 1.178 + } 1.179 + 1.180 + return mUrl; 1.181 + } 1.182 + 1.183 + public String getBaseDomain() { 1.184 + return mBaseDomain; 1.185 + } 1.186 + 1.187 + public Bitmap getFavicon() { 1.188 + return mFavicon; 1.189 + } 1.190 + 1.191 + public BitmapDrawable getThumbnail() { 1.192 + return mThumbnail; 1.193 + } 1.194 + 1.195 + public String getMostRecentHomePanel() { 1.196 + return mMostRecentHomePanel; 1.197 + } 1.198 + 1.199 + public void setMostRecentHomePanel(String panelId) { 1.200 + mMostRecentHomePanel = panelId; 1.201 + } 1.202 + 1.203 + public Bitmap getThumbnailBitmap(int width, int height) { 1.204 + if (mThumbnailBitmap != null) { 1.205 + // Bug 787318 - Honeycomb has a bug with bitmap caching, we can't 1.206 + // reuse the bitmap there. 1.207 + boolean honeycomb = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB 1.208 + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2); 1.209 + boolean sizeChange = mThumbnailBitmap.getWidth() != width 1.210 + || mThumbnailBitmap.getHeight() != height; 1.211 + if (honeycomb || sizeChange) { 1.212 + mThumbnailBitmap = null; 1.213 + } 1.214 + } 1.215 + 1.216 + if (mThumbnailBitmap == null) { 1.217 + Bitmap.Config config = (GeckoAppShell.getScreenDepth() == 24) ? 1.218 + Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; 1.219 + mThumbnailBitmap = Bitmap.createBitmap(width, height, config); 1.220 + } 1.221 + 1.222 + return mThumbnailBitmap; 1.223 + } 1.224 + 1.225 + public void updateThumbnail(final Bitmap b) { 1.226 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.227 + @Override 1.228 + public void run() { 1.229 + if (b != null) { 1.230 + try { 1.231 + mThumbnail = new BitmapDrawable(mAppContext.getResources(), b); 1.232 + if (mState == Tab.STATE_SUCCESS) 1.233 + saveThumbnailToDB(); 1.234 + } catch (OutOfMemoryError oom) { 1.235 + Log.w(LOGTAG, "Unable to create/scale bitmap.", oom); 1.236 + mThumbnail = null; 1.237 + } 1.238 + } else { 1.239 + mThumbnail = null; 1.240 + } 1.241 + 1.242 + Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.THUMBNAIL); 1.243 + } 1.244 + }); 1.245 + } 1.246 + 1.247 + public synchronized String getFaviconURL() { 1.248 + return mFaviconUrl; 1.249 + } 1.250 + 1.251 + public boolean hasFeeds() { 1.252 + return mHasFeeds; 1.253 + } 1.254 + 1.255 + public boolean hasOpenSearch() { 1.256 + return mHasOpenSearch; 1.257 + } 1.258 + 1.259 + public SiteIdentity getSiteIdentity() { 1.260 + return mSiteIdentity; 1.261 + } 1.262 + 1.263 + public boolean getReaderEnabled() { 1.264 + return mReaderEnabled; 1.265 + } 1.266 + 1.267 + public boolean isBookmark() { 1.268 + return mBookmark; 1.269 + } 1.270 + 1.271 + public boolean isReadingListItem() { 1.272 + return mReadingListItem; 1.273 + } 1.274 + 1.275 + public boolean isExternal() { 1.276 + return mExternal; 1.277 + } 1.278 + 1.279 + public synchronized void updateURL(String url) { 1.280 + if (url != null && url.length() > 0) { 1.281 + mUrl = url; 1.282 + } 1.283 + } 1.284 + 1.285 + private synchronized void updateUserSearch(String userSearch) { 1.286 + mUserSearch = userSearch; 1.287 + } 1.288 + 1.289 + public void setErrorType(String type) { 1.290 + if ("blocked".equals(type)) 1.291 + setErrorType(ErrorType.BLOCKED); 1.292 + else if ("certerror".equals(type)) 1.293 + setErrorType(ErrorType.CERT_ERROR); 1.294 + else if ("neterror".equals(type)) 1.295 + setErrorType(ErrorType.NET_ERROR); 1.296 + else 1.297 + setErrorType(ErrorType.NONE); 1.298 + } 1.299 + 1.300 + public void setErrorType(ErrorType type) { 1.301 + mErrorType = type; 1.302 + } 1.303 + 1.304 + public ErrorType getErrorType() { 1.305 + return mErrorType; 1.306 + } 1.307 + 1.308 + public void setContentType(String contentType) { 1.309 + mContentType = (contentType == null) ? "" : contentType; 1.310 + } 1.311 + 1.312 + public String getContentType() { 1.313 + return mContentType; 1.314 + } 1.315 + 1.316 + public synchronized void updateTitle(String title) { 1.317 + // Keep the title unchanged while entering reader mode. 1.318 + if (mEnteringReaderMode) { 1.319 + return; 1.320 + } 1.321 + 1.322 + // If there was a title, but it hasn't changed, do nothing. 1.323 + if (mTitle != null && 1.324 + TextUtils.equals(mTitle, title)) { 1.325 + return; 1.326 + } 1.327 + 1.328 + mTitle = (title == null ? "" : title); 1.329 + Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.TITLE); 1.330 + } 1.331 + 1.332 + public void setState(int state) { 1.333 + mState = state; 1.334 + 1.335 + if (mState != Tab.STATE_LOADING) 1.336 + mEnteringReaderMode = false; 1.337 + } 1.338 + 1.339 + public int getState() { 1.340 + return mState; 1.341 + } 1.342 + 1.343 + public void setZoomConstraints(ZoomConstraints constraints) { 1.344 + mZoomConstraints = constraints; 1.345 + } 1.346 + 1.347 + public ZoomConstraints getZoomConstraints() { 1.348 + return mZoomConstraints; 1.349 + } 1.350 + 1.351 + public void setIsRTL(boolean aIsRTL) { 1.352 + mIsRTL = aIsRTL; 1.353 + } 1.354 + 1.355 + public boolean getIsRTL() { 1.356 + return mIsRTL; 1.357 + } 1.358 + 1.359 + public void setHasTouchListeners(boolean aValue) { 1.360 + mHasTouchListeners = aValue; 1.361 + } 1.362 + 1.363 + public boolean getHasTouchListeners() { 1.364 + return mHasTouchListeners; 1.365 + } 1.366 + 1.367 + public void setFaviconLoadId(int faviconLoadId) { 1.368 + mFaviconLoadId = faviconLoadId; 1.369 + } 1.370 + 1.371 + public int getFaviconLoadId() { 1.372 + return mFaviconLoadId; 1.373 + } 1.374 + 1.375 + /** 1.376 + * Returns true if the favicon changed. 1.377 + */ 1.378 + public boolean updateFavicon(Bitmap favicon) { 1.379 + if (mFavicon == favicon) { 1.380 + return false; 1.381 + } 1.382 + mFavicon = favicon; 1.383 + return true; 1.384 + } 1.385 + 1.386 + public synchronized void updateFaviconURL(String faviconUrl, int size) { 1.387 + // If we already have an "any" sized icon, don't update the icon. 1.388 + if (mFaviconSize == -1) 1.389 + return; 1.390 + 1.391 + // Only update the favicon if it's bigger than the current favicon. 1.392 + // We use -1 to represent icons with sizes="any". 1.393 + if (size == -1 || size >= mFaviconSize) { 1.394 + mFaviconUrl = faviconUrl; 1.395 + mFaviconSize = size; 1.396 + } 1.397 + } 1.398 + 1.399 + public synchronized void clearFavicon() { 1.400 + // Keep the favicon unchanged while entering reader mode 1.401 + if (mEnteringReaderMode) 1.402 + return; 1.403 + 1.404 + mFavicon = null; 1.405 + mFaviconUrl = null; 1.406 + mFaviconSize = 0; 1.407 + } 1.408 + 1.409 + public void setHasFeeds(boolean hasFeeds) { 1.410 + mHasFeeds = hasFeeds; 1.411 + } 1.412 + 1.413 + public void setHasOpenSearch(boolean hasOpenSearch) { 1.414 + mHasOpenSearch = hasOpenSearch; 1.415 + } 1.416 + 1.417 + public void updateIdentityData(JSONObject identityData) { 1.418 + mSiteIdentity.update(identityData); 1.419 + } 1.420 + 1.421 + public void setReaderEnabled(boolean readerEnabled) { 1.422 + mReaderEnabled = readerEnabled; 1.423 + Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.MENU_UPDATED); 1.424 + } 1.425 + 1.426 + void updateBookmark() { 1.427 + if (getURL() == null) { 1.428 + return; 1.429 + } 1.430 + 1.431 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.432 + @Override 1.433 + public void run() { 1.434 + final String url = getURL(); 1.435 + if (url == null) { 1.436 + return; 1.437 + } 1.438 + 1.439 + final int flags = BrowserDB.getItemFlags(getContentResolver(), url); 1.440 + mBookmark = (flags & Bookmarks.FLAG_BOOKMARK) > 0; 1.441 + mReadingListItem = (flags & Bookmarks.FLAG_READING) > 0; 1.442 + Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.MENU_UPDATED); 1.443 + } 1.444 + }); 1.445 + } 1.446 + 1.447 + public void addBookmark() { 1.448 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.449 + @Override 1.450 + public void run() { 1.451 + String url = getURL(); 1.452 + if (url == null) 1.453 + return; 1.454 + 1.455 + BrowserDB.addBookmark(getContentResolver(), mTitle, url); 1.456 + } 1.457 + }); 1.458 + } 1.459 + 1.460 + public void removeBookmark() { 1.461 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.462 + @Override 1.463 + public void run() { 1.464 + String url = getURL(); 1.465 + if (url == null) 1.466 + return; 1.467 + 1.468 + BrowserDB.removeBookmarksWithURL(getContentResolver(), url); 1.469 + } 1.470 + }); 1.471 + } 1.472 + 1.473 + public void addToReadingList() { 1.474 + if (!mReaderEnabled) 1.475 + return; 1.476 + 1.477 + JSONObject json = new JSONObject(); 1.478 + try { 1.479 + json.put("tabID", String.valueOf(getId())); 1.480 + } catch (JSONException e) { 1.481 + Log.e(LOGTAG, "JSON error - failing to add to reading list", e); 1.482 + return; 1.483 + } 1.484 + 1.485 + GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString()); 1.486 + GeckoAppShell.sendEventToGecko(e); 1.487 + } 1.488 + 1.489 + public void toggleReaderMode() { 1.490 + if (AboutPages.isAboutReader(mUrl)) { 1.491 + Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlFromAboutReader(mUrl)); 1.492 + } else if (mReaderEnabled) { 1.493 + mEnteringReaderMode = true; 1.494 + Tabs.getInstance().loadUrl(ReaderModeUtils.getAboutReaderForUrl(mUrl, mId)); 1.495 + } 1.496 + } 1.497 + 1.498 + public boolean isEnteringReaderMode() { 1.499 + return mEnteringReaderMode; 1.500 + } 1.501 + 1.502 + public void doReload() { 1.503 + GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", ""); 1.504 + GeckoAppShell.sendEventToGecko(e); 1.505 + } 1.506 + 1.507 + // Our version of nsSHistory::GetCanGoBack 1.508 + public boolean canDoBack() { 1.509 + return mHistoryIndex > 0; 1.510 + } 1.511 + 1.512 + public boolean doBack() { 1.513 + if (!canDoBack()) 1.514 + return false; 1.515 + 1.516 + GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Back", ""); 1.517 + GeckoAppShell.sendEventToGecko(e); 1.518 + return true; 1.519 + } 1.520 + 1.521 + public boolean showBackHistory() { 1.522 + if (!canDoBack()) 1.523 + return false; 1.524 + return this.showHistory(Math.max(mHistoryIndex - MAX_HISTORY_LIST_SIZE, 0), mHistoryIndex, mHistoryIndex); 1.525 + } 1.526 + 1.527 + public boolean showForwardHistory() { 1.528 + if (!canDoForward()) 1.529 + return false; 1.530 + return this.showHistory(mHistoryIndex, Math.min(mHistorySize - 1, mHistoryIndex + MAX_HISTORY_LIST_SIZE), mHistoryIndex); 1.531 + } 1.532 + 1.533 + public boolean showAllHistory() { 1.534 + if (!canDoForward() && !canDoBack()) 1.535 + return false; 1.536 + 1.537 + int min = mHistoryIndex - MAX_HISTORY_LIST_SIZE / 2; 1.538 + int max = mHistoryIndex + MAX_HISTORY_LIST_SIZE / 2; 1.539 + if (min < 0) { 1.540 + max -= min; 1.541 + } 1.542 + if (max > mHistorySize - 1) { 1.543 + min -= max - (mHistorySize - 1); 1.544 + max = mHistorySize - 1; 1.545 + } 1.546 + min = Math.max(min, 0); 1.547 + 1.548 + return this.showHistory(min, max, mHistoryIndex); 1.549 + } 1.550 + 1.551 + /** 1.552 + * This method will show the history starting on fromIndex until toIndex of the history. 1.553 + */ 1.554 + public boolean showHistory(int fromIndex, int toIndex, int selIndex) { 1.555 + JSONObject json = new JSONObject(); 1.556 + try { 1.557 + json.put("fromIndex", fromIndex); 1.558 + json.put("toIndex", toIndex); 1.559 + json.put("selIndex", selIndex); 1.560 + } catch (JSONException e) { 1.561 + Log.e(LOGTAG, "JSON error", e); 1.562 + } 1.563 + GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:ShowHistory", json.toString()); 1.564 + GeckoAppShell.sendEventToGecko(e); 1.565 + return true; 1.566 + } 1.567 + 1.568 + public void doStop() { 1.569 + GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Stop", ""); 1.570 + GeckoAppShell.sendEventToGecko(e); 1.571 + } 1.572 + 1.573 + // Our version of nsSHistory::GetCanGoForward 1.574 + public boolean canDoForward() { 1.575 + return mHistoryIndex < mHistorySize - 1; 1.576 + } 1.577 + 1.578 + public boolean doForward() { 1.579 + if (!canDoForward()) 1.580 + return false; 1.581 + 1.582 + GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Forward", ""); 1.583 + GeckoAppShell.sendEventToGecko(e); 1.584 + return true; 1.585 + } 1.586 + 1.587 + void handleSessionHistoryMessage(String event, JSONObject message) throws JSONException { 1.588 + if (event.equals("New")) { 1.589 + final String url = message.getString("url"); 1.590 + mHistoryIndex++; 1.591 + mHistorySize = mHistoryIndex + 1; 1.592 + } else if (event.equals("Back")) { 1.593 + if (!canDoBack()) { 1.594 + Log.w(LOGTAG, "Received unexpected back notification"); 1.595 + return; 1.596 + } 1.597 + mHistoryIndex--; 1.598 + } else if (event.equals("Forward")) { 1.599 + if (!canDoForward()) { 1.600 + Log.w(LOGTAG, "Received unexpected forward notification"); 1.601 + return; 1.602 + } 1.603 + mHistoryIndex++; 1.604 + } else if (event.equals("Goto")) { 1.605 + int index = message.getInt("index"); 1.606 + if (index < 0 || index >= mHistorySize) { 1.607 + Log.w(LOGTAG, "Received unexpected history-goto notification"); 1.608 + return; 1.609 + } 1.610 + mHistoryIndex = index; 1.611 + } else if (event.equals("Purge")) { 1.612 + int numEntries = message.getInt("numEntries"); 1.613 + if (numEntries > mHistorySize) { 1.614 + Log.w(LOGTAG, "Received unexpectedly large number of history entries to purge"); 1.615 + mHistoryIndex = -1; 1.616 + mHistorySize = 0; 1.617 + return; 1.618 + } 1.619 + 1.620 + mHistorySize -= numEntries; 1.621 + mHistoryIndex -= numEntries; 1.622 + 1.623 + // If we weren't at the last history entry, mHistoryIndex may have become too small 1.624 + if (mHistoryIndex < -1) 1.625 + mHistoryIndex = -1; 1.626 + } 1.627 + } 1.628 + 1.629 + void handleLocationChange(JSONObject message) throws JSONException { 1.630 + final String uri = message.getString("uri"); 1.631 + final String oldUrl = getURL(); 1.632 + final boolean sameDocument = message.getBoolean("sameDocument"); 1.633 + mEnteringReaderMode = ReaderModeUtils.isEnteringReaderMode(oldUrl, uri); 1.634 + 1.635 + if (!TextUtils.equals(oldUrl, uri)) { 1.636 + updateURL(uri); 1.637 + updateBookmark(); 1.638 + if (!sameDocument) { 1.639 + // We can unconditionally clear the favicon and title here: we 1.640 + // already filtered both cases in which this was a (pseudo-) 1.641 + // spurious location change, so we're definitely loading a new 1.642 + // page. 1.643 + clearFavicon(); 1.644 + updateTitle(null); 1.645 + } 1.646 + } 1.647 + 1.648 + if (sameDocument) { 1.649 + // We can get a location change event for the same document with an anchor tag 1.650 + // Notify listeners so that buttons like back or forward will update themselves 1.651 + Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl); 1.652 + return; 1.653 + } 1.654 + 1.655 + setContentType(message.getString("contentType")); 1.656 + updateUserSearch(message.getString("userSearch")); 1.657 + mBaseDomain = message.optString("baseDomain"); 1.658 + 1.659 + setHasFeeds(false); 1.660 + setHasOpenSearch(false); 1.661 + updateIdentityData(null); 1.662 + setReaderEnabled(false); 1.663 + setZoomConstraints(new ZoomConstraints(true)); 1.664 + setHasTouchListeners(false); 1.665 + setBackgroundColor(DEFAULT_BACKGROUND_COLOR); 1.666 + setErrorType(ErrorType.NONE); 1.667 + setLoadProgressIfLoading(LOAD_PROGRESS_LOCATION_CHANGE); 1.668 + 1.669 + Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl); 1.670 + } 1.671 + 1.672 + private static boolean shouldShowProgress(final String url) { 1.673 + return !AboutPages.isAboutPage(url); 1.674 + } 1.675 + 1.676 + void handleDocumentStart(boolean restoring, String url) { 1.677 + setLoadProgress(LOAD_PROGRESS_START); 1.678 + setState((!restoring && shouldShowProgress(url)) ? STATE_LOADING : STATE_SUCCESS); 1.679 + updateIdentityData(null); 1.680 + setReaderEnabled(false); 1.681 + } 1.682 + 1.683 + void handleDocumentStop(boolean success) { 1.684 + setState(success ? STATE_SUCCESS : STATE_ERROR); 1.685 + 1.686 + final String oldURL = getURL(); 1.687 + final Tab tab = this; 1.688 + tab.setLoadProgress(LOAD_PROGRESS_STOP); 1.689 + ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() { 1.690 + @Override 1.691 + public void run() { 1.692 + // tab.getURL() may return null 1.693 + if (!TextUtils.equals(oldURL, getURL())) 1.694 + return; 1.695 + 1.696 + ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab); 1.697 + } 1.698 + }, 500); 1.699 + } 1.700 + 1.701 + void handleContentLoaded() { 1.702 + setLoadProgressIfLoading(LOAD_PROGRESS_LOADED); 1.703 + } 1.704 + 1.705 + protected void saveThumbnailToDB() { 1.706 + final BitmapDrawable thumbnail = mThumbnail; 1.707 + if (thumbnail == null) { 1.708 + return; 1.709 + } 1.710 + 1.711 + try { 1.712 + String url = getURL(); 1.713 + if (url == null) { 1.714 + return; 1.715 + } 1.716 + 1.717 + BrowserDB.updateThumbnailForUrl(getContentResolver(), url, thumbnail); 1.718 + } catch (Exception e) { 1.719 + // ignore 1.720 + } 1.721 + } 1.722 + 1.723 + public void addPluginView(View view) { 1.724 + mPluginViews.add(view); 1.725 + } 1.726 + 1.727 + public void removePluginView(View view) { 1.728 + mPluginViews.remove(view); 1.729 + } 1.730 + 1.731 + public View[] getPluginViews() { 1.732 + return mPluginViews.toArray(new View[mPluginViews.size()]); 1.733 + } 1.734 + 1.735 + public void addPluginLayer(Object surfaceOrView, Layer layer) { 1.736 + synchronized(mPluginLayers) { 1.737 + mPluginLayers.put(surfaceOrView, layer); 1.738 + } 1.739 + } 1.740 + 1.741 + public Layer getPluginLayer(Object surfaceOrView) { 1.742 + synchronized(mPluginLayers) { 1.743 + return mPluginLayers.get(surfaceOrView); 1.744 + } 1.745 + } 1.746 + 1.747 + public Collection<Layer> getPluginLayers() { 1.748 + synchronized(mPluginLayers) { 1.749 + return new ArrayList<Layer>(mPluginLayers.values()); 1.750 + } 1.751 + } 1.752 + 1.753 + public Layer removePluginLayer(Object surfaceOrView) { 1.754 + synchronized(mPluginLayers) { 1.755 + return mPluginLayers.remove(surfaceOrView); 1.756 + } 1.757 + } 1.758 + 1.759 + public int getBackgroundColor() { 1.760 + return mBackgroundColor; 1.761 + } 1.762 + 1.763 + /** Sets a new color for the background. */ 1.764 + public void setBackgroundColor(int color) { 1.765 + mBackgroundColor = color; 1.766 + } 1.767 + 1.768 + /** Parses and sets a new color for the background. */ 1.769 + public void setBackgroundColor(String newColor) { 1.770 + setBackgroundColor(parseColorFromGecko(newColor)); 1.771 + } 1.772 + 1.773 + // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color 1.774 + // cannot be parsed, returns white. 1.775 + private static int parseColorFromGecko(String string) { 1.776 + if (sColorPattern == null) { 1.777 + sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)"); 1.778 + } 1.779 + 1.780 + Matcher matcher = sColorPattern.matcher(string); 1.781 + if (!matcher.matches()) { 1.782 + return Color.WHITE; 1.783 + } 1.784 + 1.785 + int r = Integer.parseInt(matcher.group(1)); 1.786 + int g = Integer.parseInt(matcher.group(2)); 1.787 + int b = Integer.parseInt(matcher.group(3)); 1.788 + return Color.rgb(r, g, b); 1.789 + } 1.790 + 1.791 + public void setDesktopMode(boolean enabled) { 1.792 + mDesktopMode = enabled; 1.793 + } 1.794 + 1.795 + public boolean getDesktopMode() { 1.796 + return mDesktopMode; 1.797 + } 1.798 + 1.799 + public boolean isPrivate() { 1.800 + return false; 1.801 + } 1.802 + 1.803 + /** 1.804 + * Sets the tab load progress to the given percentage. 1.805 + * 1.806 + * @param progressPercentage Percentage to set progress to (0-100) 1.807 + */ 1.808 + void setLoadProgress(int progressPercentage) { 1.809 + mLoadProgress = progressPercentage; 1.810 + } 1.811 + 1.812 + /** 1.813 + * Sets the tab load progress to the given percentage only if the tab is 1.814 + * currently loading. 1.815 + * 1.816 + * about:neterror can trigger a STOP before other page load events (bug 1.817 + * 976426), so any post-START events should make sure the page is loading 1.818 + * before updating progress. 1.819 + * 1.820 + * @param progressPercentage Percentage to set progress to (0-100) 1.821 + */ 1.822 + void setLoadProgressIfLoading(int progressPercentage) { 1.823 + if (getState() == STATE_LOADING) { 1.824 + setLoadProgress(progressPercentage); 1.825 + } 1.826 + } 1.827 + 1.828 + /** 1.829 + * Gets the tab load progress percentage. 1.830 + * 1.831 + * @return Current progress percentage 1.832 + */ 1.833 + public int getLoadProgress() { 1.834 + return mLoadProgress; 1.835 + } 1.836 + 1.837 + public void setRecording(boolean isRecording) { 1.838 + if (isRecording) { 1.839 + mRecordingCount++; 1.840 + } else { 1.841 + mRecordingCount--; 1.842 + } 1.843 + } 1.844 + 1.845 + public boolean isRecording() { 1.846 + return mRecordingCount > 0; 1.847 + } 1.848 +}