mobile/android/base/Tab.java

branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
equal deleted inserted replaced
-1:000000000000 0:2cea6ed81a6e
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 package org.mozilla.gecko;
7
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.HashMap;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13
14 import org.json.JSONException;
15 import org.json.JSONObject;
16 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
17 import org.mozilla.gecko.db.BrowserDB;
18 import org.mozilla.gecko.gfx.Layer;
19 import org.mozilla.gecko.util.ThreadUtils;
20
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.graphics.Bitmap;
24 import android.graphics.Color;
25 import android.graphics.drawable.BitmapDrawable;
26 import android.os.Build;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import android.view.View;
30
31 public class Tab {
32 private static final String LOGTAG = "GeckoTab";
33
34 private static Pattern sColorPattern;
35 private final int mId;
36 private long mLastUsed;
37 private String mUrl;
38 private String mBaseDomain;
39 private String mUserSearch;
40 private String mTitle;
41 private Bitmap mFavicon;
42 private String mFaviconUrl;
43 private int mFaviconSize;
44 private boolean mHasFeeds;
45 private boolean mHasOpenSearch;
46 private SiteIdentity mSiteIdentity;
47 private boolean mReaderEnabled;
48 private BitmapDrawable mThumbnail;
49 private int mHistoryIndex;
50 private int mHistorySize;
51 private int mParentId;
52 private boolean mExternal;
53 private boolean mBookmark;
54 private boolean mReadingListItem;
55 private int mFaviconLoadId;
56 private String mContentType;
57 private boolean mHasTouchListeners;
58 private ZoomConstraints mZoomConstraints;
59 private boolean mIsRTL;
60 private ArrayList<View> mPluginViews;
61 private HashMap<Object, Layer> mPluginLayers;
62 private int mBackgroundColor;
63 private int mState;
64 private Bitmap mThumbnailBitmap;
65 private boolean mDesktopMode;
66 private boolean mEnteringReaderMode;
67 private Context mAppContext;
68 private ErrorType mErrorType = ErrorType.NONE;
69 private static final int MAX_HISTORY_LIST_SIZE = 50;
70 private volatile int mLoadProgress;
71 private volatile int mRecordingCount = 0;
72 private String mMostRecentHomePanel;
73
74 public static final int STATE_DELAYED = 0;
75 public static final int STATE_LOADING = 1;
76 public static final int STATE_SUCCESS = 2;
77 public static final int STATE_ERROR = 3;
78
79 public static final int LOAD_PROGRESS_INIT = 10;
80 public static final int LOAD_PROGRESS_START = 20;
81 public static final int LOAD_PROGRESS_LOCATION_CHANGE = 60;
82 public static final int LOAD_PROGRESS_LOADED = 80;
83 public static final int LOAD_PROGRESS_STOP = 100;
84
85 private static final int DEFAULT_BACKGROUND_COLOR = Color.WHITE;
86
87 public enum ErrorType {
88 CERT_ERROR, // Pages with certificate problems
89 BLOCKED, // Pages blocked for phishing or malware warnings
90 NET_ERROR, // All other types of error
91 NONE // Non error pages
92 }
93
94 public Tab(Context context, int id, String url, boolean external, int parentId, String title) {
95 mAppContext = context.getApplicationContext();
96 mId = id;
97 mLastUsed = 0;
98 mUrl = url;
99 mBaseDomain = "";
100 mUserSearch = "";
101 mExternal = external;
102 mParentId = parentId;
103 mTitle = title == null ? "" : title;
104 mFavicon = null;
105 mFaviconUrl = null;
106 mFaviconSize = 0;
107 mHasFeeds = false;
108 mHasOpenSearch = false;
109 mSiteIdentity = new SiteIdentity();
110 mReaderEnabled = false;
111 mEnteringReaderMode = false;
112 mThumbnail = null;
113 mHistoryIndex = -1;
114 mHistorySize = 0;
115 mBookmark = false;
116 mReadingListItem = false;
117 mFaviconLoadId = 0;
118 mContentType = "";
119 mZoomConstraints = new ZoomConstraints(false);
120 mPluginViews = new ArrayList<View>();
121 mPluginLayers = new HashMap<Object, Layer>();
122 mState = shouldShowProgress(url) ? STATE_LOADING : STATE_SUCCESS;
123 mLoadProgress = LOAD_PROGRESS_INIT;
124
125 // At startup, the background is set to a color specified by LayerView
126 // when the LayerView is created. Shortly after, this background color
127 // will be used before the tab's content is shown.
128 mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
129
130 updateBookmark();
131 }
132
133 private ContentResolver getContentResolver() {
134 return mAppContext.getContentResolver();
135 }
136
137 public void onDestroy() {
138 Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED);
139 }
140
141 public int getId() {
142 return mId;
143 }
144
145 public synchronized void onChange() {
146 mLastUsed = System.currentTimeMillis();
147 }
148
149 public synchronized long getLastUsed() {
150 return mLastUsed;
151 }
152
153 public int getParentId() {
154 return mParentId;
155 }
156
157 // may be null if user-entered query hasn't yet been resolved to a URI
158 public synchronized String getURL() {
159 return mUrl;
160 }
161
162 // mUserSearch should never be null, but it may be an empty string
163 public synchronized String getUserSearch() {
164 return mUserSearch;
165 }
166
167 // mTitle should never be null, but it may be an empty string
168 public synchronized String getTitle() {
169 return mTitle;
170 }
171
172 public String getDisplayTitle() {
173 if (mTitle != null && mTitle.length() > 0) {
174 return mTitle;
175 }
176
177 return mUrl;
178 }
179
180 public String getBaseDomain() {
181 return mBaseDomain;
182 }
183
184 public Bitmap getFavicon() {
185 return mFavicon;
186 }
187
188 public BitmapDrawable getThumbnail() {
189 return mThumbnail;
190 }
191
192 public String getMostRecentHomePanel() {
193 return mMostRecentHomePanel;
194 }
195
196 public void setMostRecentHomePanel(String panelId) {
197 mMostRecentHomePanel = panelId;
198 }
199
200 public Bitmap getThumbnailBitmap(int width, int height) {
201 if (mThumbnailBitmap != null) {
202 // Bug 787318 - Honeycomb has a bug with bitmap caching, we can't
203 // reuse the bitmap there.
204 boolean honeycomb = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
205 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2);
206 boolean sizeChange = mThumbnailBitmap.getWidth() != width
207 || mThumbnailBitmap.getHeight() != height;
208 if (honeycomb || sizeChange) {
209 mThumbnailBitmap = null;
210 }
211 }
212
213 if (mThumbnailBitmap == null) {
214 Bitmap.Config config = (GeckoAppShell.getScreenDepth() == 24) ?
215 Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
216 mThumbnailBitmap = Bitmap.createBitmap(width, height, config);
217 }
218
219 return mThumbnailBitmap;
220 }
221
222 public void updateThumbnail(final Bitmap b) {
223 ThreadUtils.postToBackgroundThread(new Runnable() {
224 @Override
225 public void run() {
226 if (b != null) {
227 try {
228 mThumbnail = new BitmapDrawable(mAppContext.getResources(), b);
229 if (mState == Tab.STATE_SUCCESS)
230 saveThumbnailToDB();
231 } catch (OutOfMemoryError oom) {
232 Log.w(LOGTAG, "Unable to create/scale bitmap.", oom);
233 mThumbnail = null;
234 }
235 } else {
236 mThumbnail = null;
237 }
238
239 Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.THUMBNAIL);
240 }
241 });
242 }
243
244 public synchronized String getFaviconURL() {
245 return mFaviconUrl;
246 }
247
248 public boolean hasFeeds() {
249 return mHasFeeds;
250 }
251
252 public boolean hasOpenSearch() {
253 return mHasOpenSearch;
254 }
255
256 public SiteIdentity getSiteIdentity() {
257 return mSiteIdentity;
258 }
259
260 public boolean getReaderEnabled() {
261 return mReaderEnabled;
262 }
263
264 public boolean isBookmark() {
265 return mBookmark;
266 }
267
268 public boolean isReadingListItem() {
269 return mReadingListItem;
270 }
271
272 public boolean isExternal() {
273 return mExternal;
274 }
275
276 public synchronized void updateURL(String url) {
277 if (url != null && url.length() > 0) {
278 mUrl = url;
279 }
280 }
281
282 private synchronized void updateUserSearch(String userSearch) {
283 mUserSearch = userSearch;
284 }
285
286 public void setErrorType(String type) {
287 if ("blocked".equals(type))
288 setErrorType(ErrorType.BLOCKED);
289 else if ("certerror".equals(type))
290 setErrorType(ErrorType.CERT_ERROR);
291 else if ("neterror".equals(type))
292 setErrorType(ErrorType.NET_ERROR);
293 else
294 setErrorType(ErrorType.NONE);
295 }
296
297 public void setErrorType(ErrorType type) {
298 mErrorType = type;
299 }
300
301 public ErrorType getErrorType() {
302 return mErrorType;
303 }
304
305 public void setContentType(String contentType) {
306 mContentType = (contentType == null) ? "" : contentType;
307 }
308
309 public String getContentType() {
310 return mContentType;
311 }
312
313 public synchronized void updateTitle(String title) {
314 // Keep the title unchanged while entering reader mode.
315 if (mEnteringReaderMode) {
316 return;
317 }
318
319 // If there was a title, but it hasn't changed, do nothing.
320 if (mTitle != null &&
321 TextUtils.equals(mTitle, title)) {
322 return;
323 }
324
325 mTitle = (title == null ? "" : title);
326 Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.TITLE);
327 }
328
329 public void setState(int state) {
330 mState = state;
331
332 if (mState != Tab.STATE_LOADING)
333 mEnteringReaderMode = false;
334 }
335
336 public int getState() {
337 return mState;
338 }
339
340 public void setZoomConstraints(ZoomConstraints constraints) {
341 mZoomConstraints = constraints;
342 }
343
344 public ZoomConstraints getZoomConstraints() {
345 return mZoomConstraints;
346 }
347
348 public void setIsRTL(boolean aIsRTL) {
349 mIsRTL = aIsRTL;
350 }
351
352 public boolean getIsRTL() {
353 return mIsRTL;
354 }
355
356 public void setHasTouchListeners(boolean aValue) {
357 mHasTouchListeners = aValue;
358 }
359
360 public boolean getHasTouchListeners() {
361 return mHasTouchListeners;
362 }
363
364 public void setFaviconLoadId(int faviconLoadId) {
365 mFaviconLoadId = faviconLoadId;
366 }
367
368 public int getFaviconLoadId() {
369 return mFaviconLoadId;
370 }
371
372 /**
373 * Returns true if the favicon changed.
374 */
375 public boolean updateFavicon(Bitmap favicon) {
376 if (mFavicon == favicon) {
377 return false;
378 }
379 mFavicon = favicon;
380 return true;
381 }
382
383 public synchronized void updateFaviconURL(String faviconUrl, int size) {
384 // If we already have an "any" sized icon, don't update the icon.
385 if (mFaviconSize == -1)
386 return;
387
388 // Only update the favicon if it's bigger than the current favicon.
389 // We use -1 to represent icons with sizes="any".
390 if (size == -1 || size >= mFaviconSize) {
391 mFaviconUrl = faviconUrl;
392 mFaviconSize = size;
393 }
394 }
395
396 public synchronized void clearFavicon() {
397 // Keep the favicon unchanged while entering reader mode
398 if (mEnteringReaderMode)
399 return;
400
401 mFavicon = null;
402 mFaviconUrl = null;
403 mFaviconSize = 0;
404 }
405
406 public void setHasFeeds(boolean hasFeeds) {
407 mHasFeeds = hasFeeds;
408 }
409
410 public void setHasOpenSearch(boolean hasOpenSearch) {
411 mHasOpenSearch = hasOpenSearch;
412 }
413
414 public void updateIdentityData(JSONObject identityData) {
415 mSiteIdentity.update(identityData);
416 }
417
418 public void setReaderEnabled(boolean readerEnabled) {
419 mReaderEnabled = readerEnabled;
420 Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.MENU_UPDATED);
421 }
422
423 void updateBookmark() {
424 if (getURL() == null) {
425 return;
426 }
427
428 ThreadUtils.postToBackgroundThread(new Runnable() {
429 @Override
430 public void run() {
431 final String url = getURL();
432 if (url == null) {
433 return;
434 }
435
436 final int flags = BrowserDB.getItemFlags(getContentResolver(), url);
437 mBookmark = (flags & Bookmarks.FLAG_BOOKMARK) > 0;
438 mReadingListItem = (flags & Bookmarks.FLAG_READING) > 0;
439 Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.MENU_UPDATED);
440 }
441 });
442 }
443
444 public void addBookmark() {
445 ThreadUtils.postToBackgroundThread(new Runnable() {
446 @Override
447 public void run() {
448 String url = getURL();
449 if (url == null)
450 return;
451
452 BrowserDB.addBookmark(getContentResolver(), mTitle, url);
453 }
454 });
455 }
456
457 public void removeBookmark() {
458 ThreadUtils.postToBackgroundThread(new Runnable() {
459 @Override
460 public void run() {
461 String url = getURL();
462 if (url == null)
463 return;
464
465 BrowserDB.removeBookmarksWithURL(getContentResolver(), url);
466 }
467 });
468 }
469
470 public void addToReadingList() {
471 if (!mReaderEnabled)
472 return;
473
474 JSONObject json = new JSONObject();
475 try {
476 json.put("tabID", String.valueOf(getId()));
477 } catch (JSONException e) {
478 Log.e(LOGTAG, "JSON error - failing to add to reading list", e);
479 return;
480 }
481
482 GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString());
483 GeckoAppShell.sendEventToGecko(e);
484 }
485
486 public void toggleReaderMode() {
487 if (AboutPages.isAboutReader(mUrl)) {
488 Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlFromAboutReader(mUrl));
489 } else if (mReaderEnabled) {
490 mEnteringReaderMode = true;
491 Tabs.getInstance().loadUrl(ReaderModeUtils.getAboutReaderForUrl(mUrl, mId));
492 }
493 }
494
495 public boolean isEnteringReaderMode() {
496 return mEnteringReaderMode;
497 }
498
499 public void doReload() {
500 GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", "");
501 GeckoAppShell.sendEventToGecko(e);
502 }
503
504 // Our version of nsSHistory::GetCanGoBack
505 public boolean canDoBack() {
506 return mHistoryIndex > 0;
507 }
508
509 public boolean doBack() {
510 if (!canDoBack())
511 return false;
512
513 GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Back", "");
514 GeckoAppShell.sendEventToGecko(e);
515 return true;
516 }
517
518 public boolean showBackHistory() {
519 if (!canDoBack())
520 return false;
521 return this.showHistory(Math.max(mHistoryIndex - MAX_HISTORY_LIST_SIZE, 0), mHistoryIndex, mHistoryIndex);
522 }
523
524 public boolean showForwardHistory() {
525 if (!canDoForward())
526 return false;
527 return this.showHistory(mHistoryIndex, Math.min(mHistorySize - 1, mHistoryIndex + MAX_HISTORY_LIST_SIZE), mHistoryIndex);
528 }
529
530 public boolean showAllHistory() {
531 if (!canDoForward() && !canDoBack())
532 return false;
533
534 int min = mHistoryIndex - MAX_HISTORY_LIST_SIZE / 2;
535 int max = mHistoryIndex + MAX_HISTORY_LIST_SIZE / 2;
536 if (min < 0) {
537 max -= min;
538 }
539 if (max > mHistorySize - 1) {
540 min -= max - (mHistorySize - 1);
541 max = mHistorySize - 1;
542 }
543 min = Math.max(min, 0);
544
545 return this.showHistory(min, max, mHistoryIndex);
546 }
547
548 /**
549 * This method will show the history starting on fromIndex until toIndex of the history.
550 */
551 public boolean showHistory(int fromIndex, int toIndex, int selIndex) {
552 JSONObject json = new JSONObject();
553 try {
554 json.put("fromIndex", fromIndex);
555 json.put("toIndex", toIndex);
556 json.put("selIndex", selIndex);
557 } catch (JSONException e) {
558 Log.e(LOGTAG, "JSON error", e);
559 }
560 GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:ShowHistory", json.toString());
561 GeckoAppShell.sendEventToGecko(e);
562 return true;
563 }
564
565 public void doStop() {
566 GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Stop", "");
567 GeckoAppShell.sendEventToGecko(e);
568 }
569
570 // Our version of nsSHistory::GetCanGoForward
571 public boolean canDoForward() {
572 return mHistoryIndex < mHistorySize - 1;
573 }
574
575 public boolean doForward() {
576 if (!canDoForward())
577 return false;
578
579 GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Forward", "");
580 GeckoAppShell.sendEventToGecko(e);
581 return true;
582 }
583
584 void handleSessionHistoryMessage(String event, JSONObject message) throws JSONException {
585 if (event.equals("New")) {
586 final String url = message.getString("url");
587 mHistoryIndex++;
588 mHistorySize = mHistoryIndex + 1;
589 } else if (event.equals("Back")) {
590 if (!canDoBack()) {
591 Log.w(LOGTAG, "Received unexpected back notification");
592 return;
593 }
594 mHistoryIndex--;
595 } else if (event.equals("Forward")) {
596 if (!canDoForward()) {
597 Log.w(LOGTAG, "Received unexpected forward notification");
598 return;
599 }
600 mHistoryIndex++;
601 } else if (event.equals("Goto")) {
602 int index = message.getInt("index");
603 if (index < 0 || index >= mHistorySize) {
604 Log.w(LOGTAG, "Received unexpected history-goto notification");
605 return;
606 }
607 mHistoryIndex = index;
608 } else if (event.equals("Purge")) {
609 int numEntries = message.getInt("numEntries");
610 if (numEntries > mHistorySize) {
611 Log.w(LOGTAG, "Received unexpectedly large number of history entries to purge");
612 mHistoryIndex = -1;
613 mHistorySize = 0;
614 return;
615 }
616
617 mHistorySize -= numEntries;
618 mHistoryIndex -= numEntries;
619
620 // If we weren't at the last history entry, mHistoryIndex may have become too small
621 if (mHistoryIndex < -1)
622 mHistoryIndex = -1;
623 }
624 }
625
626 void handleLocationChange(JSONObject message) throws JSONException {
627 final String uri = message.getString("uri");
628 final String oldUrl = getURL();
629 final boolean sameDocument = message.getBoolean("sameDocument");
630 mEnteringReaderMode = ReaderModeUtils.isEnteringReaderMode(oldUrl, uri);
631
632 if (!TextUtils.equals(oldUrl, uri)) {
633 updateURL(uri);
634 updateBookmark();
635 if (!sameDocument) {
636 // We can unconditionally clear the favicon and title here: we
637 // already filtered both cases in which this was a (pseudo-)
638 // spurious location change, so we're definitely loading a new
639 // page.
640 clearFavicon();
641 updateTitle(null);
642 }
643 }
644
645 if (sameDocument) {
646 // We can get a location change event for the same document with an anchor tag
647 // Notify listeners so that buttons like back or forward will update themselves
648 Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl);
649 return;
650 }
651
652 setContentType(message.getString("contentType"));
653 updateUserSearch(message.getString("userSearch"));
654 mBaseDomain = message.optString("baseDomain");
655
656 setHasFeeds(false);
657 setHasOpenSearch(false);
658 updateIdentityData(null);
659 setReaderEnabled(false);
660 setZoomConstraints(new ZoomConstraints(true));
661 setHasTouchListeners(false);
662 setBackgroundColor(DEFAULT_BACKGROUND_COLOR);
663 setErrorType(ErrorType.NONE);
664 setLoadProgressIfLoading(LOAD_PROGRESS_LOCATION_CHANGE);
665
666 Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl);
667 }
668
669 private static boolean shouldShowProgress(final String url) {
670 return !AboutPages.isAboutPage(url);
671 }
672
673 void handleDocumentStart(boolean restoring, String url) {
674 setLoadProgress(LOAD_PROGRESS_START);
675 setState((!restoring && shouldShowProgress(url)) ? STATE_LOADING : STATE_SUCCESS);
676 updateIdentityData(null);
677 setReaderEnabled(false);
678 }
679
680 void handleDocumentStop(boolean success) {
681 setState(success ? STATE_SUCCESS : STATE_ERROR);
682
683 final String oldURL = getURL();
684 final Tab tab = this;
685 tab.setLoadProgress(LOAD_PROGRESS_STOP);
686 ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
687 @Override
688 public void run() {
689 // tab.getURL() may return null
690 if (!TextUtils.equals(oldURL, getURL()))
691 return;
692
693 ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab);
694 }
695 }, 500);
696 }
697
698 void handleContentLoaded() {
699 setLoadProgressIfLoading(LOAD_PROGRESS_LOADED);
700 }
701
702 protected void saveThumbnailToDB() {
703 final BitmapDrawable thumbnail = mThumbnail;
704 if (thumbnail == null) {
705 return;
706 }
707
708 try {
709 String url = getURL();
710 if (url == null) {
711 return;
712 }
713
714 BrowserDB.updateThumbnailForUrl(getContentResolver(), url, thumbnail);
715 } catch (Exception e) {
716 // ignore
717 }
718 }
719
720 public void addPluginView(View view) {
721 mPluginViews.add(view);
722 }
723
724 public void removePluginView(View view) {
725 mPluginViews.remove(view);
726 }
727
728 public View[] getPluginViews() {
729 return mPluginViews.toArray(new View[mPluginViews.size()]);
730 }
731
732 public void addPluginLayer(Object surfaceOrView, Layer layer) {
733 synchronized(mPluginLayers) {
734 mPluginLayers.put(surfaceOrView, layer);
735 }
736 }
737
738 public Layer getPluginLayer(Object surfaceOrView) {
739 synchronized(mPluginLayers) {
740 return mPluginLayers.get(surfaceOrView);
741 }
742 }
743
744 public Collection<Layer> getPluginLayers() {
745 synchronized(mPluginLayers) {
746 return new ArrayList<Layer>(mPluginLayers.values());
747 }
748 }
749
750 public Layer removePluginLayer(Object surfaceOrView) {
751 synchronized(mPluginLayers) {
752 return mPluginLayers.remove(surfaceOrView);
753 }
754 }
755
756 public int getBackgroundColor() {
757 return mBackgroundColor;
758 }
759
760 /** Sets a new color for the background. */
761 public void setBackgroundColor(int color) {
762 mBackgroundColor = color;
763 }
764
765 /** Parses and sets a new color for the background. */
766 public void setBackgroundColor(String newColor) {
767 setBackgroundColor(parseColorFromGecko(newColor));
768 }
769
770 // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
771 // cannot be parsed, returns white.
772 private static int parseColorFromGecko(String string) {
773 if (sColorPattern == null) {
774 sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
775 }
776
777 Matcher matcher = sColorPattern.matcher(string);
778 if (!matcher.matches()) {
779 return Color.WHITE;
780 }
781
782 int r = Integer.parseInt(matcher.group(1));
783 int g = Integer.parseInt(matcher.group(2));
784 int b = Integer.parseInt(matcher.group(3));
785 return Color.rgb(r, g, b);
786 }
787
788 public void setDesktopMode(boolean enabled) {
789 mDesktopMode = enabled;
790 }
791
792 public boolean getDesktopMode() {
793 return mDesktopMode;
794 }
795
796 public boolean isPrivate() {
797 return false;
798 }
799
800 /**
801 * Sets the tab load progress to the given percentage.
802 *
803 * @param progressPercentage Percentage to set progress to (0-100)
804 */
805 void setLoadProgress(int progressPercentage) {
806 mLoadProgress = progressPercentage;
807 }
808
809 /**
810 * Sets the tab load progress to the given percentage only if the tab is
811 * currently loading.
812 *
813 * about:neterror can trigger a STOP before other page load events (bug
814 * 976426), so any post-START events should make sure the page is loading
815 * before updating progress.
816 *
817 * @param progressPercentage Percentage to set progress to (0-100)
818 */
819 void setLoadProgressIfLoading(int progressPercentage) {
820 if (getState() == STATE_LOADING) {
821 setLoadProgress(progressPercentage);
822 }
823 }
824
825 /**
826 * Gets the tab load progress percentage.
827 *
828 * @return Current progress percentage
829 */
830 public int getLoadProgress() {
831 return mLoadProgress;
832 }
833
834 public void setRecording(boolean isRecording) {
835 if (isRecording) {
836 mRecordingCount++;
837 } else {
838 mRecordingCount--;
839 }
840 }
841
842 public boolean isRecording() {
843 return mRecordingCount > 0;
844 }
845 }

mercurial