Bug 582615 - Sharing front-end [r=mfinkle]
authorMatt Brubeck <mbrubeck@mozilla.com>
Fri, 30 Jul 2010 00:51:31 -0400
changeset 66416 ba396f2efc5b7bd244ef34dd732fffaa030f20ee
parent 66415 eb6927c0b56d50bd1736fedfa8ed3e706d0e0915
child 66417 21795b8b9a8b622cb8daa313e070d8ba8effab56
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs582615
Bug 582615 - Sharing front-end [r=mfinkle]
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.js
mobile/chrome/content/browser.xul
mobile/chrome/content/content.js
mobile/chrome/content/prompt/alert.xul
mobile/chrome/content/prompt/confirm.xul
mobile/chrome/content/prompt/prompt.xul
mobile/chrome/content/prompt/promptPassword.xul
mobile/chrome/content/prompt/select.xul
mobile/chrome/content/share.xul
mobile/chrome/jar.mn
mobile/locales/en-US/chrome/browser.dtd
mobile/locales/en-US/chrome/browser.properties
mobile/themes/core/platform.css
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -1172,16 +1172,26 @@ var PageActions = {
 
     let strings = Elements.browserBundle;
     let node = this.appendItem("saveas", strings.getString("pageactions.saveas.pdf"), "");
     node.onclick = function(event) {
       PageActions._savePageAsPDF();
     }
   },
 
+  updateShare: function updateShare() {
+    this.removeItems("share");
+    let label = Elements.browserBundle.getString("pageactions.share.page");
+    let node = this.appendItem("share", label, "");
+    node.onclick = function(event) {
+      let browser = Browser.selectedBrowser;
+      SharingUI.show(browser.currentURI.spec, browser.contentTitle)
+    }
+  },
+
   appendItem: function appendItem(aType, aTitle, aDesc) {
     let container = document.getElementById("pageactions-container");
     let item = document.createElement("pageaction");
     item.setAttribute("title", aTitle);
     item.setAttribute("description", aDesc);
     item.setAttribute("type", aType);
     container.appendChild(item);
 
@@ -2106,36 +2116,113 @@ var ContextHelper = {
   },
 
   handleEvent: function handleEvent(aEvent) {
     this.sizeToContent();
   }
 };
 
 var ContextCommands = {
-  openInNewTab: function cc_openInNewTab(aEvent) {
+  openInNewTab: function cc_openInNewTab() {
     Browser.addTab(ContextHelper.popupState.linkURL, false, Browser.selectedTab);
   },
 
-  saveImage: function cc_saveImage(aEvent) {
+  saveImage: function cc_saveImage() {
     let browser = ContextHelper.popupState.target;
     saveImageURL(ContextHelper.popupState.mediaURL, null, "SaveImageTitle", false, false, browser.documentURI);
   },
 
-  editBookmark: function cc_editBookmark(aEvent) {
+  shareLink: function cc_shareLink() {
+    let state = ContextHelper.popupState;
+    SharingUI.show(state.linkURL, state.linkTitle);
+  },
+
+  shareMedia: function cc_shareMedia() {
+    let state = ContextHelper.popupState;
+    SharingUI.show(state.mediaURL, null);
+  },
+
+  editBookmark: function cc_editBookmark() {
     let target = ContextHelper.popupState.target;
     target.startEditing();
   },
 
-  removeBookmark: function cc_removeBookmark(aEvent) {
+  removeBookmark: function cc_removeBookmark() {
     let target = ContextHelper.popupState.target;
     target.remove();
   }
 }
 
+
+var SharingUI = {
+  _dialog: null,
+
+  show: function show(aURL, aTitle) {
+    this._dialog = importDialog(window, "chrome://browser/content/share.xul", null);
+    document.getElementById("share-title").value = aTitle || aURL;
+
+    BrowserUI.pushPopup(this, this._dialog);
+
+    let bbox = document.getElementById("share-buttons-box");
+    this._handlers.forEach(function(handler) {
+      let button = document.createElement("button");
+      button.setAttribute("label", handler.name);
+      button.addEventListener("command", function() {
+        handler.callback(aURL, aTitle);
+        SharingUI.hide();
+      }, false);
+      bbox.appendChild(button);
+    });
+    this._dialog.waitForClose();
+    BrowserUI.popPopup();
+  },
+
+  hide: function hide() {
+    this._dialog.close();
+    this._dialog = null;
+  },
+
+  _handlers: [
+    {
+      name: "Email",
+      callback: function callback(aURL, aTitle) {
+        let url = "mailto:?subject=" + encodeURIComponent(aTitle || "") +
+                  "&body=" + encodeURIComponent(aURL);
+        let uri = Services.io.newURI(url, null, null);
+        let extProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+                             .getService(Ci.nsIExternalProtocolService);
+        extProtocolSvc.loadUrl(uri);
+      }
+    },
+    {
+      name: "Twitter",
+      callback: function callback(aURL, aTitle) {
+        let url = "http://twitter.com/home?status=" + encodeURIComponent((aTitle ? aTitle+": " : "")+aURL);
+        Browser.addTab(url, true, Browser.selectedTab);
+      }
+    },
+    {
+      name: "Google Reader",
+      callback: function callback(aURL, aTitle) {
+        let url = "http://www.google.com/reader/link?url=" + encodeURIComponent(aURL) +
+                  "&title=" + encodeURIComponent(aTitle);
+        Browser.addTab(url, true, Browser.selectedTab);
+      }
+    },
+    {
+      name: "Facebook",
+      callback: function callback(aURL, aTitle) {
+        let url = "http://www.facebook.com/share.php?u=" + encodeURIComponent(aURL);
+        Browser.addTab(url, true, Browser.selectedTab);
+      }
+    }
+  ]
+};
+
+
 function removeBookmarksForURI(aURI) {
   //XXX blargle xpconnect! might not matter, but a method on
   // nsINavBookmarksService that takes an array of items to
   // delete would be faster. better yet, a method that takes a URI!
   let itemIds = PlacesUtils.getBookmarksForURI(aURI);
   itemIds.forEach(PlacesUtils.bookmarks.removeItem);
 
   BrowserUI.updateStar();
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -1828,26 +1828,22 @@ IdentityHandler.prototype = {
     }
 
     // Push the appropriate strings out to the UI
     this._identityPopupContentHost.textContent = host;
     this._identityPopupContentOwner.textContent = owner;
     this._identityPopupContentSupp.textContent = supplemental;
     this._identityPopupContentVerif.textContent = verifier;
 
-    // Update the find in page in-site menu
+    // Update the site menu
     FindHelperUI.updateFindInPage();
-
-    // Update the search engines results
     BrowserSearch.updatePageSearchEngines();
-
-    // Update the per site permissions results
     PageActions.updatePagePermissions();
-
     PageActions.updatePageSaveAs();
+    PageActions.updateShare();
   },
 
   show: function ih_show() {
     // dismiss any dialog which hide the identity popup
     while (BrowserUI.activeDialog)
       BrowserUI.activeDialog.close();
 
     this._identityPopup.hidden = false;
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -510,26 +510,32 @@
     </vbox>
 
     <hbox id="context-container" class="window-width window-height context-block" top="0" left="0" hidden="true">
       <vbox id="context-popup" class="dialog-dark">
         <hbox id="context-header">
           <label id="context-hint" crop="center" flex="1"/>
         </hbox>
         <richlistbox id="context-commands" onclick="ContextHelper.hide();">
-          <richlistitem id="context-openinnewtab" type="link-saveable" onclick="ContextCommands.openInNewTab(event);">
+          <richlistitem id="context-openinnewtab" type="link-saveable" onclick="ContextCommands.openInNewTab();">
             <label value="&contextOpenInNewTab.label;"/>
           </richlistitem>
-          <richlistitem id="context-saveimage" type="image-loaded" onclick="ContextCommands.saveImage(event);">
+          <richlistitem id="context-saveimage" type="image-loaded" onclick="ContextCommands.saveImage();">
             <label value="&contextSaveImage.label;"/>
           </richlistitem>
-          <richlistitem id="context-editbookmark" type="edit-bookmark" onclick="ContextCommands.editBookmark(event);">
+          <richlistitem id="context-share-link" type="link" onclick="ContextCommands.shareLink();">
+            <label value="&contextShareLink.label;"/>
+          </richlistitem>
+          <richlistitem id="context-share-image" type="image" onclick="ContextCommands.shareMedia();">
+            <label value="&contextShareImage.label;"/>
+          </richlistitem>
+          <richlistitem id="context-editbookmark" type="edit-bookmark" onclick="ContextCommands.editBookmark();">
             <label value="&contextEditBookmark.label;"/>
           </richlistitem>
-          <richlistitem id="context-removebookmark" type="edit-bookmark" onclick="ContextCommands.removeBookmark(event);">
+          <richlistitem id="context-removebookmark" type="edit-bookmark" onclick="ContextCommands.removeBookmark();">
             <label value="&contextRemoveBookmark.label;"/>
           </richlistitem>
         </richlistbox>
       </vbox>
     </hbox>
 
     <!-- alerts for content -->
     <hbox id="alerts-container" hidden="true" align="start" class="dialog-dark" top="0" left="0"
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -751,16 +751,17 @@ var ContextHandler = {
   handleEvent: function ch_handleEvent(aEvent) {
     if (aEvent.getPreventDefault())
       return;
 
     let state = {
       types: [],
       label: "",
       linkURL: "",
+      linkTitle: "",
       linkProtocol: null,
       mediaURL: ""
     };
 
     let popupNode = aEvent.originalTarget;
 
     // Do checks for nodes that never have children.
     if (popupNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
@@ -779,16 +780,17 @@ var ContextHandler = {
         if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
             (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href) ||
             elem instanceof Ci.nsIDOMHTMLLinkElement ||
             elem.getAttributeNS(kXLinkNamespace, "type") == "simple") {
 
           // Target is a link or a descendant of a link.
           state.types.push("link");
           state.label = state.linkURL = this._getLinkURL(elem);
+          state.linkTitle = popupNode.textContent || popupNode.title;
           state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
           break;
         }
       }
 
       elem = elem.parentNode;
     }
 
--- a/mobile/chrome/content/prompt/alert.xul
+++ b/mobile/chrome/content/prompt/alert.xul
@@ -14,17 +14,17 @@
   </keyset>
 
   <commandset>
     <command id="cmd_ok" oncommand="document.getElementById('prompt-alert-dialog').close()"/>
     <command id="cmd_cancel" oncommand="document.getElementById('prompt-alert-dialog').close()"/>
   </commandset>
 
   <vbox class="prompt-header" flex="1">
-    <label id="prompt-alert-title" class="prompt-title" crop="center"/>
+    <label id="prompt-alert-title" class="prompt-title" crop="center" flex="1"/>
 
     <scrollbox orient="vertical" class="prompt-message" flex="1">
       <description id="prompt-alert-message"/>
     </scrollbox>
 
     <button id="prompt-alert-checkbox" type="checkbox" class="button-checkbox" collapsed="true" pack="start" flex="1">
       <image class="button-image-icon"/>
       <description id="prompt-alert-checkbox-label" class="prompt-checkbox-label" flex="1"/>
--- a/mobile/chrome/content/prompt/confirm.xul
+++ b/mobile/chrome/content/prompt/confirm.xul
@@ -14,17 +14,17 @@
   </keyset>
 
   <commandset>
     <command id="cmd_ok" oncommand="document.getElementById('prompt-confirm-dialog').PromptHelper.closeConfirm(true)"/>
     <command id="cmd_cancel" oncommand="document.getElementById('prompt-confirm-dialog').PromptHelper.closeConfirm(false)"/>
   </commandset>
 
   <vbox class="prompt-header" flex="1">
-    <label id="prompt-confirm-title" class="prompt-title" crop="center"/>
+    <label id="prompt-confirm-title" class="prompt-title" crop="center" flex="1"/>
 
     <scrollbox orient="vertical" class="prompt-message" flex="1">
       <description id="prompt-confirm-message"/>
     </scrollbox>
 
     <button id="prompt-confirm-checkbox" type="checkbox" class="button-checkbox" collapsed="true" pack="start" flex="1">
       <image class="button-image-icon"/>
       <description id="prompt-confirm-checkbox-label" class="prompt-checkbox-label" flex="1"/>
--- a/mobile/chrome/content/prompt/prompt.xul
+++ b/mobile/chrome/content/prompt/prompt.xul
@@ -14,17 +14,17 @@
   </keyset>
 
   <commandset>
     <command id="cmd_ok" oncommand="document.getElementById('prompt-prompt-dialog').PromptHelper.closePrompt(true)"/>
     <command id="cmd_cancel" oncommand="document.getElementById('prompt-prompt-dialog').PromptHelper.closePrompt(false)"/>
   </commandset>
 
   <vbox class="prompt-header" flex="1">
-    <label id="prompt-prompt-title" class="prompt-title" crop="center"/>
+    <label id="prompt-prompt-title" class="prompt-title" crop="center" flex="1"/>
 
     <scrollbox orient="vertical" class="prompt-message" flex="1">
       <description id="prompt-prompt-message"/>
     </scrollbox>
 
     <textbox id="prompt-prompt-textbox"/>
 
     <button id="prompt-prompt-checkbox" type="checkbox" class="button-checkbox" collapsed="true" pack="start" flex="1">
--- a/mobile/chrome/content/prompt/promptPassword.xul
+++ b/mobile/chrome/content/prompt/promptPassword.xul
@@ -19,17 +19,17 @@
   </keyset>
 
   <commandset>
     <command id="cmd_ok" oncommand="document.getElementById('prompt-password-dialog').PromptHelper.closePassword(true)"/>
     <command id="cmd_cancel" oncommand="document.getElementById('prompt-password-dialog').PromptHelper.closePassword(false)"/>
   </commandset>
 
   <vbox class="prompt-header" flex="1">
-    <label id="prompt-password-title" class="prompt-title" crop="center"/>
+    <label id="prompt-password-title" class="prompt-title" crop="center" flex="1"/>
 
     <scrollbox orient="vertical" class="prompt-message" flex="1">
       <description id="prompt-password-message"/>
     </scrollbox>
 
     <grid>
       <columns>
         <column flex="1"/>
--- a/mobile/chrome/content/prompt/select.xul
+++ b/mobile/chrome/content/prompt/select.xul
@@ -14,17 +14,17 @@
   </keyset>
 
   <commandset>
     <command id="cmd_ok" oncommand="document.getElementById('prompt-select-dialog').PromptHelper.closeSelect(true)"/>
     <command id="cmd_cancel" oncommand="document.getElementById('prompt-select-dialog').PromptHelper.closeSelect(false)"/>
   </commandset>
 
   <vbox class="prompt-header" flex="1">
-    <label id="prompt-select-title" class="prompt-title" crop="center"/>
+    <label id="prompt-select-title" class="prompt-title" crop="center" flex="1"/>
 
     <scrollbox orient="vertical" class="prompt-message" flex="1">
       <description id="prompt-select-message"/>
     </scrollbox>
 
     <menulist id="prompt-select-list" class="button-dark"/>
   </vbox>
 
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/content/share.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE prompt SYSTEM "chrome://browser/locale/prompt.dtd">
+
+<dialog id="share-dialog"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <keyset>
+    <key keycode="VK_ESCAPE" command="cmd_cancel"/>
+  </keyset>
+
+  <commandset>
+    <command id="cmd_cancel" oncommand="SharingUI.hide();"/>
+  </commandset>
+
+  <hbox class="prompt-header">
+    <label id="share-title" class="prompt-title" crop="center" flex="1"/>
+  </hbox>
+
+  <hbox id="share-buttons-box" class="prompt-buttons"/>
+</dialog>
--- a/mobile/chrome/jar.mn
+++ b/mobile/chrome/jar.mn
@@ -52,12 +52,13 @@ chrome.jar:
   content/downloads.js                 (content/downloads.js)
   content/console.js                   (content/console.js)
   content/prompt/alert.xul             (content/prompt/alert.xul)
   content/prompt/confirm.xul           (content/prompt/confirm.xul)
   content/prompt/prompt.xul            (content/prompt/prompt.xul)
   content/prompt/promptPassword.xul    (content/prompt/promptPassword.xul)
   content/prompt/select.xul            (content/prompt/select.xul)
   content/prompt/prompt.js             (content/prompt/prompt.js)
+  content/share.xul                    (content/share.xul)
   content/AnimatedZoom.js              (content/AnimatedZoom.js)
   content/sync.js                      (content/sync.js)
 
 % override chrome://global/content/config.xul chrome://browser/content/config.xul
--- a/mobile/locales/en-US/chrome/browser.dtd
+++ b/mobile/locales/en-US/chrome/browser.dtd
@@ -81,10 +81,12 @@
 <!ENTITY consoleClear.label        "Clear">
 <!ENTITY consoleEvaluate.label     "…">
 <!ENTITY consoleErrFile.label      "Source File:">
 <!ENTITY consoleErrLine.label      "Line:">
 <!ENTITY consoleErrColumn.label    "Column:">
 
 <!ENTITY contextOpenInNewTab.label    "Open Link in New Tab">
 <!ENTITY contextSaveImage.label       "Save Image">
+<!ENTITY contextShareLink.label       "Share Link">
+<!ENTITY contextShareImage.label      "Share Image">
 <!ENTITY contextEditBookmark.label    "Edit">
 <!ENTITY contextRemoveBookmark.label  "Remove">
--- a/mobile/locales/en-US/chrome/browser.properties
+++ b/mobile/locales/en-US/chrome/browser.properties
@@ -138,16 +138,17 @@ tabs.closeWarningPromptMe=Warn me when I
 # LOCALIZATION NOTE: homepage.custom2 is the text displayed on the selector button if
 # the user selects a webpage to be the startpage. We can't display the entire URL
 # or webpage title on the menulist
 homepage.custom2=Custom Page
 
 # Page Actions
 pageactions.saveas.pdf=Save As PDF
 pageactions.search.addNew=Add Search Engine
+pageactions.share.page=Share Page
 pageactions.password.forget=Forget Password
 pageactions.reset=Clear Site Preferences
 pageactions.geo=Location
 pageactions.popup=Popups
 pageactions.offline-app=Offline Storage
 pageactions.password=Password
 pageactions.findInPage=Find In Page
 
--- a/mobile/themes/core/platform.css
+++ b/mobile/themes/core/platform.css
@@ -625,12 +625,11 @@ progressmeter {
   border: 3px solid #aaa;
   -moz-border-top-colors: -moz-initial;
   -moz-border-right-colors: -moz-initial;
   -moz-border-bottom-colors: -moz-initial;
   -moz-border-left-colors: -moz-initial;
   -moz-border-radius: 8px;
 }
 
-.progress-bar
-{
+.progress-bar {
   background-color: #8db8d8;
 }