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