merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 31 Jul 2014 13:01:27 +0200
changeset 197034 ef1a3ed657fc175b9a28f249cfd065ca8cee796f
parent 197021 aa176fcc56b8b38b271223c6e12aa999d4a9453f (current diff)
parent 197033 ca8d8f284b99f62e1b150f82b8788e6690f2595a (diff)
child 197035 597be00ffc53282a844045ae1e50b096c02b4c0a
child 197124 6e99b22bd6ea3b1b577917bd31e9c5bff3029aa8
child 197161 608811d9af97a52b344c6b362b7392e718a94f94
push id47022
push usercbook@mozilla.com
push dateThu, 31 Jul 2014 11:07:00 +0000
treeherdermozilla-inbound@597be00ffc53 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -816,36 +816,36 @@ pref("plugin.state.f5 ssl vpn plugin", 2
 pref("plugin.state.f5 sam inspection host plugin", 2);
 #endif
 
 // Roblox Launcher Plugin, bug 1024073
 #ifdef XP_WIN
 pref("plugin.state.nprobloxproxy", 2);
 #endif
 #ifdef XP_MACOSX
-pref("plugins.state.nproblox", 2);
+pref("plugin.state.nproblox", 2);
 #endif
 
 // Box Edit, bug 1029654
 #ifdef XP_WIN
-pref("plugins.state.npboxedit", 2);
+pref("plugin.state.npboxedit", 2);
 #endif
 #ifdef XP_MACOSX
-pref("plugins.state.box edit", 2);
+pref("plugin.state.box edit", 2);
 #endif
 
 // Nexus Personal, bug 1024965
 #ifdef XP_WIN
-pref("plugins.state.np_prsnl", 2);
+pref("plugin.state.np_prsnl", 2);
 #endif
 #ifdef XP_MACOSX
-pref("plugins.state.personalplugin", 2);
+pref("plugin.state.personalplugin", 2);
 #endif
 #ifdef UNIX_BUT_NOT_MAC
-pref("plugins.state.libplugins", 2);
+pref("plugin.state.libplugins", 2);
 #endif
 
 // display door hanger if flash not installed
 pref("plugins.notifyMissingFlash", true);
 
 #ifdef XP_MACOSX
 pref("browser.preferences.animateFadeIn", true);
 #else
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -459,21 +459,22 @@ SocialShare = {
   },
 
   _createFrame: function() {
     let panel = this.panel;
     if (!SocialUI.enabled || this.iframe)
       return;
     this.panel.hidden = false;
     // create and initialize the panel for this window
-    let iframe = document.createElement("iframe");
+    let iframe = document.createElement("browser");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-share-frame");
     iframe.setAttribute("context", "contentAreaContextMenu");
     iframe.setAttribute("tooltip", "aHTMLTooltip");
+    iframe.setAttribute("disableglobalhistory", "true");
     iframe.setAttribute("flex", "1");
     panel.appendChild(iframe);
     this.populateProviderMenu();
   },
 
   getSelectedProvider: function() {
     let provider;
     let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
@@ -586,17 +587,25 @@ SocialShare = {
     this.anchor.setAttribute("open", "true");
     this.iframe.addEventListener("click", this._onclick, true);
   },
 
   onHidden: function() {
     this.anchor.removeAttribute("open");
     this.iframe.removeEventListener("click", this._onclick, true);
     this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
+    // make sure that the frame is unloaded after it is hidden
+    this.iframe.docShell.createAboutBlankContentViewer(null);
     this.currentShare = null;
+    // share panel use is over, purge any history
+    if (this.iframe.sessionHistory) {
+      let purge = this.iframe.sessionHistory.count;
+      if (purge > 0)
+        this.iframe.sessionHistory.PurgeHistory(purge);
+    }
   },
 
   setErrorMessage: function() {
     let iframe = this.iframe;
     if (!iframe)
       return;
 
     iframe.removeAttribute("src");
@@ -697,16 +706,24 @@ SocialShare = {
             SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
           }
         }, 0);
         let evt = iframe.contentDocument.createEvent("CustomEvent");
         evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
         iframe.contentDocument.documentElement.dispatchEvent(evt);
       }, true);
     }
+    // if the user switched between share providers we do not want that history
+    // available.
+    if (iframe.sessionHistory) {
+      let purge = iframe.sessionHistory.count;
+      if (purge > 0)
+        iframe.sessionHistory.PurgeHistory(purge);
+    }
+
     // always ensure that origin belongs to the endpoint
     let uri = Services.io.newURI(shareEndpoint, null, null);
     iframe.setAttribute("origin", provider.origin);
     iframe.setAttribute("src", shareEndpoint);
 
     let anchor = document.getAnonymousElementByAttribute(this.anchor, "class", "toolbarbutton-icon");
     this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
     Social.setErrorListener(iframe, this.setErrorMessage.bind(this));
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -76,17 +76,17 @@ function runSocialTestWithProvider(manif
   let manifests = Array.isArray(manifest) ? manifest : [manifest];
 
   // Check that none of the provider's content ends up in history.
   function finishCleanUp() {
     ok(!SocialSidebar.provider, "no provider in sidebar");
     SessionStore.setWindowValue(window, "socialSidebar", "");
     for (let i = 0; i < manifests.length; i++) {
       let m = manifests[i];
-      for (let what of ['sidebarURL', 'workerURL', 'iconURL']) {
+      for (let what of ['sidebarURL', 'workerURL', 'iconURL', 'shareURL', 'markURL']) {
         if (m[what]) {
           yield promiseSocialUrlNotRemembered(m[what]);
         }
       };
     }
     for (let i = 0; i < gURLsNotRemembered.length; i++) {
       yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]);
     }
--- a/browser/base/content/test/social/share.html
+++ b/browser/base/content/test/social/share.html
@@ -3,16 +3,17 @@
     <meta charset="utf-8">
     <script>
       var shareData;
       addEventListener("OpenGraphData", function(e) {
         shareData = JSON.parse(e.detail);
         var port = navigator.mozSocial.getWorker().port;
         port.postMessage({topic: "share-data-message", result: shareData});
         // share windows self-close
+        history.back(); // bug 1042991, ensure history is available
         window.close();
       })
     </script>
   </head>
   <body>
     <p>This is a test social share window.</p>
   </body>
 </html>
--- a/browser/components/preferences/permissions.js
+++ b/browser/components/preferences/permissions.js
@@ -231,20 +231,27 @@ var gPermissionManager = {
         if (this._lastPermissionSortColumn.id == "statusCol") {
           gTreeUtils.sort(this._tree, this._view, this._permissions,
                           this._permissionsComparator,
                           this._lastPermissionSortColumn, 
                           this._lastPermissionSortAscending);
         }
         this._tree.treeBoxObject.invalidate();
       }
-      // No UI other than this window causes this method to be sent a "deleted"
-      // notification, so we don't need to implement it since Delete is handled
-      // directly by the Permission Removal handlers. If that ever changes, those
-      // implementations will have to move into here. 
+      else if (aData == "deleted") {
+        for (var i = 0; i < this._permissions.length; i++) {
+          if (this._permissions[i].host == permission.host) {
+            this._permissions.splice(i, 1);
+            this._view._rowCount--;
+            this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1);
+            this._tree.treeBoxObject.invalidate();
+            break;
+          }
+        }
+      }
     }
   },
   
   onPermissionSelected: function ()
   {
     var hasSelection = this._tree.view.selection.count > 0;
     var hasRows = this._tree.view.rowCount > 0;
     document.getElementById("removePermission").disabled = !hasRows || !hasSelection;
--- a/browser/components/preferences/tests/browser_cookies_exceptions.js
+++ b/browser/components/preferences/tests/browser_cookies_exceptions.js
@@ -109,16 +109,27 @@ function windowLoad(event, win, dialog) 
         is(tree.view.rowCount, 0, "adding unrelated permission should not change display");
       },
       observances: [{ type: "popup", host: "test.com", data: "added",
                       capability: deny }],
       cleanUp: function() {
         pm.remove("test.com", "popup");
       },
     },
+    {
+      test: function() {
+        url.value = "test.com";
+        btnAllow.doCommand();
+        pm.remove("test.com", "cookie");
+        is(tree.view.rowCount, 0, "display should update when cookie permission is deleted");
+      },
+      observances: [{ type: "cookie", host: "test.com", data: "added",
+                      capability: allow },
+                    { type: "cookie", host: "test.com", data: "deleted" }]
+    },
   ];
 
   let permObserver = {
     observe: function(aSubject, aTopic, aData) {
       if (aTopic != "perm-changed")
         return;
 
       if (tests[currentTest].observances.length == 0) {
--- a/mobile/android/base/tests/components/AppMenuComponent.java
+++ b/mobile/android/base/tests/components/AppMenuComponent.java
@@ -9,32 +9,36 @@ import static org.mozilla.gecko.tests.he
 import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
 
 import java.util.List;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.menu.MenuItemActionBar;
 import org.mozilla.gecko.menu.MenuItemDefault;
 import org.mozilla.gecko.tests.UITestContext;
+import org.mozilla.gecko.tests.helpers.DeviceHelper;
 import org.mozilla.gecko.tests.helpers.WaitHelper;
 import org.mozilla.gecko.util.HardwareUtils;
 
 import android.view.View;
 
 import com.jayway.android.robotium.solo.Condition;
 import com.jayway.android.robotium.solo.RobotiumUtils;
 import com.jayway.android.robotium.solo.Solo;
 
 /**
  * A class representing any interactions that take place on the app menu.
  */
 public class AppMenuComponent extends BaseComponent {
+    private Boolean hasLegacyMenu = null;
+
     public enum MenuItem {
         FORWARD(R.string.forward),
         NEW_TAB(R.string.new_tab),
+        PAGE(R.string.page),
         RELOAD(R.string.reload);
 
         private final int resourceID;
         private String stringResource;
 
         MenuItem(final int resourceID) {
             this.resourceID = resourceID;
         }
@@ -43,24 +47,93 @@ public class AppMenuComponent extends Ba
             if (stringResource == null) {
                 stringResource = solo.getString(resourceID);
             }
 
             return stringResource;
         }
     };
 
+    public enum PageMenuItem {
+        SAVE_AS_PDF(R.string.save_as_pdf);
+
+        private static final MenuItem PARENT_MENU = MenuItem.PAGE;
+
+        private final int resourceID;
+        private String stringResource;
+
+        PageMenuItem(final int resourceID) {
+            this.resourceID = resourceID;
+        }
+
+        public String getString(final Solo solo) {
+            if (stringResource == null) {
+                stringResource = solo.getString(resourceID);
+            }
+
+            return stringResource;
+        }
+    };
+
     public AppMenuComponent(final UITestContext testContext) {
         super(testContext);
     }
 
     private void assertMenuIsNotOpen() {
         fAssertFalse("Menu is not open", isMenuOpen());
     }
 
+    /**
+     * Legacy Android devices doesn't have hierarchical menus. Sub-menus, such as "Page", are missing in these devices.
+     * Try to determine if the menu item "Page" is present.
+     *
+     * TODO : This fragile way to determine legacy menus should be replaced with a check for 6-panel menu item.
+     *
+     * @return true if there is a legacy menu.
+     */
+    private boolean hasLegacyMenu() {
+        if (hasLegacyMenu == null) {
+            hasLegacyMenu = findAppMenuItemView(MenuItem.PAGE.getString(mSolo)) == null;
+        }
+
+        return hasLegacyMenu;
+    }
+
+    public void assertMenuItemIsDisabledAndVisible(PageMenuItem pageMenuItem) {
+        openAppMenu();
+
+        if (!hasLegacyMenu()) {
+            // Non-legacy devices have hierarchical menu, check for parent menu item "page".
+            final View parentMenuItemView = findAppMenuItemView(MenuItem.PAGE.getString(mSolo));
+            if (parentMenuItemView.isEnabled()) {
+                fAssertTrue("The parent 'page' menu item is enabled", parentMenuItemView.isEnabled());
+                fAssertEquals("The parent 'page' menu item is visible", View.VISIBLE,
+                        parentMenuItemView.getVisibility());
+
+                // Parent menu "page" is enabled, open page menu and check for menu item represented by pageMenuItem.
+                pressMenuItem(MenuItem.PAGE.getString(mSolo));
+
+                final View pageMenuItemView = findAppMenuItemView(pageMenuItem.getString(mSolo));
+                fAssertFalse("The page menu item is not enabled", pageMenuItemView.isEnabled());
+                fAssertEquals("The page menu item is visible", View.VISIBLE, pageMenuItemView.getVisibility());
+            } else {
+                fAssertFalse("The parent 'page' menu item is not enabled", parentMenuItemView.isEnabled());
+                fAssertEquals("The parent 'page' menu item is visible", View.VISIBLE, parentMenuItemView.getVisibility());
+            }
+        } else {
+            // Legacy devices don't have parent menu item "page", check for menu item represented by pageMenuItem.
+            final View pageMenuItemView = findAppMenuItemView(pageMenuItem.getString(mSolo));
+            fAssertFalse("The page menu item is not enabled", pageMenuItemView.isEnabled());
+            fAssertEquals("The page menu item is visible", View.VISIBLE, pageMenuItemView.getVisibility());
+        }
+
+        // Close the App Menu.
+        mSolo.goBack();
+    }
+
     private View getOverflowMenuButtonView() {
         return mSolo.getView(R.id.menu);
     }
 
     /**
      * Try to find a MenuItemActionBar/MenuItemDefault with the given text set as contentDescription / text.
      *
      * Will return null when the Android legacy menu is in use.
@@ -82,42 +155,71 @@ public class AppMenuComponent extends Ba
             if (menuItem.getText().equals(text)) {
                 return menuItem;
             }
         }
 
         return null;
     }
 
-    public void pressMenuItem(MenuItem menuItem) {
-        openAppMenu();
+    /**
+     * Helper function to let Robotium locate and click menu item from legacy Android menu (devices with Android 2.x).
+     *
+     * Robotium will also try to open the menu if there are no open dialog.
+     *
+     * @param menuItemText, The title of menu item to open.
+     */
+    private void pressLegacyMenuItem(final String menuItemTitle) {
+        mSolo.clickOnMenuItem(menuItemTitle, true);
+    }
 
-        final String text = menuItem.getString(mSolo);
-        final View menuItemView = findAppMenuItemView(text);
+    private void pressMenuItem(final String menuItemTitle) {
+        fAssertTrue("Menu is open", isMenuOpen(menuItemTitle));
 
-        if (menuItemView != null) {
-            fAssertTrue("The menu item is enabled", menuItemView.isEnabled());
-            fAssertEquals("The menu item is visible", View.VISIBLE, menuItemView.getVisibility());
+        if (!hasLegacyMenu()) {
+            final View menuItemView = findAppMenuItemView(menuItemTitle);
+
+            fAssertTrue(String.format("The menu item %s is enabled", menuItemTitle), menuItemView.isEnabled());
+            fAssertEquals(String.format("The menu item %s is visible", menuItemTitle), View.VISIBLE,
+                    menuItemView.getVisibility());
 
             mSolo.clickOnView(menuItemView);
         } else {
-            // We could not find a view representing this menu item: Let's let Robotium try to
-            // locate and click it in the legacy Android menu (devices with Android 2.x).
-            //
-            // Even though we already opened the menu to see if we can locate the menu item,
-            // Robotium will also try to open the menu if it doesn't find an open dialog (Does
-            // not happen in this case).
-            mSolo.clickOnMenuItem(text, true);
+            pressLegacyMenuItem(menuItemTitle);
         }
     }
 
+    private void pressSubMenuItem(final String parentMenuItemTitle, final String childMenuItemTitle) {
+        openAppMenu();
+
+        if (!hasLegacyMenu()) {
+            pressMenuItem(parentMenuItemTitle);
+
+            // Child menu item is not pressed yet, Click on it.
+            pressMenuItem(childMenuItemTitle);
+        } else {
+            pressLegacyMenuItem(childMenuItemTitle);
+        }
+    }
+
+    public void pressMenuItem(MenuItem menuItem) {
+        openAppMenu();
+        pressMenuItem(menuItem.getString(mSolo));
+    }
+
+    public void pressMenuItem(final PageMenuItem pageMenuItem) {
+        pressSubMenuItem(PageMenuItem.PARENT_MENU.getString(mSolo), pageMenuItem.getString(mSolo));
+    }
+
     private void openAppMenu() {
         assertMenuIsNotOpen();
 
-        if (HardwareUtils.hasMenuButton()) {
+        // This is a hack needed for tablets where the OverflowMenuButton is always in the GONE state,
+        // so we press the menu key instead.
+        if (HardwareUtils.hasMenuButton() || DeviceHelper.isTablet()) {
             mSolo.sendKey(Solo.MENU);
         } else {
             pressOverflowMenuButton();
         }
 
         waitForMenuOpen();
     }
 
@@ -125,20 +227,34 @@ public class AppMenuComponent extends Ba
         final View overflowMenuButton = getOverflowMenuButtonView();
 
         fAssertTrue("The overflow menu button is enabled", overflowMenuButton.isEnabled());
         fAssertEquals("The overflow menu button is visible", View.VISIBLE, overflowMenuButton.getVisibility());
 
         mSolo.clickOnView(overflowMenuButton, true);
     }
 
+    /**
+    * Determines whether the app menu is open by searching for the text "New tab".
+    *
+    * @return true if app menu is open.
+    */
     private boolean isMenuOpen() {
-        // The presence of the "New tab" menu item is our best guess about whether
-        // the menu is open or not.
-        return mSolo.searchText(MenuItem.NEW_TAB.getString(mSolo));
+        return isMenuOpen(MenuItem.NEW_TAB.getString(mSolo));
+    }
+
+    /**
+     * Determines whether the app menu is open by searching for the text in menuItemTitle.
+     *
+     * @param menuItemTitle, The contentDescription of menu item to search.
+     *
+     * @return true if app menu is open.
+     */
+    private boolean isMenuOpen(String menuItemTitle) {
+        return mSolo.searchText(menuItemTitle);
     }
 
     private void waitForMenuOpen() {
         WaitHelper.waitFor("menu to open", new Condition() {
             @Override
             public boolean isSatisfied() {
                 return isMenuOpen();
             }
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -117,16 +117,17 @@ skip-if = android_version == "10"
 #[testCheck2]
 #[testBrowserProviderPerf]
 
 # Using UITest
 #[testAboutHomePageNavigation] # see bug 947550, bug 979038 and bug 977952
 [testAboutHomeVisibility]
 # disabled on Android 2.3; bug 946656
 skip-if = android_version == "10"
+[testAppMenuPathways]
 [testEventDispatcher]
 [testInputConnection]
 # disabled on Android 2.3; bug 1025968
 skip-if = android_version == "10"
 [testJavascriptBridge]
 [testNativeCrypto]
 [testSessionHistory]
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAppMenuPathways.java
@@ -0,0 +1,34 @@
+package org.mozilla.gecko.tests;
+
+import org.mozilla.gecko.tests.components.AppMenuComponent;
+import org.mozilla.gecko.tests.helpers.GeckoHelper;
+import org.mozilla.gecko.tests.helpers.NavigationHelper;
+
+/**
+ * Set of tests to test UI App menu and submenus the user interact with.
+ */
+public class testAppMenuPathways extends UITest {
+
+    /**
+     * Robocop supports only a single test function per test class. Therefore, we
+     * have a single top-level test function that dispatches to sub-tests.
+     */
+    public void testAppMenuPathways() {
+        GeckoHelper.blockForReady();
+
+        _testSaveAsPDFPathway();
+    }
+
+    public void _testSaveAsPDFPathway() {
+        // Page menu should be disabled in about:home.
+        mAppMenu.assertMenuItemIsDisabledAndVisible(AppMenuComponent.PageMenuItem.SAVE_AS_PDF);
+
+        // Navigate to a page to test save as pdf functionality.
+        NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
+        mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+
+        // Test save as pdf functionality.
+        // The following call doesn't wait for the resulting pdf but checks that no exception are thrown.
+        mAppMenu.pressMenuItem(AppMenuComponent.PageMenuItem.SAVE_AS_PDF);
+    }
+}
\ No newline at end of file
--- a/mobile/android/search/java/org/mozilla/search/Constants.java
+++ b/mobile/android/search/java/org/mozilla/search/Constants.java
@@ -20,9 +20,11 @@ public class Constants {
     public static final String PRESEARCH_FRAGMENT = "org.mozilla.search.PRESEARCH_FRAGMENT";
     public static final String SEARCH_FRAGMENT = "org.mozilla.search.SEARCH_FRAGMENT";
 
     public static final String YAHOO_WEB_SEARCH_BASE_URL = "https://search.yahoo.com/search?p=";
     public static final String YAHOO_WEB_SEARCH_RESULTS_FILTER = "//search.yahoo.com";
 
     public static final String INTENT_START_SEARCH = "org.mozilla.search.intent.START_SEARCH";
     public static final String INTENT_START_SEARCH_QUERY_EXTRA = "org.mozilla.search.intent.START_SEARCH_QUERY_EXTRA";
+
+    public static final int SUGGESTION_MAX = 5;
 }
--- a/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PreSearchFragment.java
@@ -3,43 +3,50 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.search;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.database.Cursor;
 import android.graphics.Rect;
+import android.net.Uri;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.CursorLoader;
 import android.support.v4.content.Loader;
 import android.support.v4.widget.SimpleCursorAdapter;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.ListView;
 
+import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.SearchHistory;
 import org.mozilla.search.AcceptsSearchQuery.SuggestionAnimation;
 
 /**
  * This fragment is responsible for managing the card stream.
  */
 public class PreSearchFragment extends Fragment {
 
     private AcceptsSearchQuery searchListener;
     private SimpleCursorAdapter cursorAdapter;
 
     private ListView listView;
 
-    private final String[] PROJECTION = new String[]{SearchHistory.QUERY, SearchHistory._ID};
+    private static final String[] PROJECTION = new String[]{ SearchHistory.QUERY, SearchHistory._ID };
+
+    // Limit search history query results to 5 items. This value matches the number of search
+    // suggestions we return in SearchFragment.
+    private static final Uri SEARCH_HISTORY_URI = SearchHistory.CONTENT_URI.buildUpon().
+            appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(Constants.SUGGESTION_MAX)).build();
 
     private static final int LOADER_ID_SEARCH_HISTORY = 1;
 
     public PreSearchFragment() {
         // Mandatory empty constructor for Android's Fragment.
     }
 
     @Override
@@ -123,18 +130,18 @@ public class PreSearchFragment extends F
         super.onDestroyView();
         listView.setAdapter(null);
         listView = null;
     }
 
     private class SearchHistoryLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            return new CursorLoader(getActivity(), SearchHistory.CONTENT_URI,
-                    PROJECTION, null, null, SearchHistory.DATE_LAST_VISITED + " DESC");
+            return new CursorLoader(getActivity(), SEARCH_HISTORY_URI, PROJECTION, null, null,
+                    SearchHistory.DATE_LAST_VISITED + " DESC");
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
             if (cursorAdapter != null) {
                 cursorAdapter.swapCursor(data);
             }
         }
new file mode 100644
--- /dev/null
+++ b/mobile/android/search/java/org/mozilla/search/SearchWidget.java
@@ -0,0 +1,124 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.search;
+
+import org.mozilla.gecko.AppConstants;
+
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+import android.util.Log;
+
+/* Provides a really simple widget with two buttons, one to launch Fennec
+ * and one to launch the search activity. All intents are actually sent back
+ * here and then forwarded on to start the real activity. */
+public class SearchWidget extends AppWidgetProvider {
+    final private static String LOGTAG = "GeckoSearchWidget";
+
+    final public static String ACTION_LAUNCH_BROWSER = "org.mozilla.widget.LAUNCH_BROWSER";
+    final public static String ACTION_LAUNCH_SEARCH = "org.mozilla.widget.LAUNCH_SEARCH";
+
+    @SuppressLint("NewApi")
+    @Override
+    public void onUpdate(final Context context, final AppWidgetManager manager, final int[] ids) {
+        for (int id : ids) {
+            final Bundle bundle;
+            if (AppConstants.Versions.feature16Plus) {
+                bundle = manager.getAppWidgetOptions(id);
+            } else {
+                bundle = null;
+            }
+            addView(manager, context, id, bundle);
+        }
+
+        super.onUpdate(context, manager, ids);
+    }
+
+    @SuppressLint("NewApi")
+    @Override
+    public void onAppWidgetOptionsChanged(final Context context,
+                                          final AppWidgetManager manager,
+                                          final int id,
+                                          final Bundle options) {
+        addView(manager, context, id, options);
+        if (AppConstants.Versions.feature16Plus) {
+            super.onAppWidgetOptionsChanged(context, manager, id, options);
+        }
+    }
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        // This will hold the intent to redispatch
+        final Intent redirect;
+        Log.i(LOGTAG, "Got intent  " + intent.getAction());
+        if (intent.getAction().equals(ACTION_LAUNCH_BROWSER)) {
+            redirect = buildRedirectIntent(Intent.ACTION_VIEW,
+                                           AppConstants.ANDROID_PACKAGE_NAME,
+                                           AppConstants.BROWSER_INTENT_CLASS_NAME,
+                                           intent);
+        } else if (intent.getAction().equals(ACTION_LAUNCH_SEARCH)) {
+            redirect = buildRedirectIntent(Intent.ACTION_VIEW,
+                                           AppConstants.SEARCH_PACKAGE_NAME,
+                                           AppConstants.SEARCH_INTENT_CLASS_NAME,
+                                           intent);
+        } else {
+            redirect = null;
+        }
+
+        if (redirect != null) {
+            try {
+                context.startActivity(redirect);
+            } catch(Exception ex) {
+                // When this is built stand alone, its hardcoded to try and launch nightly.
+                // If that fails, just fire a generic VIEW intent.
+                Intent redirect2 = buildRedirectIntent(Intent.ACTION_VIEW, null, null, intent);
+                context.startActivity(redirect2);
+            }
+        }
+
+        super.onReceive(context, intent);
+    }
+
+    // Utility to create the view for this widget and attach any event listeners to it
+    private void addView(final AppWidgetManager manager, final Context context, final int id, final Bundle options) {
+        final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.search_widget);
+
+        addClickIntent(context, views, R.id.search_button, ACTION_LAUNCH_SEARCH);
+        addClickIntent(context, views, R.id.new_tab_button, ACTION_LAUNCH_BROWSER);
+        // Clicking the logo also launches the browser
+        addClickIntent(context, views, R.id.logo_button, ACTION_LAUNCH_BROWSER);
+
+        manager.updateAppWidget(id, views);
+    }
+
+    // Utility for adding a pending intent to be fired when a View is clicked.
+    private void addClickIntent(final Context context, final RemoteViews views, final int viewId, final String action) {
+        final Intent intent = new Intent(context, SearchWidget.class);
+        intent.setAction(action);
+        intent.setData(Uri.parse("about:home"));
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
+        views.setOnClickPendingIntent(viewId, pendingIntent);
+    }
+
+    // Utility for building an intent to be redispatched (i.e. to launch the browser or the search intent).
+    private Intent buildRedirectIntent(final String action, final String pkg, final String className, final Intent source) {
+        final Intent activity = new Intent(action);
+        if (pkg != null && className != null) {
+            activity.setClassName(pkg, className);
+        }
+        activity.setData(source.getData());
+        activity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return activity;
+    }
+
+}
--- a/mobile/android/search/java/org/mozilla/search/autocomplete/SearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/autocomplete/SearchFragment.java
@@ -17,16 +17,17 @@ import android.text.style.ForegroundColo
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.ListView;
 
 import org.mozilla.search.AcceptsSearchQuery;
 import org.mozilla.search.AcceptsSearchQuery.SuggestionAnimation;
+import org.mozilla.search.Constants;
 import org.mozilla.search.R;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * A fragment to handle autocomplete. Its interface with the outside
  * world should be very very limited.
@@ -36,19 +37,16 @@ import java.util.List;
 public class SearchFragment extends Fragment implements AcceptsJumpTaps {
 
     private static final int LOADER_ID_SUGGESTION = 0;
     private static final String KEY_SEARCH_TERM = "search_term";
 
     // Timeout for the suggestion client to respond
     private static final int SUGGESTION_TIMEOUT = 3000;
 
-    // Maximum number of results returned by the suggestion client
-    private static final int SUGGESTION_MAX = 5;
-
     // Color of search term match in search suggestion
     private static final int SUGGESTION_HIGHLIGHT_COLOR = 0xFF999999;
 
     private AcceptsSearchQuery searchListener;
     private SuggestClient suggestClient;
     private SuggestionLoaderCallbacks suggestionLoaderCallbacks;
 
     private AutoCompleteAdapter autoCompleteAdapter;
@@ -75,19 +73,19 @@ public class SearchFragment extends Frag
         if (activity instanceof AcceptsSearchQuery) {
             searchListener = (AcceptsSearchQuery) activity;
         } else {
             throw new ClassCastException(activity.toString() + " must implement AcceptsSearchQuery.");
         }
 
         // TODO: Don't hard-code this template string (bug 1039758)
         final String template = "https://search.yahoo.com/sugg/ff?" +
-                "output=fxjson&appid=ffm&command=__searchTerms__&nresults=" + SUGGESTION_MAX;
+                "output=fxjson&appid=ffm&command=__searchTerms__&nresults=" + Constants.SUGGESTION_MAX;
 
-        suggestClient = new SuggestClient(activity, template, SUGGESTION_TIMEOUT, SUGGESTION_MAX);
+        suggestClient = new SuggestClient(activity, template, SUGGESTION_TIMEOUT, Constants.SUGGESTION_MAX);
         suggestionLoaderCallbacks = new SuggestionLoaderCallbacks();
 
         autoCompleteAdapter = new AutoCompleteAdapter(activity, this);
     }
 
     @Override
     public void onDetach() {
         super.onDetach();
--- a/mobile/android/search/manifests/SearchAndroidManifest_activities.xml.in
+++ b/mobile/android/search/manifests/SearchAndroidManifest_activities.xml.in
@@ -8,18 +8,38 @@
 
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.ASSIST"/>
 
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
+
         </activity>
 
+        <!-- Basic launcher widget. -->
+        <receiver android:name="org.mozilla.search.SearchWidget"
+                  android:label="@string/search_widget_name">
+
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="org.mozilla.widget.LAUNCH_BROWSER"/>
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="org.mozilla.widget.LAUNCH_SEARCH"/>
+            </intent-filter>
+
+            <meta-data android:name="android.appwidget.provider" android:resource="@xml/search_widget_info" />
+        </receiver>
+
         <activity
             android:name="org.mozilla.search.SearchPreferenceActivity"
             android:label="@string/search_pref_title"
             android:parentActivityName="org.mozilla.search.MainActivity"
             android:theme="@style/SettingsTheme" >
             <meta-data
                 android:name="android.support.PARENT_ACTIVITY"
                 android:value="org.mozilla.search.MainActivity"/>
new file mode 100644
index 0000000000000000000000000000000000000000..98fa453123bbde937f0fef6946af8c6bf29256e4
GIT binary patch
literal 141
zc%17D@N?(olHy`uVBq!ia0vp^Dj>|r3?y%#<GKx`SkfJR9T^xl_H+M9WCijWi-X*q
z7}lMWc?smO1^9%x0_o)B<kGoE3_#K)L4Lsu4$p3+0XY($E{-7_*OL<zB>yP%{`vo(
gU2amy<qlSc{IzoapK~)xfNB^#UHx3vIVCg!0R03ee*gdg
new file mode 100644
index 0000000000000000000000000000000000000000..90e2cdd355931a4bff84159145aeb7851dce9d55
GIT binary patch
literal 2091
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
new file mode 100644
index 0000000000000000000000000000000000000000..ce5006de8a3d6046b9cef8cb5e138266496e2019
GIT binary patch
literal 1888
zc%02u`8(U$8c)R%Bx$&mvF|ixTAH@eB8ymJ2~tx!GsRdkrcyzoGa74Xi_S$+!?bGb
zx2>hBwqmBD!gN%`HYIp0NhL_MV_#DDdvAZc&-?-ReV+5apYu8A`JDH>?|Gh+<x6sd
zspzPHKp+^=oj?Yn8!&tzV8G`%T48}8|1HiN2Ld(asvdl!0L+ij$!>U1!;qdBz*D|n
z{w|>Z+Ro0-*47s2?`(H>cW-a+6FxXN*x%om$z*@K^Z&szGBW;$VK5k<fJ7pp(P(RH
zYe4=}Q&Utb6@fso*=#zUE)t0<D=W>-%_k-%?Ck75m122$8Sq_QU9+>Z+uPfnot*#=
zC`?aJ$Hm31tgHZ~0C+$O*d~=q<KyFj>FVn0(9jUTWM^k1k;v54R1%5A=kr5CLXwh_
z-oJm(<MD_@A^_Uj+5kK^HwPR9Fo4>|#>Uv#*uuhskB<)^2#3RGW@Z32AZK6+$Z&IW
z6X?a`$B&zvn}J?nFc>bEJ3l`UkieyMb#;Y8p;#>bl)o>EXa|H+2AOmbq$FLFS5Q16
z2UeD>szR1m#41qOQFXZ5KM=Z_$F#LH4D~ENGd8l+x7IN|VP^8Vovo9>x#OptPovLX
zK$=^fMA^9G-E<wW7<<PUmornA1#=7?#KqX?6ms(=DX!Gjz5TUKH8m-EoG0lxe?$9u
z9f?45)^X3pewk6DIgwEGW4-s=x0$W_L+Ux{Y4btN9Ax;s{rMR2J9JG$eYF0yaNwyj
zi3FT~V6UWDNMCeE^wnz%BCiEsDzcd@@}p4tH8|B_6bfZJj8b#Z$age-dakK%TJqDA
z_5FO0@pl1@%r8f26O_|`-nHQ4Rc)RR9|o>IZ%|k?tQf=4eU>+@(wnFQ*vr-b0x1oM
zhtH(Zzv}Vw)g#q<7K{=DqdlHw`x!$QFZP{+;*7IeKx~Vb7zGtPI%lcd>fsXF5ZqwR
zO2x1KqGj06l(F6~1I?kc=C=QG$9qaU98H!ow5vdZpBfK8imaD^cYBzm?ORCDZ+fSv
z+4~`RzO*eF6D8vCX=(=!wJa~#-A6cRv6>tvu~EI@Vv18rGo*-th+>z#O#Gm40_~xl
zb3cMpz(v$0d=Xd}08vUQt?(VWgHtiNMS-_oy&dYbuYAaG(K2>417~jA3U=P0tB|vX
zkk>~eHI90iUMb5XVAkmN-z?PHyNyPuU8pF`oFSYtaL!;vIj9{gw;M6Oo*#$0a%@VC
zUH*f>2}W0}PBS>+ntlXDK(AWoP~1pM!A8ZWUs#D`iBP1o^C;{*C)RdY|7n2?+iP8J
z^<9MUk8pX|UG|{8Q=^gj$nJ8mMsrmbD~MYan)MW`a=*uq4~|f%><btAuyL%J=vt$>
zMDo?@2rDQWzAZ08!dsQ#`mf2qk|l4mP|lc~g5mEX5rZ43rNH}5j*q;gUybJ^$q7Dm
zm4};tKwQh)7ZCmJyD)m$!Y3VNDeKzTArQ2oR$NJ81W67Z)$)gB%q$;;CV3`LoITt*
z&|Vt7(WBmv+XL%~xD?qNTEjB8vvFM2!&Q<SMY^J=?_50{nX)rC@+!e`bL&L9H1fee
z^VV6O1BI1bZlrACw9@wuNm)Z&N1y4<xb>LI3EonF7Y5B0tg{ry`ON*3l4;q_E`JDf
zV@cMXKL3l~z!&R`hdl0&s+kdDNqQL*dBLEbwz0j%)Vki<+IszSs^Mx&KvbzpV(_qm
zLBJ#xKAHR=82Va#=}dq$dgJyRW*f76b}O0wHt5R96D1DE@7(WATl4Qs$**p~e;*%%
zcKDNemJ2(ol=}O<xhIgj&qCa|?_O>T!-w<C`pJ_X4PQr5p;NNt-;!cm{G7cA-N`Y|
zT(48%shP&@WuuR%M6t%z8Q1P08YAjv4%wq7)UF#Ii{bvdB&JyIn`n5~=8tEOe0ERu
zCO+YLjlW$zBvhoB&K%5G<g~M*e4)1mfwpCXg~%`$Kh8a}MafByFeI=SO<B{|^>+GU
ztG!2^$1ofBpfs0I&rW)1hVkTM7fqv!ytkhH{OSSp@*vqghcnU#d0gz8T@^NPE%Lr9
zd*mP#t70LDL>c6|3JDOail5KvnOP3y7NgpYj$0FPg=ROw+SQ4Ql%Tx06<qc!TR*70
zT!q!nCiYfdhd2PNmhW@twwckMw#~$4WnNvhva9+!QpqH3+{N?REgp8PK&@;(&p(@g
zwd&jWe4R>V{EGB=kRnGnB4M{miSrv%`rxm7p&viKu%LDHffLFPU(|g}S=0PFzDo<%
Si^==XKS6XM5gPCztbYSngg@y3
new file mode 100644
index 0000000000000000000000000000000000000000..cbf6a5588a1a40705158710b40c1419d804ea5f3
GIT binary patch
literal 140
zc%17D@N?(olHy`uVBq!ia0vp^5+KaT3?y&uT)!Jgv7|ftIx;Y9?C1WI$O_~$76-XI
zF|0c$^AgBm3-AeX1=7jM$)$6T7=WZpg8YIR9G=}s19HSYT^vI=t|uoPU`yGM{Ov#g
f1BumAJ`4<Y9&FG3)b-u~l`wd^`njxgN@xNA-;pI1
new file mode 100644
index 0000000000000000000000000000000000000000..77020d47087992edd4f9eb575bee94d8761f0536
GIT binary patch
literal 1388
zc%17D@N?(olHy`uVBq!ia0vp^E<kL?!3-ol)z5nYDVB6cUq=Rpjs4tz5?O(K#^NA%
zCx&(BWL^T<I|F<|T!Dh$zI_9dKYsjRzy^N){Q2wGF9`e3pFcqI_wV0WRgwh${{0KL
z5s!gDWtf6+jX;M1o%j9wcT6?@{{BE^K<z*`0~LJy_z@`R<Kwey*RB~eW&qh=zI*|a
z4h{}4Uc7kn<jIE*AGo=>Z``;6m%4WC8c^fiyLSZy1c0^xMIJwX3?#d|yL)<iPMtcn
zfB$|U_t>#xKxgdPvj@oj`t>W2ZE0x<w0iR7$**6(784T#x&f#?EiKL1*!U(G96o#)
zC<rud;lhR9-rlcXy#gu#x)3P!`SWKW``*2Ky1KeKIXU|J`mL?4Kr?|xm6nzQ9R>_5
zNOZVflUf3#CY1#F1vC6(`^Un{&d9{f_3s~tz+axfoc!E;ydr|4!qP%g;xb~AidwP?
z5=wu6D;xh(RngGVH#e03Bd4zBsBQIEQ}_4ZCr(B-)&`~-e}4UXxK}ULuGZp*ZQ}In
zU;K7@pEZkreA?}&eYs1Ra%Gui^}D%tul{O&Gdpbj^S7PkhOOy84(3|E`t`o-;`<Uu
zYbT4Jt5e_q{Cs51WMEQZ+U)7#7~*mK?e(w4Qh@^O1w{u;FWh-rbxyJ%qM4<CxA$Ez
z`1j~tw8yf`ceeEZi`r*@fY-Pt?fco}@((}%&5@Ugw|=|jbA6iZ5@Df36VyMQv=>+y
zz#zoK_E2=;v>BpG5-AZphZ#h&JQdj#C8h}<mYA3<&9k+MP4UPE9S@sMr3PjmG3QQ!
z=BJ06ioRWW<~w<^JOA68$GA70FuJnUgf}qkr}Fe^ucSR+mD}A9ZxL-3Qfynazc%K5
zoaf(C7okHr6Z{T3N%)1WnSKA{cJm1beqYk|ZHj8z!eJsD_Vvr!whLFUCfvI*O{Mt8
zg<EgGSUYhEvc`GGy;XBkUZ7Lo^D;}qJJPvAA)q5^$@+TbV^utf)8vnu>a=n-3Jd<|
z_-l9H;7^^s(a+*Q_i)!s=i>$6xNV*DWUr0k9|;$|YmeHR{st)A@ZIxGr{9g8W$MSz
z>m(cN_6W>pSMr|qwy$<Cn}@r5@JlKC$-T!a4l;kNRFqLzHoL;;o({{zPU-E{lMQ1Z
z%n+M!=EY@y;r$Bb@;#;#8ALB-ERU%Dk(44jv*Kssilt>A5`zCeEob-B=@Up!+4%hD
z^g9{HdOjGb3N?7=Fa6E6^v;=Y$-k=<tiQ+E#ofF#Uuk>qQpXdi&C&A4#z#N>dGp}l
zU2g8^@AL14u2id<+{gOe&Y|_eNvV(f?j5=F@S%2daB#DFy4K~TjZ<VFvR-_At6F)@
zidTZxp%PlpwZz-f+sn^{d;iV5vQgx`{DSWq2MQm}Sa~R8Wz5;9%bp(-V$L&Ko2WR2
zL(_Ev&+#>>Tt{7!Gy-K#EL)T?!Bb#?j8}?QW75$_7wya^pG~_Hb*YTUz~YwT?HT<#
zXR2bmm*yHvnZDhzI5LR)*ea&SahG^?nJ)6oT_bnH=1MnvxrUxxWN1O9XBXFPZgIEt
j?&!U9%T_plb^oO9uATdHV@rN7s1)&Z^>bP0l+XkK7|LIr
new file mode 100644
index 0000000000000000000000000000000000000000..c397c7d404ff375487e1a3c63744ff824905f31d
GIT binary patch
literal 1307
zc%17D@N?(olHy`uVBq!ia0vp^E<kL?!3-ol)z5nYDVB6cUq=Rpjs4tz5?O(K#^NA%
zCx&(BWL^T<Jpn!;t_)!C^XJcR-@ZZEKYsjRfPr7Xe!<zlfB*jT=MMum@b~Xutl~5R
z-@ku{v;F=3zkK<!Yu7GaUEPl#Ki;@;W8uPuH*em2{P;0YSx-;Tix)3|Y@kPg+!-@w
zSXx^8`1lA22%I`~3gWw*oSb9Fj=g&I%E7?_$Q2V41Cn3Aeg%dD(90h_d;kUx&<dd7
zo;`bvjg5hxy>{&y(5}OW4+917-o5+e$rB)>w6wIlyZhd~dq6hO0p8x;X=!P%U%&qR
z`7@9Nn!wG?4RQ1S{riCmCQqIWWb5ne1IgCbRv@YI{n2e8)m#$f7tFx^_B|`hJ4Pnv
zH*8!yuU~O+a`W-?io6sM5*8DemK2mx6qQntRnn5s(NI>^S24EIG?bT9S2NHybu!;=
z<Y*l?$HcyWb;`2Tt@4E%uI?;RiMG$Xr)N@dHrztb_UN-053aw^ohb4o@LH_R-s5KB
zzNrrLjm@Vouf67O?~;?MYiVO=wCy0rL<R<?d7dtgAs)xyUQaK+<siV8Ai^yqu<2^x
z`Y%bd4(?s@{a*F^z2E;GFRm7vsdBBy|7X@cexAcT=ikU3*IN=K{Y2d>Yfj;@n@etX
zZQ@n!W<LL5>VJhzH%+*FFTW6U?wld$c$ug5umQ&@wV(qGO$#mT`VSX}oDk5y!O(P`
zdyS4$ikM}`!J`672>~(lMET#`-ddme^2E1hYnQMGnr%C#op`_+xwm?Uz|(WLZ!1{`
zaR?f?Jda#xVCwa-S8Zcums)UvB9D5<t?9uZ-g$Cp%rE7<>NHtt#mqf#eyn#^oX$P-
z^)fY1nY@g@MZd%wohrgY-<%FsXkmQX{_sR)%qc4+HpkYEs;@Uf4YoaCFfP3xR-oy^
zVI>&$wb^*$#FHro#+8>$WoNe6nYk30Z%UgMm#Ey_JaKbvw0NOC3&*=-@Al6*a*V}m
zMa?H2`LDw5OOgZ}gP7L;<D1ag>2dPuJhK?*)Z_^vGKUn8eA?As6sVY}8LsCyou~cj
z3>J@_yPxMjan*IY<l=GV+?#v$6RYn(6WZF$zs(}!?VZ2B6O5nF`?4$bbU3S%O0VAD
zu(_GnZp~fHzy6?l&Etz}g+DK6cioXI!pSt9_ioi;t?SXBSD&|^7awi?ukPWcr1eIz
zMy4h2FS~HqMc$n^bKM!ccQx^m5fMK>Boyv6(_!0p>XYGhJGq4wUsU<J&h8AW{+OM<
zwK#EKxOBz3m7N>o5BvyhINq5lI{R+a>g##>=kKlI*&7skuvI`QfJ^)3va9;$ucw}h
zS!$Of5}R^B#Y31+b%vM6MV{MpKc4D!yZL%LQ?NUmyH(2JpB@V%-)p#;Ziu<gl@ok%
z35(km0p`bXMI}egdEzgchNet(6xN@2kuTNaMx2Gy7Us>J*FI*<zZ`g|cixZogBG*Z
dRO(gg86y(o7cD<i^d6M4JYD@<);T3K0RU~`6^sA?
new file mode 100644
index 0000000000000000000000000000000000000000..a19a65599aab6d78f84ec9fd2af221d716f54047
GIT binary patch
literal 144
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDH3?y^UWFG-iEa{HEjtmSN`?>!lvI6;x#X;^)
z4C~IxyaaOC0(?STfpk(*l9coM3n1x|AirP+hi5m^fE;O07sn8Z%gG4~gc9l<Y)}3B
i|KI*dV93^KQVa~M{M8K;?=J$G!r<xZ=d#Wzp$PzX+$cl<
new file mode 100644
index 0000000000000000000000000000000000000000..7905d1fb2876b3d36995b0794c035cf8abf844c3
GIT binary patch
literal 3085
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
MrZy(;j6CE11)N+eEdT%j
new file mode 100644
index 0000000000000000000000000000000000000000..6582a9625cd93688bab0e36beef9b0eb479f0d89
GIT binary patch
literal 2682
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
bo+uIV{3p2vP1AaRKb9O$<84Z;FW&kuIDP;s
--- a/mobile/android/search/res/drawable/search_card_background.xml
+++ b/mobile/android/search/res/drawable/search_card_background.xml
@@ -1,25 +1,10 @@
 <!-- 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/. -->
 
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item>
-        <shape>
-            <padding
-                android:bottom="@dimen/card_background_padding_y"
-                android:top="@dimen/card_background_padding_y"
-                android:left="@dimen/card_background_padding_x"
-                android:right="@dimen/card_background_padding_x"/>
-            <solid android:color="@android:color/transparent"/>
-        </shape>
-    </item>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <!-- Background -->
-    <item>
-        <shape>
-            <solid android:color="@color/card_background"/>
-            <corners android:radius="5dp"/>
-            <stroke android:width="1dp" android:color="@color/card_border" />
-        </shape>
-    </item>
-</layer-list>
+    <item android:state_pressed="true" android:drawable="@drawable/search_card_pressed" />
+    <item android:drawable="@drawable/search_card_default"/>
+
+</selector>
new file mode 100644
--- /dev/null
+++ b/mobile/android/search/res/drawable/search_widget_button.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<!-- Drawable used for buttons in the launch widget -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:constantSize="true">
+
+    <item android:state_pressed="true"
+          android:drawable="@drawable/widget_active_bg"/>
+
+    <item android:drawable="@drawable/widget_bg"/>
+
+</selector>
--- a/mobile/android/search/res/layout/search_auto_complete.xml
+++ b/mobile/android/search/res/layout/search_auto_complete.xml
@@ -21,11 +21,12 @@
 
     <ListView
         android:id="@+id/auto_complete_dropdown"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@color/global_background_color"
         android:divider="@null"
         android:dividerHeight="0dp"
+        android:listSelector="@android:color/transparent"
         android:visibility="gone"/>
 
 </LinearLayout>
--- a/mobile/android/search/res/layout/search_fragment_pre_search.xml
+++ b/mobile/android/search/res/layout/search_fragment_pre_search.xml
@@ -7,17 +7,18 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
     <ListView
         android:id="@+id/list_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:divider="@null"
-        android:dividerHeight="0dp"/>
+        android:dividerHeight="0dp"
+        android:listSelector="@android:color/transparent"/>
 
     <ImageButton
         android:id="@+id/settings_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="@android:color/transparent"
         android:padding="15dp"
         android:src="@drawable/ic_action_overflow"
new file mode 100644
--- /dev/null
+++ b/mobile/android/search/res/layout/search_widget.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<!-- A homescreen widget for launching Fennec or the search activity. We can't use styles in here
+     so make sure any changes you make are also made to launch_widget.xml which doesn't have
+     the search widget button. -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/widget_header_height">
+
+    <!-- Wrap in a linear layout to center the text in a flexible space. We use a negative margin
+         to extend this into the firefox logo so that the button background appears to come from behind the logo, but
+         highlights correctly when tapped. -->
+    <LinearLayout android:layout_width="match_parent"
+                  android:layout_height="match_parent"
+                  android:layout_toLeftOf="@+id/logo_button"
+                  android:layout_marginRight="@dimen/widget_button_offset"
+                  android:paddingRight="@dimen/widget_button_padding"
+                  android:gravity="center"
+                  android:background="@drawable/search_widget_button"
+                  android:orientation="horizontal"
+                  android:id="@+id/search_button">
+
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:drawableLeft="@drawable/ic_url_bar_search"
+                  android:drawablePadding="@dimen/widget_drawable_padding"
+                  android:text="@string/search_widget_button_label"
+                  android:contentDescription="@string/search_widget_button_label"
+                  android:gravity="center"
+                  android:textSize="@dimen/widget_text_size"
+                  android:textColor="@color/text_color_primary"
+                  android:id="@+id/search_button_label"/>
+
+    </LinearLayout>
+
+    <LinearLayout android:layout_width="match_parent"
+                  android:layout_toRightOf="@+id/logo_button"
+                  android:layout_height="match_parent"
+                  android:layout_marginLeft="@dimen/widget_button_offset"
+                  android:paddingLeft="@dimen/widget_button_padding"
+                  android:layout_centerVertical="true"
+                  android:gravity="center"
+                  android:background="@drawable/search_widget_button"
+                  android:orientation="horizontal"
+                  android:id="@+id/new_tab_button">
+
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:drawableLeft="@drawable/ic_widget_new_tab"
+                  android:drawablePadding="@dimen/widget_drawable_padding"
+                  android:gravity="center"
+                  android:text="@string/new_tab"
+                  android:contentDescription="@string/new_tab"
+                  android:textSize="@dimen/widget_text_size"
+                  android:textColor="@color/text_color_primary"
+                  android:id="@+id/new_tab_button_label"/>
+
+    </LinearLayout>
+
+    <!-- The logo. adjustViewBounds is required for the buttons above to stretch underneath the logo. -->
+    <ImageView android:id="@+id/logo_button"
+               android:layout_centerInParent="true"
+               android:adjustViewBounds="true"
+               android:layout_width="@dimen/widget_header_height"
+               android:layout_height="match_parent"
+               android:src="@drawable/icon"/>
+
+</RelativeLayout>
--- a/mobile/android/search/res/values/search_colors.xml
+++ b/mobile/android/search/res/values/search_colors.xml
@@ -5,12 +5,13 @@
 <resources>
 
     <color name="global_background_color">#EBEBF0</color>
 
     <color name="highlight_orange">#FF9500</color>
 
     <!-- card colors -->
     <color name="card_background">#ffffff</color>
+    <color name="card_background_pressed">#DCDCE1</color>
     <color name="card_border">#BFBFBF</color>
 
     <!-- Search suggestion highlight color is defined in SearchFragment.java -->
 </resources>
\ No newline at end of file
--- a/mobile/android/search/res/values/search_dimens.xml
+++ b/mobile/android/search/res/values/search_dimens.xml
@@ -18,9 +18,16 @@
     <dimen name="card_background_padding_x">15dp</dimen>
     <dimen name="card_background_padding_y">3dp</dimen>
 
     <!-- To create the padding we see around the content of each
          card, we need to account for the padding of the background -->
     <dimen name="card_padding_x">38dp</dimen>
     <dimen name="card_padding_y">23dp</dimen>
 
+    <!-- Widget Buttons -->
+    <dimen name="widget_header_height">70dp</dimen>
+    <dimen name="widget_button_offset">-50dp</dimen>
+    <dimen name="widget_button_padding">45dp</dimen>
+    <dimen name="widget_text_size">15sp</dimen>
+    <dimen name="widget_drawable_padding">2dp</dimen>
+
 </resources>
new file mode 100644
--- /dev/null
+++ b/mobile/android/search/res/xml/search_widget_info.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="300dp"
+    android:minHeight="40dp"
+    android:label="@string/search_widget_name"
+    android:widgetCategory="home_screen"
+    android:previewImage="@drawable/launcher_widget"
+    android:initialLayout="@layout/search_widget"/>
--- a/mobile/android/search/search_activity_sources.mozbuild
+++ b/mobile/android/search/search_activity_sources.mozbuild
@@ -11,9 +11,10 @@ search_activity_sources = [
     'java/org/mozilla/search/autocomplete/ClearableEditText.java',
     'java/org/mozilla/search/autocomplete/SearchFragment.java',
     'java/org/mozilla/search/autocomplete/SuggestClient.java',
     'java/org/mozilla/search/Constants.java',
     'java/org/mozilla/search/MainActivity.java',
     'java/org/mozilla/search/PostSearchFragment.java',
     'java/org/mozilla/search/PreSearchFragment.java',
     'java/org/mozilla/search/SearchPreferenceActivity.java',
+    'java/org/mozilla/search/SearchWidget.java',
 ]
--- a/mobile/android/search/strings/search_strings.dtd
+++ b/mobile/android/search/strings/search_strings.dtd
@@ -1,14 +1,17 @@
 <!-- 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/. -->
 
 <!ENTITY search_jump_arrow '&#8598;'>
 
-<!ENTITY search_app_name 'Firefox Search'>
+<!ENTITY search_app_name '&brandShortName; Search'>
 <!ENTITY search_for_something 'Search for something'>
 
 <!ENTITY search_pref_title 'Settings'>
 <!ENTITY search_pref_clear_history_confirmation 'History cleared'>
 <!ENTITY search_pref_clear_history_dialog_message 'Delete all search history from this device?'>
 <!ENTITY search_pref_clear_history_title 'Clear search history'>
 <!ENTITY search_pref_button_content_description 'Settings'>
+
+<!ENTITY search_widget_button_label 'Search'>
+
--- a/mobile/android/search/strings/search_strings.xml.in
+++ b/mobile/android/search/strings/search_strings.xml.in
@@ -3,8 +3,11 @@
     <string name="search_app_name">&search_app_name;</string>
     <string name="search_for_something">&search_for_something;</string>
 
     <string name="search_pref_title">&search_pref_title;</string>
     <string name="search_pref_clear_history_confirmation">&search_pref_clear_history_confirmation;</string>
     <string name="search_pref_clear_history_dialog_message">&search_pref_clear_history_dialog_message;</string>
     <string name="search_pref_clear_history_title">&search_pref_clear_history_title;</string>
     <string name="search_pref_button_content_description">&search_pref_button_content_description;</string>
+
+    <string name="search_widget_name">&search_app_name;</string>
+    <string name="search_widget_button_label">&search_widget_button_label;</string>
--- a/toolkit/devtools/server/actors/memory.js
+++ b/toolkit/devtools/server/actors/memory.js
@@ -1,40 +1,109 @@
 /* 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/. */
 
 "use strict";
 
-const {Cc, Ci, Cu} = require("chrome");
+const { Cc, Ci, Cu } = require("chrome");
 let protocol = require("devtools/server/protocol");
-let {method, RetVal} = protocol;
+let { method, RetVal } = protocol;
+const { reportException } = require("devtools/toolkit/DevToolsUtils");
+
+/**
+ * A method decorator that ensures the actor is in the expected state before
+ * proceeding. If the actor is not in the expected state, the decorated method
+ * returns a rejected promise.
+ *
+ * @param String expectedState
+ *        The expected state.
+ *
+ * @param Function method
+ *        The actor method to proceed with when the actor is in the expected
+ *        state.
+ *
+ * @returns Function
+ *          The decorated method.
+ */
+function expectState(expectedState, method) {
+  return function(...args) {
+    if (this.state !== expectedState) {
+      const msg = "Wrong State: Expected '" + expectedState + "', but current "
+                + "state is '" + this.state + "'";
+      return Promise.reject(new Error(msg));
+    }
+
+    return method.apply(this, args);
+  };
+}
 
 /**
  * An actor that returns memory usage data for its parent actor's window.
  * A tab-scoped instance of this actor will measure the memory footprint of its
  * parent tab. A global-scoped instance however, will measure the memory
  * footprint of the chrome window referenced by the root actor.
  */
 let MemoryActor = protocol.ActorClass({
   typeName: "memory",
 
-  initialize: function(conn, tabActor) {
+  get dbg() {
+    if (!this._dbg) {
+      this._dbg = this.parent.makeDebugger();
+    }
+    return this._dbg;
+  },
+
+  initialize: function(conn, parent) {
     protocol.Actor.prototype.initialize.call(this, conn);
-    this.tabActor = tabActor;
+    this.parent = parent;
     this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
                   .getService(Ci.nsIMemoryReporterManager);
+    this.state = "detached";
+    this._dbg = null;
   },
 
   destroy: function() {
     this._mgr = null;
+    if (this.state === "attached") {
+      this.detach();
+    }
     protocol.Actor.prototype.destroy.call(this);
   },
 
   /**
+   * Attach to this MemoryActor.
+   */
+  attach: method(expectState("detached", function() {
+    this.dbg.addDebuggees();
+    this.dbg.enabled = true;
+    this.state = "attached";
+  }), {
+    request: {},
+    response: {
+      type: "attached"
+    }
+  }),
+
+  /**
+   * Detach from this MemoryActor.
+   */
+  detach: method(expectState("attached", function() {
+    this.dbg.removeAllDebuggees();
+    this.dbg.enabled = false;
+    this._dbg = null;
+    this.state = "detached";
+  }), {
+    request: {},
+    response: {
+      type: "detached"
+    }
+  }),
+
+  /**
    * A method that returns a detailed breakdown of the memory consumption of the
    * associated window.
    *
    * @returns object
    */
   measure: method(function() {
     let result = {};
 
@@ -44,31 +113,29 @@ let MemoryActor = protocol.ActorClass({
     let domSize = {};
     let styleSize = {};
     let otherSize = {};
     let totalSize = {};
     let jsMilliseconds = {};
     let nonJSMilliseconds = {};
 
     try {
-      this._mgr.sizeOfTab(this.tabActor.window, jsObjectsSize, jsStringsSize, jsOtherSize,
+      this._mgr.sizeOfTab(this.parent.window, jsObjectsSize, jsStringsSize, jsOtherSize,
                           domSize, styleSize, otherSize, totalSize, jsMilliseconds, nonJSMilliseconds);
       result.total = totalSize.value;
       result.domSize = domSize.value;
       result.styleSize = styleSize.value;
       result.jsObjectsSize = jsObjectsSize.value;
       result.jsStringsSize = jsStringsSize.value;
       result.jsOtherSize = jsOtherSize.value;
       result.otherSize = otherSize.value;
       result.jsMilliseconds = jsMilliseconds.value.toFixed(1);
       result.nonJSMilliseconds = nonJSMilliseconds.value.toFixed(1);
     } catch (e) {
-      console.error(e);
-      let url = this.tabActor.url;
-      console.error("Error getting size of " + url);
+      reportException("MemoryActor.prototype.measure", e);
     }
 
     return result;
   }, {
     request: {},
     response: RetVal("json"),
   }),
 
--- a/toolkit/devtools/server/tests/mochitest/chrome.ini
+++ b/toolkit/devtools/server/tests/mochitest/chrome.ini
@@ -2,16 +2,17 @@
 support-files =
   inspector-helpers.js
   inspector-styles-data.css
   inspector-styles-data.html
   inspector-traversal-data.html
   nonchrome_unsafeDereference.html
   inspector_getImageData.html
   large-image.jpg
+  memory-helpers.js
   small-image.gif
   Debugger.Source.prototype.element.js
   Debugger.Source.prototype.element-2.js
   Debugger.Source.prototype.element.html
 
 [test_Debugger.Source.prototype.introductionScript.html]
 [test_Debugger.Source.prototype.introductionType.html]
 [test_Debugger.Source.prototype.element.html]
@@ -61,11 +62,13 @@ skip-if = buildapp == 'mulet'
 [test_styles-matched.html]
 [test_styles-modify.html]
 [test_styles-svg.html]
 [test_unsafeDereference.html]
 [test_evalInGlobal-outerized_this.html]
 [test_inspector_getImageData.html]
 skip-if = buildapp == 'mulet'
 [test_memory.html]
+[test_memory_attach_01.html]
+[test_memory_attach_02.html]
 [test_preference.html]
 [test_connectToChild.html]
 skip-if = buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/memory-helpers.js
@@ -0,0 +1,54 @@
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+SimpleTest.registerCleanupFunction(function() {
+  Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/devtools/Loader.jsm");
+let { require } = devtools;
+
+let { MemoryFront } = require("devtools/server/actors/memory");
+
+function startServerAndGetSelectedTabMemory() {
+  DebuggerServer.init(() => true);
+  DebuggerServer.addBrowserActors();
+  var client = new DebuggerClient(DebuggerServer.connectPipe());
+
+  return new Promise((resolve, reject) => {
+    client.connect(response => {
+      if (response.error) {
+        reject(new Error(response.error + ": " + response.message));
+        return;
+      }
+
+      client.listTabs(response => {
+        if (response.error) {
+          reject(new Error(response.error + ": " + response.message));
+          return;
+        }
+
+        var form = response.tabs[response.selected];
+        var memory = MemoryFront(client, form);
+
+        resolve({ memory, client });
+      });
+    });
+  });
+}
+
+function destroyServerAndFinish(client) {
+  client.close(() => {
+    DebuggerServer.destroy();
+    SimpleTest.finish()
+  });
+}
--- a/toolkit/devtools/server/tests/mochitest/test_memory.html
+++ b/toolkit/devtools/server/tests/mochitest/test_memory.html
@@ -6,65 +6,32 @@ Bug 923275 - Add a memory monitor widget
 <head>
   <meta charset="utf-8">
   <title>Memory monitoring actor test</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
 <script>
-
 window.onload = function() {
-  var Cu = Components.utils;
-  var Cc = Components.classes;
-  var Ci = Components.interfaces;
-
-  Cu.import("resource://gre/modules/Services.jsm");
-
-  // Always log packets when running tests.
-  Services.prefs.setBoolPref("devtools.debugger.log", true);
-  SimpleTest.registerCleanupFunction(function() {
-    Services.prefs.clearUserPref("devtools.debugger.log");
-  });
-
-  Cu.import("resource://gre/modules/devtools/Loader.jsm");
-  Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
-  Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
-
   SimpleTest.waitForExplicitFinish();
 
-  var {MemoryFront} = devtools.require("devtools/server/actors/memory");
-
-  DebuggerServer.init(function () { return true; });
-  DebuggerServer.addBrowserActors();
-
-  var client = new DebuggerClient(DebuggerServer.connectPipe());
-  client.connect(function onConnect() {
-    client.listTabs(function onListTabs(aResponse) {
-      var form = aResponse.tabs[aResponse.selected];
-      var front = MemoryFront(client, form);
-
-      front.measure().then(measurement => {
-        ok(measurement.total > 0, "total memory is valid");
-        ok(measurement.domSize > 0, "domSize is valid");
-        ok(measurement.styleSize > 0, "styleSize is valid");
-        ok(measurement.jsObjectsSize > 0, "jsObjectsSize is valid");
-        ok(measurement.jsStringsSize > 0, "jsStringsSize is valid");
-        ok(measurement.jsOtherSize > 0, "jsOtherSize is valid");
-        ok(measurement.otherSize > 0, "otherSize is valid");
-        ok(measurement.jsMilliseconds, "jsMilliseconds is valid");
-        ok(measurement.nonJSMilliseconds, "nonJSMilliseconds is valid");
-
-        client.close(() => {
-          DebuggerServer.destroy();
-          SimpleTest.finish()
-        });
-      });
-
-    });
+  Task.spawn(function* () {
+    var { memory, client } = yield startServerAndGetSelectedTabMemory();
+    var measurement = yield memory.measure();
+    ok(measurement.total > 0, "total memory is valid");
+    ok(measurement.domSize > 0, "domSize is valid");
+    ok(measurement.styleSize > 0, "styleSize is valid");
+    ok(measurement.jsObjectsSize > 0, "jsObjectsSize is valid");
+    ok(measurement.jsStringsSize > 0, "jsStringsSize is valid");
+    ok(measurement.jsOtherSize > 0, "jsOtherSize is valid");
+    ok(measurement.otherSize > 0, "otherSize is valid");
+    ok(measurement.jsMilliseconds, "jsMilliseconds is valid");
+    ok(measurement.nonJSMilliseconds, "nonJSMilliseconds is valid");
+    destroyServerAndFinish(client);
   });
-
-}
+};
 </script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_attach_01.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 960671 - Test attaching and detaching from a memory actor.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Memory monitoring actor test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+
+  Task.spawn(function* () {
+    var { memory, client } = yield startServerAndGetSelectedTabMemory();
+    yield memory.attach();
+    ok(true, "Shouldn't have gotten an error attaching.");
+    yield memory.detach();
+    ok(true, "Shouldn't have gotten an error detaching.");
+   destroyServerAndFinish(client);
+  });
+};
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_attach_02.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 960671 - Test attaching and detaching while in the wrong state.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Memory monitoring actor test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+
+  Task.spawn(function* () {
+    var { memory, client } = yield startServerAndGetSelectedTabMemory();
+
+    var e = null;
+    try {
+      yield memory.detach();
+    }
+    catch (ee) {
+      e = ee;
+    }
+    ok(e, "Should have hit the wrongState error");
+
+    yield memory.attach();
+
+    e = null;
+    try {
+      yield memory.attach();
+    }
+    catch (ee) {
+      e = ee;
+    }
+    ok(e, "Should have hit the wrongState error");
+
+    yield memory.detach();
+    destroyServerAndFinish(client);
+  });
+};
+</script>
+</pre>
+</body>
+</html>
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -996,17 +996,17 @@ AndroidBridge::ValidateBitmap(jobject bi
 
 bool
 AndroidBridge::InitCamera(const nsCString& contentType, uint32_t camera, uint32_t *width, uint32_t *height, uint32_t *fps)
 {
     JNIEnv *env = GetJNIEnv();
 
     AutoLocalJNIFrame jniFrame(env, 1);
     jintArray arr = mozilla::widget::android::GeckoAppShell::InitCameraWrapper
-      (NS_ConvertUTF8toUTF16(contentType), (int32_t) camera, (int32_t) width, (int32_t) height);
+      (NS_ConvertUTF8toUTF16(contentType), (int32_t) camera, (int32_t) *width, (int32_t) *height);
 
     if (!arr)
         return false;
 
     jint *elements = env->GetIntArrayElements(arr, 0);
 
     *width = elements[1];
     *height = elements[2];