summaryrefslogtreecommitdiffstats
path: root/browser/extensions/pocket/bootstrap.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/pocket/bootstrap.js')
-rw-r--r--browser/extensions/pocket/bootstrap.js511
1 files changed, 511 insertions, 0 deletions
diff --git a/browser/extensions/pocket/bootstrap.js b/browser/extensions/pocket/bootstrap.js
new file mode 100644
index 000000000..c470eb8d3
--- /dev/null
+++ b/browser/extensions/pocket/bootstrap.js
@@ -0,0 +1,511 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Preferences.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, "AddonManager",
+ "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");
+XPCOMUtils.defineLazyGetter(this, "gPocketBundle", function() {
+ return Services.strings.createBundle("chrome://pocket/locale/pocket.properties");
+});
+XPCOMUtils.defineLazyGetter(this, "gPocketStyleURI", function() {
+ return Services.io.newURI("chrome://pocket/skin/pocket.css", null, null);
+});
+
+// Due to bug 1051238 frame scripts are cached forever, so we can't update them
+// as a restartless add-on. The Math.random() is the work around for this.
+const PROCESS_SCRIPT = "chrome://pocket/content/pocket-content-process.js?" + Math.random();
+
+const PREF_BRANCH = "extensions.pocket.";
+const PREFS = {
+ enabled: true, // bug 1229937, figure out ui tour support
+ api: "api.getpocket.com",
+ site: "getpocket.com",
+ oAuthConsumerKey: "40249-e88c401e1b1f2242d9e441c4"
+};
+
+function setDefaultPrefs() {
+ let branch = Services.prefs.getDefaultBranch(PREF_BRANCH);
+ for (let [key, val] of Object.entries(PREFS)) {
+ // If someone beat us to setting a default, don't overwrite it. This can
+ // happen if distribution.ini sets the default first.
+ if (branch.getPrefType(key) != branch.PREF_INVALID)
+ continue;
+ switch (typeof val) {
+ case "boolean":
+ branch.setBoolPref(key, val);
+ break;
+ case "number":
+ branch.setIntPref(key, val);
+ break;
+ case "string":
+ branch.setCharPref(key, val);
+ break;
+ }
+ }
+}
+
+function createElementWithAttrs(document, type, attrs) {
+ let element = document.createElement(type);
+ Object.keys(attrs).forEach(function (attr) {
+ element.setAttribute(attr, attrs[attr]);
+ })
+ return element;
+}
+
+function CreatePocketWidget(reason) {
+ let id = "pocket-button"
+ let widget = CustomizableUI.getWidget(id);
+ // The widget is only null if we've created then destroyed the widget.
+ // Once we've actually called createWidget the provider will be set to
+ // PROVIDER_API.
+ if (widget && widget.provider == CustomizableUI.PROVIDER_API)
+ return;
+ // if upgrading from builtin version and the button was placed in ui,
+ // seenWidget will not be null
+ let seenWidget = CustomizableUI.getPlacementOfWidget("pocket-button", false, true);
+ let pocketButton = {
+ id: "pocket-button",
+ defaultArea: CustomizableUI.AREA_NAVBAR,
+ introducedInVersion: "pref",
+ type: "view",
+ tabSpecific: true,
+ viewId: "PanelUI-pocketView",
+ label: gPocketBundle.GetStringFromName("pocket-button.label"),
+ tooltiptext: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
+ // Use forwarding functions here to avoid loading Pocket.jsm on startup:
+ onViewShowing: function() {
+ return Pocket.onPanelViewShowing.apply(this, arguments);
+ },
+ onViewHiding: function() {
+ return Pocket.onPanelViewHiding.apply(this, arguments);
+ },
+ onBeforeCreated: function(doc) {
+ // Bug 1223127,CUI should make this easier to do.
+ if (doc.getElementById("PanelUI-pocketView"))
+ return;
+ let view = doc.createElement("panelview");
+ view.id = "PanelUI-pocketView";
+ let panel = doc.createElement("vbox");
+ panel.setAttribute("class", "panel-subview-body");
+ view.appendChild(panel);
+ doc.getElementById("PanelUI-multiView").appendChild(view);
+ }
+ };
+
+ CustomizableUI.createWidget(pocketButton);
+ CustomizableUI.addListener(pocketButton);
+ // placed is null if location is palette
+ let placed = CustomizableUI.getPlacementOfWidget("pocket-button");
+
+ // a first time install will always have placed the button somewhere, and will
+ // not have a placement prior to creating the widget. Thus, !seenWidget &&
+ // placed.
+ if (reason == ADDON_ENABLE && !seenWidget && placed) {
+ // initially place the button after the bookmarks button if it is in the UI
+ let widgets = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR);
+ let bmbtn = widgets.indexOf("bookmarks-menu-button");
+ if (bmbtn > -1) {
+ CustomizableUI.moveWidgetWithinArea("pocket-button", bmbtn + 1);
+ }
+ }
+
+ // Uninstall the Pocket social provider if it exists, but only if we haven't
+ // already uninstalled it in this manner. That way the user can reinstall
+ // it if they prefer it without its being uninstalled every time they start
+ // the browser.
+ let SocialService;
+ try {
+ // For Firefox 51+
+ SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+ } catch (e) {
+ SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+ }
+
+ let origin = "https://getpocket.com";
+ SocialService.getProvider(origin, provider => {
+ if (provider) {
+ let pref = "social.backup.getpocket-com";
+ if (!Services.prefs.prefHasUserValue(pref)) {
+ let str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ str.data = JSON.stringify(provider.manifest);
+ Services.prefs.setComplexValue(pref, Ci.nsISupportsString, str);
+ SocialService.uninstallProvider(origin, () => {});
+ }
+ }
+ });
+
+}
+
+// PocketContextMenu
+// When the context menu is opened check if we need to build and enable pocket UI.
+var PocketContextMenu = {
+ init: function() {
+ Services.obs.addObserver(this, "on-build-contextmenu", false);
+ },
+ shutdown: function() {
+ 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) {
+ let document = win.document;
+ for (let id of ["context-pocket", "context-savelinktopocket"]) {
+ let element = document.getElementById(id);
+ if (element)
+ element.remove();
+ }
+ }
+ },
+ observe: function(aSubject, aTopic, aData) {
+ let subject = aSubject.wrappedJSObject;
+ let document = subject.menu.ownerDocument;
+ let pocketEnabled = CustomizableUI.getPlacementOfWidget("pocket-button");
+
+ 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, null, null);
+ let canPocket = pocketEnabled && (targetURI.schemeIs("http") || targetURI.schemeIs("https") ||
+ (targetURI.schemeIs("about") && ReaderMode.getOriginalUrl(targetUrl)));
+
+ let showSaveLinkToPocket = canPocket && !showSaveCurrentPageToPocket && subject.onLink;
+
+ // create menu entries if necessary
+ let menu = document.getElementById("context-pocket");
+ if (!menu) {
+ menu = createElementWithAttrs(document, "menuitem", {
+ "id": "context-pocket",
+ "label": gPocketBundle.GetStringFromName("saveToPocketCmd.label"),
+ "accesskey": gPocketBundle.GetStringFromName("saveToPocketCmd.accesskey"),
+ "oncommand": "Pocket.savePage(gContextMenu.browser, gContextMenu.browser.currentURI.spec, gContextMenu.browser.contentTitle);"
+ });
+ let sibling = document.getElementById("context-savepage");
+ if (sibling.nextSibling) {
+ sibling.parentNode.insertBefore(menu, sibling.nextSibling);
+ } else {
+ sibling.parentNode.appendChild(menu);
+ }
+ }
+ menu.hidden = !(canPocket && showSaveCurrentPageToPocket);
+
+ menu = document.getElementById("context-savelinktopocket");
+ if (!menu) {
+ menu = createElementWithAttrs(document, "menuitem", {
+ "id": "context-savelinktopocket",
+ "label": gPocketBundle.GetStringFromName("saveLinkToPocketCmd.label"),
+ "accesskey": gPocketBundle.GetStringFromName("saveLinkToPocketCmd.accesskey"),
+ "oncommand": "Pocket.savePage(gContextMenu.browser, gContextMenu.linkURL);"
+ });
+ let sibling = document.getElementById("context-savelink");
+ if (sibling.nextSibling) {
+ sibling.parentNode.insertBefore(menu, sibling.nextSibling);
+ } else {
+ sibling.parentNode.appendChild(menu);
+ }
+ }
+ menu.hidden = !showSaveLinkToPocket;
+ }
+}
+
+// PocketReader
+// Listen for reader mode setup and add our button to the reader toolbar
+var PocketReader = {
+ _hidden: true,
+ get hidden() {
+ return this._hidden;
+ },
+ set hidden(hide) {
+ hide = !!hide;
+ if (hide === this._hidden)
+ return;
+ this._hidden = hide;
+ this.update();
+ },
+ startup: function() {
+ // Setup the listeners, update will be called when the widget is added,
+ // no need to do that now.
+ let mm = Services.mm;
+ mm.addMessageListener("Reader:OnSetup", this);
+ mm.addMessageListener("Reader:Clicked-pocket-button", this);
+ },
+ shutdown: function() {
+ let mm = Services.mm;
+ mm.removeMessageListener("Reader:OnSetup", this);
+ mm.removeMessageListener("Reader:Clicked-pocket-button", this);
+ this.hidden = true;
+ },
+ update: function() {
+ if (this.hidden) {
+ Services.mm.broadcastAsyncMessage("Reader:RemoveButton", { id: "pocket-button" });
+ } else {
+ Services.mm.broadcastAsyncMessage("Reader:AddButton",
+ { id: "pocket-button",
+ title: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
+ image: "chrome://pocket/content/panels/img/pocket.svg#pocket-mark" });
+ }
+ },
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "Reader:OnSetup": {
+ // Tell the reader about our button.
+ if (this.hidden)
+ break;
+ 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");
+ pocketWidget.doCommand();
+ });
+ } else {
+ pocketWidget.doCommand();
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+
+function pktUIGetter(prop, window) {
+ return {
+ get: function() {
+ // delete any getters for properties loaded from main.js so we only load main.js once
+ delete window.pktUI;
+ delete window.pktApi;
+ delete window.pktUIMessaging;
+ Services.scriptloader.loadSubScript("chrome://pocket/content/main.js", window);
+ return window[prop];
+ },
+ configurable: true,
+ enumerable: true
+ };
+}
+
+var PocketOverlay = {
+ startup: function(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);
+ PocketContextMenu.init();
+
+ for (let win of CustomizableUI.windows) {
+ this.onWindowOpened(win);
+ }
+ },
+ shutdown: function(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) {
+ for (let id of ["panelMenu_pocket", "menu_pocket", "BMB_pocket",
+ "panelMenu_pocketSeparator", "menu_pocketSeparator",
+ "BMB_pocketSeparator"]) {
+ 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: function(window) {
+ if (window.hasOwnProperty("pktUI"))
+ return;
+ this.setWindowScripts(window);
+ this.addStyles(window);
+ this.updateWindow(window);
+ },
+ setWindowScripts: function(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: function(window) {
+ // insert our three menu items
+ let document = window.document;
+ let hidden = !CustomizableUI.getPlacementOfWidget("pocket-button");
+
+ // 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
+ "oncommand": "openUILink(Pocket.listURL, event);",
+ "hidden": hidden
+ });
+ let sep = createElementWithAttrs(document, "menuseparator", {
+ "id": "menu_pocketSeparator",
+ "hidden": hidden
+ });
+ sib.parentNode.insertBefore(menu, sib);
+ sib.parentNode.insertBefore(sep, sib);
+ }
+
+ // add to bookmarks-menu-button
+ sib = document.getElementById("BMB_bookmarksToolbar");
+ if (sib && !document.getElementById("BMB_pocket")) {
+ let menu = createElementWithAttrs(document, "menuitem", {
+ "id": "BMB_pocket",
+ "label": gPocketBundle.GetStringFromName("pocketMenuitem.label"),
+ "class": "menuitem-iconic bookmark-item subviewbutton",
+ "oncommand": "openUILink(Pocket.listURL, event);",
+ "hidden": hidden
+ });
+ let sep = createElementWithAttrs(document, "menuseparator", {
+ "id": "BMB_pocketSeparator",
+ "hidden": hidden
+ });
+ sib.parentNode.insertBefore(menu, sib);
+ sib.parentNode.insertBefore(sep, sib);
+ }
+
+ // add to PanelUI-bookmarks
+ sib = document.getElementById("panelMenuBookmarkThisPage");
+ if (sib && !document.getElementById("panelMenu_pocket")) {
+ let menu = createElementWithAttrs(document, "toolbarbutton", {
+ "id": "panelMenu_pocket",
+ "label": gPocketBundle.GetStringFromName("pocketMenuitem.label"),
+ "class": "subviewbutton cui-withicon",
+ "oncommand": "openUILink(Pocket.listURL, event);",
+ "hidden": hidden
+ });
+ let sep = createElementWithAttrs(document, "toolbarseparator", {
+ "id": "panelMenu_pocketSeparator",
+ "hidden": hidden
+ });
+ // nextSibling is no-id toolbarseparator
+ // insert separator first then button
+ sib = sib.nextSibling;
+ sib.parentNode.insertBefore(sep, sib);
+ sib.parentNode.insertBefore(menu, sib);
+ }
+ },
+ onWidgetAfterDOMChange: function(aWidgetNode) {
+ if (aWidgetNode.id != "pocket-button") {
+ return;
+ }
+ let doc = aWidgetNode.ownerDocument;
+ let hidden = !CustomizableUI.getPlacementOfWidget("pocket-button");
+ for (let prefix of ["panelMenu_", "menu_", "BMB_"]) {
+ let element = doc.getElementById(prefix + "pocket");
+ if (element) {
+ element.hidden = hidden;
+ doc.getElementById(prefix + "pocketSeparator").hidden = hidden;
+ }
+ }
+ // enable or disable reader button
+ PocketReader.hidden = hidden;
+ },
+
+ addStyles: function(win) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ utils.addSheet(this._cachedSheet, this._sheetType);
+ },
+
+ removeStyles: function(win) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ utils.removeSheet(gPocketStyleURI, this._sheetType);
+ }
+
+}
+
+// use enabled pref as a way for tests (e.g. test_contextmenu.html) to disable
+// the addon when running.
+function prefObserver(aSubject, aTopic, aData) {
+ let enabled = Services.prefs.getBoolPref("extensions.pocket.enabled");
+ if (enabled)
+ PocketOverlay.startup(ADDON_ENABLE);
+ else
+ PocketOverlay.shutdown(ADDON_DISABLE);
+}
+
+function startup(data, reason) {
+ AddonManager.getAddonByID("isreaditlater@ideashower.com", addon => {
+ if (addon && addon.isActive)
+ return;
+ setDefaultPrefs();
+ // migrate enabled pref
+ if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) {
+ Services.prefs.setBoolPref("extensions.pocket.enabled", Services.prefs.getBoolPref("browser.pocket.enabled"));
+ Services.prefs.clearUserPref("browser.pocket.enabled");
+ }
+ // watch pref change and enable/disable if necessary
+ Services.prefs.addObserver("extensions.pocket.enabled", prefObserver, false);
+ if (!Services.prefs.getBoolPref("extensions.pocket.enabled"))
+ return;
+ PocketOverlay.startup(reason);
+ });
+}
+
+function shutdown(data, reason) {
+ // For speed sake, we should only do a shutdown if we're being disabled.
+ // On an app shutdown, just let it fade away...
+ if (reason != APP_SHUTDOWN) {
+ Services.prefs.removeObserver("extensions.pocket.enabled", prefObserver);
+ PocketOverlay.shutdown(reason);
+ }
+}
+
+function install() {
+}
+
+function uninstall() {
+}