|
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 } |