Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(300)

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/dom_distiller/ReaderModePanel.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.dom_distiller;
6
7 import static org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Ani matableAnimation.createAnimation;
8
9 import android.content.Context;
10
11 import org.chromium.base.metrics.RecordUserAction;
12 import org.chromium.chrome.browser.ContentViewUtil;
13 import org.chromium.chrome.browser.Tab;
14 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation;
15 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable ;
16 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeEvent Filter.ScrollDirection;
17 import org.chromium.chrome.browser.dom_distiller.ReaderModeButtonView.ReaderMode ButtonViewDelegate;
18 import org.chromium.chrome.browser.tab.ChromeTab;
19 import org.chromium.chrome.browser.util.MathUtils;
20 import org.chromium.content.browser.ContentView;
21 import org.chromium.content.browser.ContentViewCore;
22 import org.chromium.content_public.browser.WebContentsObserver;
23 import org.chromium.content_public.common.TopControlsState;
24 import org.chromium.ui.base.WindowAndroid;
25
26 /**
27 * Manages UI effects for reader mode including hiding and showing the
28 * reader mode and reader mode preferences toolbar icon and hiding the
29 * top controls when a reader mode page has finished loading.
30 *
31 * TODO(aruslan): combine with ContextualSearchPanel.
32 */
33 public class ReaderModePanel implements ChromeAnimation.Animatable<ReaderModePan el.Property> {
34 // TODO(aruslan): pull this from the FullscreenManager.
35 private static final float TOOLBAR_HEIGHT_DP = 56.0f;
36
37 private static final float PANEL_HEIGHT_DP = TOOLBAR_HEIGHT_DP;
38 private static final float SHADOW_HEIGHT_DP = 4.0f;
39 private static final float MINIMAL_BORDER_X_DP = 4.0f;
40 private static final float DARKEN_LAYOUTTAB_BRIGHTNESS = 0.3f;
41 private static final float MAX_LAYOUTTAB_DISPLACEMENT = 3.0f * TOOLBAR_HEIGH T_DP;
42
43 private static final float SNAP_BACK_THRESHOLD = 0.3f;
44 private static final long BASE_ANIMATION_DURATION_MS = 500;
45
46 /**
47 * Panel's host interface.
48 */
49 public interface ReaderModePanelHost {
50 /**
51 * @return Whether the reader mode button should be animated.
52 */
53 boolean allowReaderModeButtonAnimation();
54
55 /**
56 * @return Reader mode header background color.
57 */
58 int getReaderModeHeaderBackgroundColor();
59
60 /**
61 * @return One of ReaderModeManager.POSSIBLE, NOT_POSSIBLE, STARTED cons tants.
62 */
63 int getReaderModeStatus();
64
65 /**
66 * @return An associated tab.
67 */
68 Tab getTab();
69
70 /**
71 * @param X X-coordinate in dp
72 * @param Y Y-coordinate in dp
73 * @return Whether a given coordinates are within the bounds of the "dis miss" button
74 */
75 public boolean isInsideDismissButton(float x, float y);
76 }
77
78 /**
79 * Layout integration interface.
80 */
81 public interface ReaderModePanelLayoutDelegate {
82 /**
83 * Requests a next update to refresh the transforms and changing propert ies.
84 */
85 void requestUpdate();
86
87 /**
88 * Sets the brightness of the LayoutTab to a given value.
89 * @param v Brightness
90 */
91 void setLayoutTabBrightness(float v);
92
93 /**
94 * Sets the Y offset of the LayoutTab to a given value.
95 * @param v Y-offset in dp
96 */
97 void setLayoutTabY(float v);
98 }
99
100 /**
101 * Properties that can be animated by using a
102 * {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Ani matable}.
103 */
104 public enum Property {
105 /**
106 * Parametric vertical slider from
107 * -1.0 (panel is out of screen) to
108 * 0.0 (panel is on screen) to
109 * 1.0 (panel covers the entire screen)
110 */
111 SLIDING_T,
112 /**
113 * Horizontal slider, offset in dp
114 */
115 X,
116 }
117
118 private float mSlidingT;
119 private float mX;
120
121 private ScrollDirection mSwipeDirection; // set in swipeStarted
122 private float mInitialPanelDistanceFromBottom; // distance from the bottom at swipeStarted
123 private float mInitialX; // X at swipeStarted
124
125 /**
126 * The animation set.
127 */
128 private ChromeAnimation<ChromeAnimation.Animatable<?>> mLayoutAnimations;
129
130 private boolean mIsReaderModePanelHidden;
131 private boolean mIsReaderModePanelDismissed;
132 private ContentViewCore mDistilledContentViewCore;
133 private WebContentsObserver mDistilledContentObserver;
134 private boolean mDidFirstNonEmptyDistilledPaint;
135 private ReaderModePanelLayoutDelegate mLayoutDelegate;
136
137 private float mLayoutWidth;
138 private float mLayoutHeight;
139 private boolean mIsToolbarShowing;
140 private float mDpToPx;
141
142 /**
143 * The {@link ReaderModePanelHost} used to get reader mode status and the as sociated tab.
144 */
145 private final ReaderModePanelHost mReaderModeHost;
146
147 /**
148 * Non-animated button support.
149 */
150 private boolean mAllowAnimatedButton;
151 private ReaderModeButtonView mReaderModeButtonView;
152
153 public ReaderModePanel(ReaderModePanelHost readerModeHost) {
154 mReaderModeHost = readerModeHost;
155 mAllowAnimatedButton = mReaderModeHost.allowReaderModeButtonAnimation();
156
157 mLayoutWidth = 0.0f;
158 mLayoutHeight = 0.0f;
159 mDpToPx = 1.0f;
160
161 mSlidingT = -1.0f;
162 mX = 0.0f;
163 }
164
165 /**
166 * Destroys the panel and associated resources.
167 */
168 public void onDestroy() {
169 mLayoutAnimations = null;
170 hideButtonBar();
171 }
172
173 /**
174 * Set the layout delegate.
175 * @param layoutDelegate A {@link ReaderModePanelLayoutDelegate} to call.
176 */
177 public void setLayoutDelegate(ReaderModePanelLayoutDelegate layoutDelegate) {
178 mLayoutDelegate = layoutDelegate;
179 requestUpdate();
180 }
181
182 // ChromeAnimation.Animatable<Property>:
183
184 private void setSlidingT(float val) {
185 mSlidingT = val;
186 if (mLayoutDelegate != null) {
187 mLayoutDelegate.setLayoutTabBrightness(getTabBrightness());
188 mLayoutDelegate.setLayoutTabY(getTabYOffset());
189 }
190 }
191
192 @Override
193 public void setProperty(Property prop, float val) {
194 switch (prop) {
195 case SLIDING_T:
196 setSlidingT(val);
197 break;
198 case X:
199 mX = val;
200 break;
201 }
202 }
203
204 private static float clamp(float val, float lower, float higher) {
205 return val < lower ? lower : (val > higher ? higher : val);
206 }
207
208 private static float interp(float factor, float start, float end) {
209 return start + clamp(factor, 0.0f, 1.0f) * (end - start);
210 }
211
212 private float getPanelDistanceFromBottom() {
213 if (mSlidingT < 0.0f) return interp(mSlidingT + 1.0f, 0.0f, PANEL_HEIGHT _DP);
214 return PANEL_HEIGHT_DP + interp(mSlidingT, 0.0f, getFullscreenHeight());
215 }
216
217 private float getSlidingTForPanelDistanceFromBottom(float distanceFromBottom ) {
218 if (distanceFromBottom >= PANEL_HEIGHT_DP) {
219 return interp(
220 (distanceFromBottom - PANEL_HEIGHT_DP) / getFullscreenHeight (),
221 0.0f, 1.0f);
222 }
223 return interp(
224 (PANEL_HEIGHT_DP - distanceFromBottom) / PANEL_HEIGHT_DP,
225 0.0f, -1.0f);
226 }
227
228 private float getDistilledContentDistanceFromBottom() {
229 if (mSlidingT < 0.0f) return interp(mSlidingT + 1.0f, -PANEL_HEIGHT_DP, 0.0f);
230 return interp(mSlidingT, 0.0f, getFullscreenHeight());
231 }
232
233 private static float snapBackSlidingT(float v) {
234 // We snap asymmetrically: 30% is enough to get it opened, but 70% is ne cessary to dismiss.
235 v = (v < -1.0f + SNAP_BACK_THRESHOLD) ? v : (v >= SNAP_BACK_THRESHOLD ? v : 0.0f);
236 return Math.signum(v);
237 }
238
239 private static float snapBackX(float v) {
240 // Horizontally we snap symmetrically: more than 70% to each side to dis miss.
241 v = (v < -1.0f + SNAP_BACK_THRESHOLD) ? v : (v >= 1.0f - SNAP_BACK_THRES HOLD ? v : 0.0f);
242 return Math.signum(v);
243 }
244
245 // Gesture handling:
246
247 /**
248 * @param direction Swipe direction to test
249 * @return Whether the swipe in a given direction is enabled
250 */
251 public boolean isSwipeEnabled(ScrollDirection direction) {
252 return !isAnimating();
253 }
254
255 /**
256 * Called when the swipe is started.
257 * @param direction Swipe direction
258 * @param x X-coordinate of the starting point in dp
259 * @param y Y-coordinate of the starting point in dp
260 */
261 public void swipeStarted(ScrollDirection direction, float x, float y) {
262 if (isAnimating()) return;
263
264 mSwipeDirection = direction;
265 mInitialPanelDistanceFromBottom = getPanelDistanceFromBottom();
266 mX = getX();
267
268 if (mSwipeDirection == ScrollDirection.UP) activatePreviewOfDistilledMod e();
269
270 requestUpdate();
271 }
272
273 /**
274 * Called when the swipe is continued.
275 * @param tx X-offset since the start of the swipe in dp
276 * @param ty Y-offset since the start of the swipe in dp
277 */
278 public void swipeUpdated(float x, float y, float dx, float dy, float tx, flo at ty) {
279 if (isAnimating()) return;
280
281 if (mSwipeDirection == ScrollDirection.LEFT || mSwipeDirection == Scroll Direction.RIGHT) {
282 setProperty(ReaderModePanel.Property.X, clamp(mInitialX + tx,
283 -mLayoutWidth + MINIMAL_BORDER_X_DP, mLayoutWidth - MINIMAL_ BORDER_X_DP));
284 } else {
285 setProperty(ReaderModePanel.Property.SLIDING_T,
286 getSlidingTForPanelDistanceFromBottom(mInitialPanelDistanceF romBottom - ty));
287 }
288 requestUpdate();
289 }
290
291 /**
292 * Called when the swipe is finished.
293 */
294 public void swipeFinished() {
295 if (isAnimating()) return;
296
297 final float snappedX = snapBackX(mX / mLayoutWidth) * mLayoutWidth;
298 final float snappedSlidingT = snapBackSlidingT(mSlidingT);
299 if (snappedX <= -mLayoutWidth || snappedX >= mLayoutWidth) dismissButton Bar();
300 if (snappedSlidingT < 0.0f) dismissButtonBar();
301
302 animateTo(snappedX, snappedSlidingT, true);
303 }
304
305 // Panel layout handling:
306
307 /**
308 * @return Whether the panel should be shown.
309 */
310 public boolean isShowing() {
311 return isPanelWithinScreenBounds() || isAnimating() || mDistilledContent ViewCore != null;
312 }
313
314 /**
315 * @return Whether the panel is within screen bounds.
316 */
317 private boolean isPanelWithinScreenBounds() {
318 return mSlidingT > -1.0f;
319 }
320
321 /**
322 * @return The fullscreen height.
323 */
324 private float getFullscreenHeight() {
325 return mLayoutHeight + TOOLBAR_HEIGHT_DP;
326 }
327
328 public float getFullscreenY(float y) {
329 if (mIsToolbarShowing) y += TOOLBAR_HEIGHT_DP * mDpToPx;
330 return y;
331 }
332
333 public float getPanelY() {
334 return getFullscreenHeight() - getPanelDistanceFromBottom() - SHADOW_HEI GHT_DP;
335 }
336
337 public float getDistilledContentY() {
338 return getFullscreenHeight() - getDistilledContentDistanceFromBottom() - SHADOW_HEIGHT_DP;
339 }
340
341 public float getWidth() {
342 return mLayoutWidth;
343 }
344
345 public float getPanelHeight() {
346 return getPanelDistanceFromBottom();
347 }
348
349 public float getMarginTop() {
350 return SHADOW_HEIGHT_DP;
351 }
352
353 public float getDistilledHeight() {
354 return getDistilledContentDistanceFromBottom();
355 }
356
357 public float getX() {
358 return mX;
359 }
360
361 public float getTextOpacity() {
362 return interp(mSlidingT, 1.0f, 0.0f);
363 }
364
365 public float getTabBrightness() {
366 return interp(mSlidingT, 1.0f, DARKEN_LAYOUTTAB_BRIGHTNESS);
367 }
368
369 public float getTabYOffset() {
370 return interp(mSlidingT, 0.0f, -MAX_LAYOUTTAB_DISPLACEMENT);
371 }
372
373 /**
374 * @param currentOffset The current top controls offset in dp.
375 * @return {@link Float#NaN} if no offset should be used, or a value in dp
376 * if the top controls offset should be overridden.
377 */
378 public float getTopControlsOffset(float currentOffsetDp) {
379 if (mSlidingT <= 0.0f) return Float.NaN;
380 return MathUtils.clamp(getTabYOffset(), -TOOLBAR_HEIGHT_DP, Math.min(cur rentOffsetDp, 0f));
381 }
382
383
384 public ContentViewCore getDistilledContentViewCore() {
385 return mDistilledContentViewCore;
386 }
387
388 public boolean didFirstNonEmptyDistilledPaint() {
389 return mDidFirstNonEmptyDistilledPaint;
390 }
391
392 public int getReaderModeHeaderBackgroundColor() {
393 return mReaderModeHost.getReaderModeHeaderBackgroundColor();
394 }
395
396 /**
397 * Called when the size of the view has changed.
398 *
399 * @param width The new width in dp.
400 * @param height The new width in dp.
401 * @param isToolbarShowing Whether the Toolbar is showing.
402 * @param dpToPx Multipler to convert from dp to pixels.
403 */
404 public void onSizeChanged(float width, float height, boolean isToolbarShowin g, float dpToPx) {
405 mLayoutWidth = width;
406 mLayoutHeight = height;
407 mIsToolbarShowing = isToolbarShowing;
408 mDpToPx = dpToPx;
409 }
410
411 // Layout integration:
412
413 /**
414 * Requests a new frame to be updated and rendered.
415 */
416 private void requestUpdate() {
417 if (mLayoutDelegate != null) mLayoutDelegate.requestUpdate();
418 }
419
420 // Animation handling:
421
422 /**
423 * @return Whether a panel animation is in progress.
424 */
425 private boolean isAnimating() {
426 return mLayoutAnimations != null && !mLayoutAnimations.finished();
427 }
428
429 /**
430 * Animates to a given target value.
431 * @param targetX A target value for the X parameter
432 * @param targetSlidingT A target value for the SlidingT parameter
433 */
434 private void animateTo(float targetX, float targetSlidingT, boolean animate) {
435 if (targetSlidingT > 0.0f) activatePreviewOfDistilledMode();
436
437 if (isAnimating()) {
438 mLayoutAnimations.cancel(this, Property.SLIDING_T);
439 mLayoutAnimations.cancel(this, Property.X);
440 }
441 if (mLayoutAnimations == null || mLayoutAnimations.finished()) {
442 mLayoutAnimations = new ChromeAnimation<Animatable<?>>();
443 }
444
445 mLayoutAnimations.add(createAnimation(
446 this, Property.SLIDING_T, mSlidingT, targetSlidingT,
447 BASE_ANIMATION_DURATION_MS, 0, false,
448 ChromeAnimation.getDecelerateInterpolator()));
449 mLayoutAnimations.add(createAnimation(
450 this, Property.X, mX, targetX,
451 BASE_ANIMATION_DURATION_MS, 0, false,
452 ChromeAnimation.getDecelerateInterpolator()));
453 mLayoutAnimations.start();
454
455 if (!animate) mLayoutAnimations.updateAndFinish();
456 requestUpdate();
457 }
458
459 /**
460 * Steps the animation forward and updates all the animated values.
461 * @param time The current time of the app in ms.
462 * @param jumpToEnd Whether to finish the animation.
463 * @return Whether the animation was finished.
464 */
465 public boolean onUpdateAnimation(long time, boolean jumpToEnd) {
466 boolean finished = true;
467 if (mLayoutAnimations != null) {
468 if (jumpToEnd) {
469 finished = mLayoutAnimations.finished();
470 mLayoutAnimations.updateAndFinish();
471 } else {
472 finished = mLayoutAnimations.update(time);
473 }
474
475 if (finished || jumpToEnd) {
476 mLayoutAnimations = null;
477 onAnimationFinished();
478 }
479 requestUpdate();
480 }
481 return finished;
482 }
483
484 /**
485 * Called when layout-specific actions are needed after the animation finish es.
486 */
487 private void onAnimationFinished() {
488 if (mSlidingT >= 1.0f) enterDistilledMode();
489 updateBottomButtonBar();
490 }
491
492 // Gesture handling:
493
494 /**
495 * @param y The y coordinate in dp.
496 * @return Whether the given |y| coordinate is inside the Reader mode area.
497 */
498 public boolean isYCoordinateInsideReaderModePanel(float y) {
499 return y >= getPanelY() || y >= getDistilledContentY();
500 }
501
502 /**
503 * Handles a click in the panel area.
504 * @param x X-coordinate in dp
505 * @param y Y-coordinate in dp
506 */
507 public void handleClick(long time, float x, float y) {
508 if (mReaderModeHost.isInsideDismissButton(x * mDpToPx + mX, PANEL_HEIGHT _DP / 2)) {
509 dismissButtonBar();
510 return;
511 }
512
513 animateTo(mX, 1.0f, true);
514 }
515
516 private void nonAnimatedUpdateButtomButtonBar() {
517 final int status = mReaderModeHost.getReaderModeStatus();
518 final Tab tab = mReaderModeHost.getTab();
519
520 if (mReaderModeButtonView != null
521 && (status != ReaderModeManager.POSSIBLE || mIsReaderModePanelHi dden
522 || mIsReaderModePanelDismissed)) {
523 mReaderModeButtonView.dismiss(true);
524 mReaderModeButtonView = null;
525 return;
526 }
527 if (mReaderModeButtonView == null
528 && (status == ReaderModeManager.POSSIBLE && !mIsReaderModePanelH idden
529 && !mIsReaderModePanelDismissed)) {
530 mReaderModeButtonView = ReaderModeButtonView.create(tab.getContentVi ewCore(),
531 new ReaderModeButtonViewDelegate() {
532 @Override
533 public void onSwipeAway() {
534 dismissButtonBar();
535 }
536
537 @Override
538 public void onClick() {
539 nonAnimatedEnterDistilledMode();
540 }
541 });
542 }
543 }
544
545 /**
546 * Updates the visibility of the reader mode button bar as required.
547 */
548 public void updateBottomButtonBar() {
549 if (!mAllowAnimatedButton) {
550 nonAnimatedUpdateButtomButtonBar();
551 return;
552 }
553
554 if (isAnimating()) return;
555
556 final int status = mReaderModeHost.getReaderModeStatus();
557 if (isPanelWithinScreenBounds()
558 && (status != ReaderModeManager.POSSIBLE
559 || mIsReaderModePanelHidden || mIsReaderModePanelDismiss ed)) {
560 animateTo(0.0f, -1.0f, true);
561 destroyDistilledContentViewCore();
562 requestUpdate();
563 return;
564 }
565
566 if (!isPanelWithinScreenBounds()
567 && (status == ReaderModeManager.POSSIBLE
568 && !mIsReaderModePanelHidden && !mIsReaderModePanelDismi ssed)) {
569 animateTo(0.0f, 0.0f, true);
570 requestUpdate();
571 return;
572 }
573 }
574
575 private static ContentViewCore createDistillerContentViewCore(
576 Context context, WindowAndroid windowAndroid) {
577 ContentViewCore cvc = new ContentViewCore(context);
578 ContentView cv = new ContentView(context, cvc);
579 cvc.initialize(cv, cv, ContentViewUtil.createWebContents(false, true), w indowAndroid);
580 return cvc;
581 }
582
583 /**
584 * Prepares the distilled mode.
585 */
586 public void activatePreviewOfDistilledMode() {
587 if (mDistilledContentViewCore != null) return;
588
589 mDidFirstNonEmptyDistilledPaint = false;
590 mDistilledContentViewCore = createDistillerContentViewCore(
591 mReaderModeHost.getTab().getContentViewCore().getContext(),
592 mReaderModeHost.getTab().getWindowAndroid());
593 mDistilledContentObserver = new WebContentsObserver(
594 mDistilledContentViewCore.getWebContents()) {
595 @Override
596 public void didFirstVisuallyNonEmptyPaint() {
597 super.didFirstVisuallyNonEmptyPaint();
598 mDidFirstNonEmptyDistilledPaint = true;
599 }
600 };
601 mReaderModeHost.getTab().attachOverlayContentViewCore(
602 mDistilledContentViewCore, true, false);
603 DomDistillerTabUtils.distillAndView(
604 mReaderModeHost.getTab().getContentViewCore().getWebContents(),
605 mDistilledContentViewCore.getWebContents());
606 mDistilledContentViewCore.onShow();
607
608 mReaderModeHost.getTab().updateTopControlsState(TopControlsState.BOTH, f alse);
609 }
610
611 private void nonAnimatedEnterDistilledMode() {
612 RecordUserAction.record("DomDistiller_DistilledPageOpened");
613 DomDistillerTabUtils.distillCurrentPageAndView(mReaderModeHost.getTab(). getWebContents());
614 mReaderModeHost.getTab().updateTopControlsState(TopControlsState.SHOWN, false);
615 nonAnimatedUpdateButtomButtonBar();
616 }
617
618 private void enterDistilledMode() {
619 RecordUserAction.record("DomDistiller_DistilledPageOpened");
620 mSlidingT = -1.0f;
621 requestUpdate();
622
623 mReaderModeHost.getTab().updateTopControlsState(TopControlsState.HIDDEN, false);
624 DomDistillerTabUtils.distillCurrentPageAndView(mReaderModeHost.getTab(). getWebContents());
625 destroyDistilledContentViewCore();
626 if (mLayoutDelegate != null) {
627 mLayoutDelegate.setLayoutTabBrightness(1.0f);
628 mLayoutDelegate.setLayoutTabY(0.0f);
629 }
630
631 updateBottomButtonBar();
632 }
633
634 private void destroyDistilledContentViewCore() {
635 if (mDistilledContentViewCore == null) return;
636
637 mDistilledContentObserver.destroy();
638 mDistilledContentObserver = null;
639 mReaderModeHost.getTab().detachOverlayContentViewCore(mDistilledContentV iewCore);
640 mDistilledContentViewCore.destroy();
641 mDistilledContentViewCore = null;
642 }
643
644 /**
645 * Hides the reader mode button bar if shown.
646 */
647 public void hideButtonBar() {
648 if (mIsReaderModePanelHidden) return;
649
650 mIsReaderModePanelHidden = true;
651 updateBottomButtonBar();
652 }
653
654 /**
655 * Dismisses the reader mode button bar if shown.
656 */
657 public void dismissButtonBar() {
658 if (mIsReaderModePanelDismissed) return;
659
660 mIsReaderModePanelDismissed = true;
661 updateBottomButtonBar();
662 }
663
664 /**
665 * Shows the reader mode button bar if necessary.
666 */
667 public void unhideButtonBar() {
668 mIsReaderModePanelHidden = false;
669 updateBottomButtonBar();
670 }
671
672 /**
673 * @param tab A {@link Tab}.
674 * @return The panel associated with a given Tab.
675 */
676 public static ReaderModePanel getReaderModePanel(Tab tab) {
677 if (!(tab instanceof ChromeTab)) return null;
678 ReaderModeManager manager = ((ChromeTab) tab).getReaderModeManager();
679 if (manager == null) return null;
680 return manager.getReaderModePanel();
681 }
682 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698