Bug 1234331 - Push article into readercache when bookmarking readerview page r?margaret draft
authorAndrzej Hunt <ahunt@mozilla.com>
Tue, 22 Mar 2016 14:29:03 -0700
changeset 343514 3237c925db734111863a8f2cac87e62f19624fe7
parent 343513 ae36efdea3f9e99946fbb21f173b345289687f56
child 343515 cc8e298bf08dfb426b37709cd73d53c57a6655ea
push id13648
push userahunt@mozilla.com
push dateTue, 22 Mar 2016 21:53:51 +0000
reviewersmargaret
bugs1234331
milestone48.0a1
Bug 1234331 - Push article into readercache when bookmarking readerview page r?margaret MozReview-Commit-ID: D7Yy45xkFd8
mobile/android/base/java/org/mozilla/gecko/Tab.java
mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java
mobile/android/chrome/content/Reader.js
mobile/android/chrome/content/browser.js
toolkit/components/reader/ReaderMode.jsm
--- a/mobile/android/base/java/org/mozilla/gecko/Tab.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tab.java
@@ -20,16 +20,17 @@ import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.URLMetadata;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.LoadFaviconTask;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.RemoteFavicon;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.reader.ReaderModeUtils;
+import org.mozilla.gecko.reader.ReadingListHelper;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
@@ -565,16 +566,20 @@ public class Tab {
 
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 mDB.addBookmark(getContentResolver(), mTitle, pageUrl);
                 Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.BOOKMARK_ADDED);
             }
         });
+
+        if (AboutPages.isAboutReader(url)) {
+            ReadingListHelper.cacheReaderItem(pageUrl, mAppContext);
+        }
     }
 
     public void removeBookmark() {
         final String url = getURL();
         if (url == null) {
             return;
         }
 
--- a/mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java
@@ -1,16 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.reader;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+
+import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.ReadingListAccessor;
@@ -52,18 +54,17 @@ public final class ReadingListHelper imp
     volatile boolean fetchInBackground = true;
 
     public ReadingListHelper(Context context, GeckoProfile profile, OnReadingListEventListener listener) {
         this.context = context;
         this.db = profile.getDB();
         this.readingListAccessor = db.getReadingListAccessor();
 
         EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this,
-            "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest");
-
+            "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:AddedToCache");
 
         contentObserver = new ContentObserver(null) {
             @Override
             public void onChange(boolean selfChange) {
                 if (fetchInBackground) {
                     fetchContent();
                 }
             }
@@ -71,17 +72,17 @@ public final class ReadingListHelper imp
 
         this.readingListAccessor.registerContentObserver(context, contentObserver);
 
         onReadingListEventListener = listener;
     }
 
     public void uninit() {
         EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
-            "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest");
+            "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:AddedToCache");
 
         context.getContentResolver().unregisterContentObserver(contentObserver);
     }
 
     @Override
     public void handleMessage(final String event, final NativeJSObject message,
                               final EventCallback callback) {
         switch(event) {
@@ -93,16 +94,22 @@ public final class ReadingListHelper imp
             case "Reader:UpdateList": {
                 handleUpdateList(message);
                 break;
             }
             case "Reader:FaviconRequest": {
                 handleReaderModeFaviconRequest(callback, message.getString("url"));
                 break;
             }
+            case "Reader:AddedToCache": {
+                // AddedToCache is a one way message: callback will be null, and we therefore shouldn't
+                // attempt to handle it.
+                handleAddedToCache(message.getString("url"), message.getString("path"), message.getInt("size"));
+                break;
+            }
         }
     }
 
     /**
      * A page can be added to the ReadingList by long-tap of the page-action
      * icon, or by tapping the readinglist-add icon in the ReaderMode banner.
      *
      * This method will only add new items, not update existing items.
@@ -222,24 +229,30 @@ public final class ReadingListHelper imp
                         Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
                     }
                 }
                 callback.sendSuccess(args.toString());
             }
         }).execute();
     }
 
+    private void handleAddedToCache(final String url, final String path, final int size) {
+        final ReaderCacheHelper rch = GeckoProfile.get(context).getReaderCacheHelper();
+
+        rch.put(url, path, size);
+    }
+
     /**
      * Handle various reading list events (and display appropriate toasts).
      */
     private void handleEvent(final ReadingListEvent event, final String url) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
-                switch(event) {
+                switch (event) {
                     case ADDED:
                         onReadingListEventListener.onAddedToReadingList(url);
                         break;
                     case REMOVED:
                         onReadingListEventListener.onRemovedFromReadingList(url);
                         break;
                     case ALREADY_EXISTS:
                         onReadingListEventListener.onAlreadyInReadingList(url);
@@ -271,16 +284,29 @@ public final class ReadingListHelper imp
                     }
                 } finally {
                     c.close();
                 }
             }
         });
     }
 
+    public static void cacheReaderItem(final String url, Context context) {
+        if (AboutPages.isAboutReader(url)) {
+            throw new IllegalArgumentException("Page url must be original (not about:reader) url");
+        }
+
+        ReaderCacheHelper rch = GeckoProfile.get(context).getReaderCacheHelper();
+
+        if (!rch.isURLCached(url)) {
+            GeckoAppShell.sendEventToGecko(
+                    GeckoEvent.createBroadcastEvent("Reader:AddToCache", url));
+        }
+    }
+
     @RobocopTarget
     /**
      * Test code will want to disable background fetches to avoid upsetting
      * the test harness. Call this by accessing the instance from BrowserApp.
      */
     public void disableBackgroundFetches() {
         fetchInBackground = false;
     }
--- a/mobile/android/chrome/content/Reader.js
+++ b/mobile/android/chrome/content/Reader.js
@@ -75,16 +75,25 @@ var Reader = {
         this._fetchContent(data.url, data.id);
         break;
       }
 
       case "Reader:Removed": {
         ReaderMode.removeArticleFromCache(aData).catch(e => Cu.reportError("Error removing article from cache: " + e));
         break;
       }
+
+      case "Reader:AddToCache": {
+        // If the article is coming from reader mode, we must have fetched it already.
+        this._getArticle(aData).then((article) => {
+          console.log("trying");
+          ReaderMode.storeArticleInCache(article).catch(e => Cu.reportError("Error storing article in cache: " + e));
+        });
+        break;
+      }
     }
   },
 
   receiveMessage: function(message) {
     switch (message.name) {
       case "Reader:ArticleGet":
         this._getArticle(message.data.url).then((article) => {
           // Make sure the target browser is still alive before trying to send data back.
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -147,17 +147,17 @@ var lazilyLoadedObserverScripts = [
   ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
   ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
   ["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
   ["PermissionsHelper", ["Permissions:Check", "Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
-  ["Reader", ["Reader:FetchContent", "Reader:Removed"], "chrome://browser/content/Reader.js"],
+  ["Reader", ["Reader:FetchContent", "Reader:AddToCache", "Reader:Removed"], "chrome://browser/content/Reader.js"],
   ["PrintHelper", ["Print:PDF"], "chrome://browser/content/PrintHelper.js"],
 ];
 if (AppConstants.NIGHTLY_BUILD) {
   lazilyLoadedObserverScripts.push(
     ["ActionBarHandler", ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
       "chrome://browser/content/ActionBarHandler.js"]
   );
 }
@@ -183,16 +183,17 @@ lazilyLoadedObserverScripts.forEach(func
   notifications.forEach((notification) => {
     Services.obs.addObserver(observer, notification, false);
   });
 });
 
 // Lazily-loaded browser scripts that use message listeners.
 [
   ["Reader", [
+    ["Reader:AddToCache", false],
     ["Reader:ArticleGet", false],
     ["Reader:DropdownClosed", true], // 'true' allows us to survive mid-air cycle-collection.
     ["Reader:DropdownOpened", false],
     ["Reader:FaviconRequest", false],
     ["Reader:ToolbarHidden", false],
     ["Reader:SystemUIVisibility", false],
     ["Reader:UpdateReaderButton", false],
     ["Reader:SetIntPref", false],
--- a/toolkit/components/reader/ReaderMode.jsm
+++ b/toolkit/components/reader/ReaderMode.jsm
@@ -19,16 +19,17 @@ const PARSE_ERROR_WORKER = 2;
 const PARSE_ERROR_NO_ARTICLE = 3;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.importGlobalProperties(["XMLHttpRequest"]);
 
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", "resource://services-common/utils.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging", "resource://gre/modules/Messaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderWorker", "resource://gre/modules/reader/ReaderWorker.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "Readability", function() {
   let scope = {};
   scope.dump = this.dump;
@@ -280,17 +281,27 @@ this.ReaderMode = {
    * @return {Promise}
    * @resolves When the article is stored.
    * @rejects OS.File.Error
    */
   storeArticleInCache: Task.async(function* (article) {
     let array = new TextEncoder().encode(JSON.stringify(article));
     let path = this._toHashedPath(article.url);
     yield this._ensureCacheDir();
-    yield OS.File.writeAtomic(path, array, { tmpPath: path + ".tmp" });
+    return OS.File.writeAtomic(path, array, { tmpPath: path + ".tmp" })
+      .then(success => {
+        OS.File.stat(path).then(info => {
+          return Messaging.sendRequest({
+            type: "Reader:AddedToCache",
+            url: article.url,
+            size: info.size,
+            path: path,
+          });
+        });
+      });
   }),
 
   /**
    * Removes an article from the cache given an article URI.
    *
    * @param url The article URL.
    * @return {Promise}
    * @resolves When the article is removed.