Bug 1367927 - Add a "save to pocket" item to the page action menu. r?mixedpuppy draft
authorDrew Willcoxon <adw@mozilla.com>
Tue, 01 Aug 2017 10:17:00 -0700
changeset 619115 3bb548ddbb838637479a963aa2bfbf6f15f8d47a
parent 619114 a8eac0412b7e96656bbb50d21716dc03dcc14899
child 640310 77fea895d7a202a346473ca8e2ccba5d9e92d204
push id71585
push userdwillcoxon@mozilla.com
push dateTue, 01 Aug 2017 17:17:22 +0000
reviewersmixedpuppy
bugs1367927
milestone56.0a1
Bug 1367927 - Add a "save to pocket" item to the page action menu. r?mixedpuppy MozReview-Commit-ID: APmutOUh2Q6
browser/extensions/pocket/bootstrap.js
browser/extensions/pocket/content/Pocket.jsm
browser/extensions/pocket/content/main.js
browser/extensions/pocket/skin/shared/pocket.css
browser/extensions/pocket/skin/shared/pocket.svg
testing/profiles/prefs_general.js
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -11,16 +11,18 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PageActions",
+                                  "resource:///modules/PageActions.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
                                   "resource://gre/modules/ReaderMode.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
                                   "chrome://pocket/content/Pocket.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AboutPocket",
                                   "chrome://pocket/content/AboutPocket.jsm");
@@ -145,39 +147,88 @@ function CreatePocketWidget(reason) {
     let widgets = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR);
     let bmbtn = widgets.indexOf("bookmarks-menu-button");
     if (bmbtn > -1) {
       CustomizableUI.moveWidgetWithinArea("pocket-button", bmbtn + 1);
     }
   }
 }
 
+function isPocketEnabled() {
+  return PocketPageAction.shouldUse ? PocketPageAction.enabled :
+         !!CustomizableUI.getPlacementOfWidget("pocket-button");
+}
+
+var PocketPageAction = {
+  pageAction: null,
+
+  get shouldUse() {
+    return !Services.prefs.getBranch(PREF_BRANCH)
+            .getBoolPref("disablePageAction", false);
+  },
+
+  get enabled() {
+    return !!this.pageAction;
+  },
+
+  init() {
+    if (!this.shouldUse) {
+      return;
+    }
+    let id = "pocket";
+    this.pageAction = PageActions.actionForID(id);
+    if (!this.pageAction) {
+      this.pageAction = PageActions.addAction(new PageActions.Action({
+        id,
+        title: gPocketBundle.GetStringFromName("pocket-button.label"),
+        shownInUrlbar: true,
+        wantsIframe: true,
+        _insertBeforeActionID: PageActions.ACTION_ID_BOOKMARK_SEPARATOR,
+        onPlacedInPanel(panelNode, urlbarNode) {
+          PocketOverlay.onWindowOpened(panelNode.ownerGlobal);
+        },
+        onIframeShown(iframe, panel) {
+          Pocket.onShownInPhotonPageActionPanel(panel, iframe);
+        },
+      }));
+    }
+  },
+
+  shutdown() {
+    if (!this.pageAction) {
+      return;
+    }
+    this.pageAction.remove();
+    this.pageAction = null;
+  },
+};
+
 // PocketContextMenu
 // When the context menu is opened check if we need to build and enable pocket UI.
 var PocketContextMenu = {
   init() {
     Services.obs.addObserver(this, "on-build-contextmenu");
   },
   shutdown() {
     Services.obs.removeObserver(this, "on-build-contextmenu");
     // loop through windows and remove context menus
     // iterate through all windows and add pocket to them
-    for (let win of CustomizableUI.windows) {
+    for (let win of browserWindows()) {
       let document = win.document;
       for (let id of ["context-pocket", "context-savelinktopocket"]) {
         let element = document.getElementById(id);
         if (element)
           element.remove();
       }
     }
   },
   observe(aSubject, aTopic, aData) {
     let subject = aSubject.wrappedJSObject;
     let document = subject.menu.ownerDocument;
-    let pocketEnabled = CustomizableUI.getPlacementOfWidget("pocket-button");
+    let pocketEnabled = isPocketEnabled();
 
     let showSaveCurrentPageToPocket = !(subject.onTextInput || subject.onLink ||
                                         subject.isContentSelected || subject.onImage ||
                                         subject.onCanvas || subject.onVideo || subject.onAudio);
     let targetUrl = subject.onLink ? subject.linkUrl : subject.pageUrl;
     let targetURI = Services.io.newURI(targetUrl);
     let canPocket = pocketEnabled && (targetURI.schemeIs("http") || targetURI.schemeIs("https") ||
                     (targetURI.schemeIs("about") && ReaderMode.getOriginalUrl(targetUrl)));
@@ -267,27 +318,34 @@ var PocketReader = {
         message.target.messageManager.
           sendAsyncMessage("Reader:AddButton", { id: "pocket-button",
                                                  title: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
                                                  image: "chrome://pocket/content/panels/img/pocket.svg#pocket-mark"});
         break;
       }
       case "Reader:Clicked-pocket-button": {
         let doc = message.target.ownerDocument;
-        let pocketWidget = doc.getElementById("pocket-button");
-        let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
-        if (placement) {
-          if (placement.area == CustomizableUI.AREA_PANEL) {
-            doc.defaultView.PanelUI.show().then(function() {
-              // The DOM node might not exist yet if the panel wasn't opened before.
-              pocketWidget = doc.getElementById("pocket-button");
+        if (PocketPageAction.shouldUse) {
+          // TODO: PageActions should make this easier.
+          let event = new doc.defaultView.CustomEvent("command");
+          let panelButton = doc.getElementById("pageAction-panel-pocket");
+          panelButton.dispatchEvent(event);
+        } else {
+          let pocketWidget = doc.getElementById("pocket-button");
+          let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
+          if (placement) {
+            if (placement.area == CustomizableUI.AREA_PANEL) {
+              doc.defaultView.PanelUI.show().then(function() {
+                // The DOM node might not exist yet if the panel wasn't opened before.
+                pocketWidget = doc.getElementById("pocket-button");
+                pocketWidget.doCommand();
+              });
+            } else {
               pocketWidget.doCommand();
-            });
-          } else {
-            pocketWidget.doCommand();
+            }
           }
         }
         break;
       }
     }
   }
 }
 
@@ -311,75 +369,87 @@ var PocketOverlay = {
   startup(reason) {
     let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
                               .getService(Ci.nsIStyleSheetService);
     this._sheetType = styleSheetService.AUTHOR_SHEET;
     this._cachedSheet = styleSheetService.preloadSheet(gPocketStyleURI,
                                                        this._sheetType);
     Services.ppmm.loadProcessScript(PROCESS_SCRIPT, true);
     PocketReader.startup();
-    CustomizableUI.addListener(this);
-    CreatePocketWidget(reason);
+    if (PocketPageAction.shouldUse) {
+      PocketPageAction.init();
+    } else {
+      CustomizableUI.addListener(this);
+      CreatePocketWidget(reason);
+    }
     PocketContextMenu.init();
-
-    for (let win of CustomizableUI.windows) {
+    for (let win of browserWindows()) {
       this.onWindowOpened(win);
     }
   },
   shutdown(reason) {
     let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                  .getService(Ci.nsIMessageBroadcaster);
     ppmm.broadcastAsyncMessage("PocketShuttingDown");
     // Although the ppmm loads the scripts into the chrome process as well,
     // we need to manually unregister here anyway to ensure these aren't part
     // of the chrome process and avoid errors.
     AboutPocket.aboutSaved.unregister();
     AboutPocket.aboutSignup.unregister();
 
-    CustomizableUI.removeListener(this);
-    for (let window of CustomizableUI.windows) {
+    if (PocketPageAction.shouldUse) {
+      PocketPageAction.shutdown();
+    } else {
+      CustomizableUI.removeListener(this);
+      CustomizableUI.destroyWidget("pocket-button");
+    }
+
+    for (let window of browserWindows()) {
       for (let id of ["panelMenu_pocket", "menu_pocket", "BMB_pocket",
                       "panelMenu_pocketSeparator", "menu_pocketSeparator",
                       "BMB_pocketSeparator", "appMenu-library-pocket-button"]) {
         let element = window.document.getElementById(id);
         if (element)
           element.remove();
       }
       this.removeStyles(window);
       // remove script getters/objects
       delete window.Pocket;
       delete window.pktApi;
       delete window.pktUI;
       delete window.pktUIMessaging;
     }
-    CustomizableUI.destroyWidget("pocket-button");
+
     PocketContextMenu.shutdown();
     PocketReader.shutdown();
   },
   onWindowOpened(window) {
     if (window.hasOwnProperty("pktUI"))
       return;
     this.setWindowScripts(window);
     this.addStyles(window);
     this.updateWindow(window);
+    if (PocketPageAction.shouldUse) {
+      this.updateWindowAfterWidgetPlaced(window);
+    }
   },
   setWindowScripts(window) {
     XPCOMUtils.defineLazyModuleGetter(window, "Pocket",
                                       "chrome://pocket/content/Pocket.jsm");
     // Can't use XPCOMUtils for these because the scripts try to define the variables
     // on window, and so the defineProperty inside defineLazyGetter fails.
     Object.defineProperty(window, "pktApi", pktUIGetter("pktApi", window));
     Object.defineProperty(window, "pktUI", pktUIGetter("pktUI", window));
     Object.defineProperty(window, "pktUIMessaging", pktUIGetter("pktUIMessaging", window));
   },
   // called for each window as it is opened
   updateWindow(window) {
     // insert our three menu items
     let document = window.document;
-    let hidden = !CustomizableUI.getPlacementOfWidget("pocket-button");
+    let hidden = !isPocketEnabled();
 
     // add to bookmarksMenu
     let sib = document.getElementById("menu_bookmarkThisPage");
     if (sib && !document.getElementById("menu_pocket")) {
       let menu = createElementWithAttrs(document, "menuitem", {
         "id": "menu_pocket",
         "label": gPocketBundle.GetStringFromName("pocketMenuitem.label"),
         "class": "menuitem-iconic", // OSX only
@@ -445,18 +515,22 @@ var PocketOverlay = {
       });
       sib.parentNode.insertBefore(menu, sib);
     }
   },
   onWidgetAfterDOMChange(aWidgetNode) {
     if (aWidgetNode.id != "pocket-button") {
       return;
     }
-    let doc = aWidgetNode.ownerDocument;
-    let hidden = !CustomizableUI.getPlacementOfWidget("pocket-button");
+    this.updateWindowAfterWidgetPlaced(aWidgetNode.ownerGlobal);
+  },
+
+  updateWindowAfterWidgetPlaced(browserWindow) {
+    let doc = browserWindow.document;
+    let hidden = !isPocketEnabled();
     let elementIds = [
       "panelMenu_pocket",
       "menu_pocket",
       "BMB_pocket",
       "appMenu-library-pocket-button",
     ];
     for (let elementId of elementIds) {
       let element = doc.getElementById(elementId);
@@ -520,8 +594,15 @@ function shutdown(data, reason) {
   }
 }
 
 function install() {
 }
 
 function uninstall() {
 }
+
+function* browserWindows() {
+  let windows = Services.wm.getEnumerator("navigator:browser");
+  while (windows.hasMoreElements()) {
+    yield windows.getNext();
+  }
+}
--- a/browser/extensions/pocket/content/Pocket.jsm
+++ b/browser/extensions/pocket/content/Pocket.jsm
@@ -23,19 +23,28 @@ var Pocket = {
 
   /**
    * Functions related to the Pocket panel UI.
    */
   onBeforeCommand(event) {
     BrowserUtils.setToolbarButtonHeightProperty(event.target);
   },
 
+  onShownInPhotonPageActionPanel(panel, iframe) {
+    let window = panel.ownerGlobal;
+    window.pktUI.setPhotonPageActionPanelFrame(iframe);
+    Pocket._initPanelView(window);
+  },
+
   onPanelViewShowing(event) {
-    let document = event.target.ownerDocument;
-    let window = document.defaultView;
+    Pocket._initPanelView(event.target.ownerGlobal);
+  },
+
+  _initPanelView(window) {
+    let document = window.document;
     let iframe = window.pktUI.getPanelFrame();
 
     let libraryButton = document.getElementById("library-button");
     if (libraryButton) {
       BrowserUtils.setToolbarButtonHeightProperty(libraryButton);
     }
 
     let urlToSave = Pocket._urlToSave;
--- a/browser/extensions/pocket/content/main.js
+++ b/browser/extensions/pocket/content/main.js
@@ -562,36 +562,54 @@ var pktUI = (function() {
         var frame = getPanelFrame();
         var panel = frame;
         while (panel && panel.localName != "panel") {
             panel = panel.parentNode;
         }
         return panel;
     }
 
+    var photonPageActionPanelFrame;
+
+    function setPhotonPageActionPanelFrame(frame) {
+        photonPageActionPanelFrame = frame;
+    }
+
     function getPanelFrame() {
+        if (photonPageActionPanelFrame) {
+            return photonPageActionPanelFrame;
+        }
+
         var frame = document.getElementById("pocket-panel-iframe");
         if (!frame) {
             var frameParent = document.getElementById("PanelUI-pocketView").firstChild;
             frame = document.createElement("iframe");
             frame.id = "pocket-panel-iframe";
             frame.setAttribute("type", "content");
             frameParent.appendChild(frame);
         }
         return frame;
     }
 
     function getSubview() {
+        if (photonPageActionPanelFrame) {
+            return null;
+        }
+
         var view = document.getElementById("PanelUI-pocketView");
         if (view && view.getAttribute("current") == "true" && !view.getAttribute("mainview"))
             return view;
         return null;
     }
 
     function isInOverflowMenu() {
+        if (photonPageActionPanelFrame) {
+            return false;
+        }
+
         var subview = getSubview();
         return !!subview;
     }
 
     function getFirefoxAccountSignedInUser(callback) {
         fxAccounts.getSignedInUser().then(userData => {
             callback(userData);
         }).then(null, error => {
@@ -602,16 +620,17 @@ var pktUI = (function() {
     function getUILocale() {
         return Services.locale.getAppLocaleAsLangTag();
     }
 
     /**
      * Public functions
      */
     return {
+        setPhotonPageActionPanelFrame,
         getPanelFrame,
 
         openTabWithUrl,
 
         pocketPanelDidShow,
         pocketPanelDidHide,
 
         tryToSaveUrl,
--- a/browser/extensions/pocket/skin/shared/pocket.css
+++ b/browser/extensions/pocket/skin/shared/pocket.css
@@ -243,8 +243,13 @@ toolbar[brighttext] #pocket-button {
   #BMB_pocket {
     list-style-image: url("chrome://pocket/content/panels/img/pocketmenuitem16@2x.png");
   }
 
   #panelMenu_pocket > .toolbarbutton-icon {
     width: 16px;
   }
 }
+
+#pageAction-panel-pocket,
+#pageAction-urlbar-pocket {
+  list-style-image: url("chrome://pocket-shared/skin/pocket.svg");
+}
--- a/browser/extensions/pocket/skin/shared/pocket.svg
+++ b/browser/extensions/pocket/skin/shared/pocket.svg
@@ -1,6 +1,6 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
-  <path fill="context-fill" d="M9,15.969a8,8,0,0,1-8-8v-4a2,2,0,0,1,2-2H15a2,2,0,0,1,2,2v4A8,8,0,0,1,9,15.969ZM12.985,5.937a0.99,0.99,0,0,0-.725.319L8.978,9.539,5.755,6.305A0.984,0.984,0,0,0,5,5.937a1,1,0,0,0-.714,1.7L4.27,7.648l3.293,3.306h0l0.707,0.707a1,1,0,0,0,1.414,0l0.707-.707h0L13.7,7.648l0,0A1,1,0,0,0,12.985,5.937Z"/>
+  <path fill-opacity="context-fill-opacity" fill="context-fill" d="M9,15.969a8,8,0,0,1-8-8v-4a2,2,0,0,1,2-2H15a2,2,0,0,1,2,2v4A8,8,0,0,1,9,15.969ZM12.985,5.937a0.99,0.99,0,0,0-.725.319L8.978,9.539,5.755,6.305A0.984,0.984,0,0,0,5,5.937a1,1,0,0,0-.714,1.7L4.27,7.648l3.293,3.306h0l0.707,0.707a1,1,0,0,0,1.414,0l0.707-.707h0L13.7,7.648l0,0A1,1,0,0,0,12.985,5.937Z"/>
 </svg>
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -388,8 +388,12 @@ user_pref("marionette.prefs.recommended"
 
 // Disable Screenshots by default for now
 user_pref("extensions.screenshots.system-disabled", true);
 
 // Set places maintenance far in the future to avoid it kicking in during tests.
 // The maintenance can take a relatively long time which may cause unnecessary
 // intermittents and slow down tests.
 user_pref("places.database.lastMaintenance", 7258114800);
+
+// Disable the Pocket page action and enable the CUI widget until bug 1385418 is
+// fixed.
+user_pref("extensions.pocket.disablePageAction", true);