diff options
Diffstat (limited to 'browser/extensions/pocket/bootstrap.js')
-rw-r--r-- | browser/extensions/pocket/bootstrap.js | 511 |
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() { +} |