Skip to content

Commit

Permalink
Closes mozilla-mobile#1385: Determine and expose reader-ability state
Browse files Browse the repository at this point in the history
  • Loading branch information
csadilek committed Apr 26, 2019
1 parent b98206c commit ba449b4
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 25 deletions.
Expand Up @@ -72,6 +72,7 @@ class Session(
fun onMediaAdded(session: Session, media: List<Media>, added: Media) = Unit
fun onCrashStateChanged(session: Session, crashed: Boolean) = Unit
fun onIconChanged(session: Session, icon: Bitmap?) = Unit
fun onReaderableStateUpdated(session: Session, readerable: Boolean) = Unit
}

/**
Expand Down Expand Up @@ -371,6 +372,13 @@ class Session(
notifyObservers(old, new) { onCrashStateChanged(this@Session, new) }
}

/**
* Readerable state, whether or not the current page can be shown in reader view.
*/
var readerable: Boolean by Delegates.observable(false) { _, _, new ->
notifyObservers{ onReaderableStateUpdated(this@Session, new) }
}

/**
* Returns whether or not this session is used for a Custom Tab.
*/
Expand Down
Expand Up @@ -5,9 +5,24 @@
/* Avoid adding ID selector rules in this style sheet, since they could
* inadvertently match elements in the article content. */

const supportedProtocols = ["http:", "https:"];
const blockedHosts = ["amazon.com", "github.com", "mail.google.com", "pinterest.com", "reddit.com", "twitter.com", "youtube.com"];

class ReaderView {

static isReaderable() {
if (!supportedProtocols.includes(location.protocol)) {
return false;
}

if (blockedHosts.some(blockedHost => location.hostname.endsWith(blockedHost))) {
return false;
}

if (location.pathname == "/") {
return false;
}

return isProbablyReaderable(document);
}

Expand Down Expand Up @@ -261,15 +276,24 @@ class ReaderView {
}
}

// TODO remove (for testing purposes only)
let port = browser.runtime.connectNative("mozacReaderview");
port.postMessage(`Hello from ReaderView on page ${location.hostname}`);

port.onMessage.addListener((response) => {
console.log(`Received: ${JSON.stringify(response)} on page ${location.hostname}`);
port.onMessage.addListener((message) => {
switch (message.action) {
case 'show':
readerView.show({fontSize: 3, fontType: "serif", colorScheme: "light"});
break;
case 'hide':
readerView.hide();
break;
case 'checkReaderable':
port.postMessage({readerable: ReaderView.isReaderable()});
break;
default:
console.error(`Received invalid action ${message.action}`);
}
});


// TODO remove hostname check (for testing purposes only)
// e.g. https://blog.mozilla.org/firefox/reader-view
if (ReaderView.isReaderable() && location.hostname.endsWith("blog.mozilla.org")) {
Expand Down
Expand Up @@ -36,16 +36,18 @@ typealias OnReaderViewAvailableChange = (available: Boolean) -> Unit
* @property context a reference to the context.
* @property engine a reference to the application's browser engine.
* @property sessionManager a reference to the application's [SessionManager].
* @property onReaderViewAvailableChange a callback invoked to indicate whether
* @property onReaderViewAvailableChanged a callback invoked to indicate whether
* or not reader view is available for the page loaded by the currently selected
* (active) session.
* session. The callback will be invoked when a page is loaded or refreshed,
* on any navigation (back or forward) and when the selected session
* changes.
*/
class ReaderViewFeature(
private val context: Context,
private val engine: Engine,
private val sessionManager: SessionManager,
controlsView: ReaderViewControlsView,
private val onReaderViewAvailableChange: OnReaderViewAvailableChange = { }
private val onReaderViewAvailableChanged: OnReaderViewAvailableChange = { }
) : SelectionAwareSessionObserver(sessionManager), LifecycleAwareFeature, BackHandler {

private val config = Config(context.getSharedPreferences("mozac_feature_reader_view", Context.MODE_PRIVATE))
Expand Down Expand Up @@ -93,6 +95,8 @@ class ReaderViewFeature(
ReaderViewFeature.install(engine)
}

checkReaderable()

controlsInteractor.start()
}

Expand All @@ -101,26 +105,24 @@ class ReaderViewFeature(
return
}

// TODO https://github.com/mozilla-mobile/android-components/issues/2624
val messageHandler = object : MessageHandler {
override fun onPortConnected(port: Port) {
ReaderViewFeature.ports[port.engineSession] = port
ports[port.engineSession] = port
checkReaderable()
}

override fun onPortDisconnected(port: Port) {
ReaderViewFeature.ports.remove(port.engineSession)
ports.remove(port.engineSession)
}

override fun onPortMessage(message: Any, port: Port) {
Logger.info("Received message: $message")
val response = JSONObject().apply {
put("response", "Message received! Hello from Android!")
if (message is JSONObject) {
activeSession?.readerable = message.getBoolean(READERABLE_RESPONSE_MESSAGE_KEY)
}
ReaderViewFeature.sendMessage(response, port.engineSession!!)
}
}

ReaderViewFeature.registerMessageHandler(sessionManager.getOrCreateEngineSession(session), messageHandler)
registerMessageHandler(sessionManager.getOrCreateEngineSession(session), messageHandler)
}

override fun stop() {
Expand All @@ -136,19 +138,32 @@ class ReaderViewFeature(
override fun onSessionSelected(session: Session) {
// TODO restore selected state of whether the controls are open or not
registerContentMessageHandler(activeSession)
checkReaderable()
super.onSessionSelected(session)
}

override fun onSessionRemoved(session: Session) {
ReaderViewFeature.ports.remove(sessionManager.getEngineSession(session))
ports.remove(sessionManager.getEngineSession(session))
}

override fun onUrlChanged(session: Session, url: String) {
checkReaderable()
}

override fun onReaderableStateUpdated(session: Session, readerable: Boolean) {
onReaderViewAvailableChanged(readerable)
}

fun showReaderView() {
// TODO send message to show reader view (-> see ReaderView.show())
activeSession?.let {
sendMessage(JSONObject().apply { put(ACTION_MESSAGE_KEY, ACTION_SHOW) }, it)
}
}

fun hideReaderView() {
// TODO send message to hide reader view (-> see ReaderView.hide())
activeSession?.let {
sendMessage(JSONObject().apply { put(ACTION_MESSAGE_KEY, ACTION_HIDE) }, it)
}
}

/**
Expand All @@ -165,12 +180,43 @@ class ReaderViewFeature(
controlsPresenter.hide()
}

internal fun checkReaderable() {
activeSession?.let {
if (ports.containsKey(sessionManager.getEngineSession(it))) {
sendMessage(JSONObject().apply { put(ACTION_MESSAGE_KEY, ACTION_CHECK_READERABLE) }, it)
}
}
}

private fun sendMessage(msg: Any, session: Session) {
val port = ports[sessionManager.getEngineSession(session)]
port?.postMessage(msg) ?: throw IllegalStateException("No port connected for the provided session")
}

companion object {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val READER_VIEW_EXTENSION_ID = "mozacReaderview"

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val READER_VIEW_EXTENSION_URL = "resource://android/assets/extensions/readerview/"

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val ACTION_MESSAGE_KEY = "action"

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val ACTION_SHOW = "show"

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val ACTION_HIDE = "hide"

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val ACTION_CHECK_READERABLE = "checkReaderable"

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal const val READERABLE_RESPONSE_MESSAGE_KEY = "readerable"

@Volatile
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var installedWebExt: WebExtension? = null

@Volatile
Expand Down Expand Up @@ -205,10 +251,5 @@ class ReaderViewFeature(

installedWebExt?.let { registerContentMessageHandler(it) }
}

fun sendMessage(msg: Any, engineSession: EngineSession) {
val port = ports[engineSession]
port?.postMessage(msg) ?: throw IllegalStateException("No port connected for the provided session")
}
}
}
Expand Up @@ -13,6 +13,7 @@ import mozilla.components.feature.readerview.ReaderViewFeature
import mozilla.components.feature.readerview.view.ReaderViewControlsView
import mozilla.components.support.base.feature.BackHandler
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.base.log.Log

class ReaderViewIntegration(
context: Context,
Expand All @@ -21,7 +22,9 @@ class ReaderViewIntegration(
private val view: ReaderViewControlsView
) : LifecycleAwareFeature, BackHandler {

private val feature = ReaderViewFeature(context, engine, sessionManager, view)
private val feature = ReaderViewFeature(context, engine, sessionManager, view) { sessionReaderable ->
Log.log(Log.Priority.ERROR, "Foo", null, "Selected session " + sessionManager.selectedSession?.url + " readerable " + sessionReaderable)
}

override fun start() {
feature.start()
Expand Down

0 comments on commit ba449b4

Please sign in to comment.