Merge fx-team to m-c a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Mon, 06 Oct 2014 19:29:41 -0700
changeset 232287 9ee9e193fc48b0c7e6f27337c64fe4780842ea87
parent 232256 0208159db84df29d10dd18705a53c6d5334db613 (current diff)
parent 232286 650603771acde4563e4b8b20d3efd213c746d9cb (diff)
child 232306 f65726bd726555bd7cab954c3347931f4c9d4c00
child 232311 896e255af48ebaf6fc0b5581865fcdbd53e07733
child 232319 be8ee7774bb95b714ec5880adf7bfb6224374e29
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone35.0a1
first release with
nightly linux32
9ee9e193fc48 / 35.0a1 / 20141007030202 / files
nightly linux64
9ee9e193fc48 / 35.0a1 / 20141007030202 / files
nightly mac
9ee9e193fc48 / 35.0a1 / 20141007030202 / files
nightly win32
9ee9e193fc48 / 35.0a1 / 20141007030202 / files
nightly win64
9ee9e193fc48 / 35.0a1 / 20141007030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c a=merge CLOSED TREE
--- a/addon-sdk/source/lib/sdk/content/sandbox.js
+++ b/addon-sdk/source/lib/sdk/content/sandbox.js
@@ -69,17 +69,20 @@ const WorkerSandbox = Class({
 
   /**
    * Synchronous version of `emit`.
    * /!\ Should only be used when it is strictly mandatory /!\
    *     Doesn't ensure passing only JSON values.
    *     Mainly used by context-menu in order to avoid breaking it.
    */
   emitSync: function emitSync(...args) {
-    return emitToContent(this, args);
+    // because the arguments could be also non JSONable values,
+    // we need to ensure the array instance is created from
+    // the content's sandbox
+    return emitToContent(this, new modelFor(this).sandbox.Array(...args));
   },
 
   /**
    * Configures sandbox and loads content scripts into it.
    * @param {Worker} worker
    *    content worker
    */
   initialize: function WorkerSandbox(worker, window) {
--- a/addon-sdk/source/lib/sdk/panel/window.js
+++ b/addon-sdk/source/lib/sdk/panel/window.js
@@ -30,20 +30,24 @@ function getWindow(anchor) {
     for (let enumWindow of windows) {
       // Check if the anchor is in this browser window.
       if (enumWindow == anchorWindow) {
         window = anchorWindow;
         break;
       }
 
       // Check if the anchor is in a browser tab in this browser window.
-      let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
-      if (browser) {
-        window = enumWindow;
-        break;
+      try {
+        let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
+        if (browser) {
+          window = enumWindow;
+          break;
+        }
+      }
+      catch (e) {
       }
 
       // Look in other subdocuments (sidebar, etc.)?
     }
   }
 
   // If we didn't find the anchor's window (or we have no anchor),
   // return the most recent browser window.
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -130,16 +130,17 @@ skip-if = os == "linux"
 
 [browser_985815_propagate_setToolbarVisibility.js]
 [browser_981305_separator_insertion.js]
 [browser_988072_sidebar_events.js]
 [browser_989338_saved_placements_not_resaved.js]
 [browser_989751_subviewbutton_class.js]
 [browser_987177_destroyWidget_xul.js]
 [browser_987177_xul_wrapper_updating.js]
+[browser_987185_syncButton.js]
 [browser_987492_window_api.js]
 [browser_987640_charEncoding.js]
 [browser_992747_toggle_noncustomizable_toolbar.js]
 [browser_993322_widget_notoolbar.js]
 [browser_995164_registerArea_during_customize_mode.js]
 [browser_996364_registerArea_different_properties.js]
 [browser_996635_remove_non_widgets.js]
 [browser_1003588_no_specials_in_panel.js]
new file mode 100755
--- /dev/null
+++ b/browser/components/customizableui/test/browser_987185_syncButton.js
@@ -0,0 +1,69 @@
+/* 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";
+
+let syncService = {};
+Components.utils.import("resource://services-sync/service.js", syncService);
+
+let needsSetup;
+let originalSync;
+let service = syncService.Service;
+let syncWasCalled = false;
+
+add_task(function* testSyncButtonFunctionality() {
+  info("Check Sync button functionality");
+  storeInitialValues();
+  mockFunctions();
+
+  // add the Sync button to the panel
+  CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+
+  // check the button's functionality
+  yield PanelUI.show();
+  info("The panel menu was opened");
+
+  let syncButton = document.getElementById("sync-button");
+  ok(syncButton, "The Sync button was added to the Panel Menu");
+  syncButton.click();
+  info("The sync button was clicked");
+
+  yield waitForCondition(() => syncWasCalled);
+});
+
+add_task(function* asyncCleanup() {
+  // reset the panel UI to the default state
+  yield resetCustomization();
+  ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
+
+  if (isPanelUIOpen()) {
+    let panelHidePromise = promisePanelHidden(window);
+    PanelUI.hide();
+    yield panelHidePromise;
+  }
+
+  restoreValues();
+});
+
+function mockFunctions() {
+  // mock needsSetup
+  gSyncUI._needsSetup = function() false;
+
+  // mock service.errorHandler.syncAndReportErrors()
+  service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors;
+}
+
+function mocked_syncAndReportErrors() {
+  syncWasCalled = true;
+}
+
+function restoreValues() {
+  gSyncUI._needsSetup = needsSetup;
+  service.sync = originalSync;
+}
+
+function storeInitialValues() {
+  needsSetup = gSyncUI._needsSetup;
+  originalSync = service.sync;
+}
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -50,17 +50,19 @@ loop.panel = (function(_, mozL10n) {
         tabs.push(
           React.DOM.div({key: i, className: cx({tab: true, selected: isSelected})}, 
             tab.props.children
           )
         );
       }, this);
       return (
         React.DOM.div({className: "tab-view-container"}, 
-          React.DOM.ul({className: "tab-view"}, tabButtons), 
+          !this.props.buttonsHidden
+            ? React.DOM.ul({className: "tab-view"}, tabButtons)
+            : null, 
           tabs
         )
       );
     }
   });
 
   var Tab = React.createClass({displayName: 'Tab',
     render: function() {
@@ -434,16 +436,17 @@ loop.panel = (function(_, mozL10n) {
    */
   var PanelView = React.createClass({displayName: 'PanelView',
     propTypes: {
       notifications: React.PropTypes.object.isRequired,
       client: React.PropTypes.object.isRequired,
       // Mostly used for UI components showcase and unit tests
       callUrl: React.PropTypes.string,
       userProfile: React.PropTypes.object,
+      showTabButtons: React.PropTypes.bool,
     },
 
     getInitialState: function() {
       return {
         userProfile: this.props.userProfile || navigator.mozLoop.userProfile,
       };
     },
 
@@ -470,17 +473,22 @@ loop.panel = (function(_, mozL10n) {
           detailsButtonLabel: serviceError.error.friendlyDetailsButtonLabel,
         });
       } else {
         this.props.notifications.remove(this.props.notifications.get("service-error"));
       }
     },
 
     _onStatusChanged: function() {
-      this.setState({userProfile: navigator.mozLoop.userProfile});
+      var profile = navigator.mozLoop.userProfile;
+      if (profile != this.state.userProfile) {
+        // On profile change (login, logout), switch back to the default tab.
+        this.selectTab("call");
+      }
+      this.setState({userProfile: profile});
       this.updateServiceErrors();
     },
 
     startForm: function(name, contact) {
       this.refs[name].initForm(contact);
       this.selectTab(name);
     },
 
@@ -503,17 +511,17 @@ loop.panel = (function(_, mozL10n) {
     render: function() {
       var NotificationListView = sharedViews.NotificationListView;
       var displayName = this.state.userProfile && this.state.userProfile.email ||
                         __("display_name_guest");
       return (
         React.DOM.div(null, 
           NotificationListView({notifications: this.props.notifications, 
                                 clearOnDocumentHidden: true}), 
-          TabView({ref: "tabView"}, 
+          TabView({ref: "tabView", buttonsHidden: !this.state.userProfile && !this.props.showTabButtons}, 
             Tab({name: "call"}, 
               React.DOM.div({className: "content-area"}, 
                 CallUrlResult({client: this.props.client, 
                                notifications: this.props.notifications, 
                                callUrl: this.props.callUrl}), 
                 ToSView(null)
               )
             ), 
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -50,17 +50,19 @@ loop.panel = (function(_, mozL10n) {
         tabs.push(
           <div key={i} className={cx({tab: true, selected: isSelected})}>
             {tab.props.children}
           </div>
         );
       }, this);
       return (
         <div className="tab-view-container">
-          <ul className="tab-view">{tabButtons}</ul>
+          {!this.props.buttonsHidden
+            ? <ul className="tab-view">{tabButtons}</ul>
+            : null}
           {tabs}
         </div>
       );
     }
   });
 
   var Tab = React.createClass({
     render: function() {
@@ -434,16 +436,17 @@ loop.panel = (function(_, mozL10n) {
    */
   var PanelView = React.createClass({
     propTypes: {
       notifications: React.PropTypes.object.isRequired,
       client: React.PropTypes.object.isRequired,
       // Mostly used for UI components showcase and unit tests
       callUrl: React.PropTypes.string,
       userProfile: React.PropTypes.object,
+      showTabButtons: React.PropTypes.bool,
     },
 
     getInitialState: function() {
       return {
         userProfile: this.props.userProfile || navigator.mozLoop.userProfile,
       };
     },
 
@@ -470,17 +473,22 @@ loop.panel = (function(_, mozL10n) {
           detailsButtonLabel: serviceError.error.friendlyDetailsButtonLabel,
         });
       } else {
         this.props.notifications.remove(this.props.notifications.get("service-error"));
       }
     },
 
     _onStatusChanged: function() {
-      this.setState({userProfile: navigator.mozLoop.userProfile});
+      var profile = navigator.mozLoop.userProfile;
+      if (profile != this.state.userProfile) {
+        // On profile change (login, logout), switch back to the default tab.
+        this.selectTab("call");
+      }
+      this.setState({userProfile: profile});
       this.updateServiceErrors();
     },
 
     startForm: function(name, contact) {
       this.refs[name].initForm(contact);
       this.selectTab(name);
     },
 
@@ -503,17 +511,17 @@ loop.panel = (function(_, mozL10n) {
     render: function() {
       var NotificationListView = sharedViews.NotificationListView;
       var displayName = this.state.userProfile && this.state.userProfile.email ||
                         __("display_name_guest");
       return (
         <div>
           <NotificationListView notifications={this.props.notifications}
                                 clearOnDocumentHidden={true} />
-          <TabView ref="tabView">
+          <TabView ref="tabView" buttonsHidden={!this.state.userProfile && !this.props.showTabButtons}>
             <Tab name="call">
               <div className="content-area">
                 <CallUrlResult client={this.props.client}
                                notifications={this.props.notifications}
                                callUrl={this.props.callUrl} />
                 <ToSView />
               </div>
             </Tab>
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -135,17 +135,18 @@ describe("loop.panel", function() {
       fakeClient = {
         requestCallUrl: function(_, cb) {
           cb(null, callUrlData);
         }
       };
 
       view = TestUtils.renderIntoDocument(loop.panel.PanelView({
         notifications: notifications,
-        client: fakeClient
+        client: fakeClient,
+        showTabButtons: true,
       }));
 
       [callTab, contactsTab] =
         TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
     });
 
     describe('TabView', function() {
       it("should select contacts tab when clicking tab button", function() {
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -27,18 +27,18 @@ this.Translation = {
   STATE_OFFER: 0,
   STATE_TRANSLATING: 1,
   STATE_TRANSLATED: 2,
   STATE_ERROR: 3,
   STATE_UNAVAILABLE: 4,
 
   serviceUnavailable: false,
 
-  supportedSourceLanguages: ["de", "en", "es", "fr", "ja", "ko", "pt", "ru", "zh"],
-  supportedTargetLanguages: ["de", "en", "es", "fr", "ja", "ko", "pl", "pt", "ru", "tr", "vi", "zh"],
+  supportedSourceLanguages: ["bg", "cs", "de", "en", "es", "fr", "ja", "ko", "nl", "no", "pl", "pt", "ru", "tr", "vi", "zh"],
+  supportedTargetLanguages: ["bg", "cs", "de", "en", "es", "fr", "ja", "ko", "nl", "no", "pl", "pt", "ru", "tr", "vi", "zh"],
 
   _defaultTargetLanguage: "",
   get defaultTargetLanguage() {
     if (!this._defaultTargetLanguage) {
       this._defaultTargetLanguage = Cc["@mozilla.org/chrome/chrome-registry;1"]
                                       .getService(Ci.nsIXULChromeRegistry)
                                       .getSelectedLocale("global")
                                       .split("-")[0];
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/translation-infobar.xml
@@ -18,16 +18,31 @@
     <resources>
       <stylesheet src="chrome://global/skin/notification.css"/>
     </resources>
     <content>
       <xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type">
         <xul:hbox anonid="details" align="center" flex="1">
           <xul:image class="translate-infobar-element messageImage"
                      anonid="messageImage"/>
+          <xul:panel anonid="welcomePanel" class="translation-welcome-panel"
+                     type="arrow" align="start">
+            <xul:image class="translation-welcome-logo"/>
+            <xul:vbox flex="1" class="translation-welcome-content">
+              <xul:description class="translation-welcome-headline"
+                               anonid="welcomeHeadline"/>
+              <xul:description class="translation-welcome-body" anonid="welcomeBody"/>
+              <xul:hbox align="center">
+                <xul:label anonid="learnMore" class="plain text-link"/>
+                <xul:spacer flex="1"/>
+                <xul:button class="translate-infobar-element" anonid="thanksButton"
+                            onclick="this.parentNode.parentNode.parentNode.hidePopup();"/>
+              </xul:hbox>
+            </xul:vbox>
+          </xul:panel>
           <xul:deck anonid="translationStates" selectedIndex="0">
 
             <!-- offer to translate -->
             <xul:hbox class="translate-offer-box" align="center">
               <xul:label class="translate-infobar-element" value="&translation.thisPageIsIn.label;"/>
               <xul:menulist class="translate-infobar-element" anonid="detectedLanguage">
                 <xul:menupopup/>
               </xul:menulist>
@@ -193,16 +208,51 @@
             for (let [code, name] of targetLanguages)
               toLanguage.appendItem(name, code);
 
             if (aTranslation.translatedTo)
               toLanguage.value = aTranslation.translatedTo;
 
             if (aTranslation.state)
               this.state = aTranslation.state;
+
+            const kWelcomePref = "browser.translation.ui.welcomeMessageShown";
+            if (Services.prefs.prefHasUserValue(kWelcomePref))
+              return;
+
+            this.addEventListener("transitionend", function onShown() {
+              this.removeEventListener("transitionend", onShown);
+
+              // These strings are hardcoded because they need to reach beta
+              // without riding the trains.
+              let localizedStrings = {
+                en: ["Hey look! It's something new!",
+                     "Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!",
+                     "Learn more.",
+                     "Thanks"]
+              };
+
+              let locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+                             .getService(Ci.nsIXULChromeRegistry)
+                             .getSelectedLocale("browser");
+              if (!(locale in localizedStrings))
+                locale = "en";
+              let strings = localizedStrings[locale];
+
+              this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]);
+              this._getAnonElt("welcomeBody").textContent = strings[1];
+              this._getAnonElt("learnMore").setAttribute("value", strings[2]);
+              this._getAnonElt("thanksButton").setAttribute("label", strings[3]);
+
+              let panel = this._getAnonElt("welcomePanel");
+              panel.openPopup(this._getAnonElt("messageImage"),
+                              "bottomcenter topleft");
+
+              Services.prefs.setBoolPref(kWelcomePref, true);
+            });
           ]]>
         </body>
       </method>
 
       <method name="_getAnonElt">
         <parameter name="aAnonId"/>
         <body>
           return document.getAnonymousElementByAttribute(this, "anonid", aAnonId);
--- a/browser/locales/en-US/chrome/browser/devtools/webide.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.properties
@@ -14,17 +14,17 @@ local_runtime=Local Runtime
 remote_runtime=Remote Runtime
 remote_runtime_promptTitle=Remote Runtime
 remote_runtime_promptMessage=hostname:port
 
 importPackagedApp_title=Select Directory
 importHostedApp_title=Open Hosted App
 importHostedApp_header=Enter Manifest URL
 
-notification_showTroubleShooting_label=troubleshooting
+notification_showTroubleShooting_label=Troubleshooting
 notification_showTroubleShooting_accesskey=t
 
 # LOCALIZATION NOTE (project_tab_loading): This is shown as a temporary tab
 # title for browser tab projects when the tab is still loading.
 project_tab_loading=Loading…
 
 # These messages appear in a notification box when an error occur.
 
--- a/browser/themes/shared/translation/infobar.inc.css
+++ b/browser/themes/shared/translation/infobar.inc.css
@@ -59,8 +59,33 @@ notification[value="translation"] menuli
 
 .translation-attribution > label {
   margin-bottom: 0;
 }
 
 .translation-attribution > image {
   width: 70px;
 }
+
+.translation-welcome-panel {
+  width: 305px;
+}
+
+.translation-welcome-logo {
+  height: 32px;
+  width: 32px;
+  list-style-image: url(chrome://browser/skin/translation-16@2x.png);
+  -moz-image-region: rect(0, 64px, 32px, 32px);
+}
+
+.translation-welcome-content {
+  -moz-margin-start: 16px;
+}
+
+.translation-welcome-headline {
+  font-size: larger;
+  font-weight: bold;
+}
+
+.translation-welcome-body {
+  padding: 1em 0;
+  margin: 0 0;
+}
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -18,20 +18,18 @@
 #include ../services/manifests/FxAccountAndroidManifest_permissions.xml.in
 #include ../services/manifests/HealthReportAndroidManifest_permissions.xml.in
 #include ../services/manifests/SyncAndroidManifest_permissions.xml.in
 
 #ifdef MOZ_ANDROID_SEARCH_ACTIVITY
 #include ../search/manifests/SearchAndroidManifest_permissions.xml.in
 #endif
 
-#ifndef RELEASE_BUILD
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
-#endif
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
     <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
     <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
 
--- a/mobile/android/base/home/TwoLinePageRow.java
+++ b/mobile/android/base/home/TwoLinePageRow.java
@@ -2,17 +2,19 @@
  * 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.gecko.home;
 
 import java.lang.ref.WeakReference;
 
+import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.ReaderModeUtils;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.URLColumns;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.widget.FaviconView;
 
@@ -243,17 +245,22 @@ public class TwoLinePageRow extends Line
         // No point updating the below things if URL has not changed. Prevents evil Favicon flicker.
         if (url.equals(mPageUrl)) {
             return;
         }
 
         // Blank the Favicon, so we don't show the wrong Favicon if we scroll and miss DB.
         mFavicon.clearImage();
         Favicons.cancelFaviconLoad(mLoadFaviconJobId);
-        mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(getContext(), url, mFaviconListener);
+
+        // Displayed RecentTabsPanel urls may refer to pages openned in readermode, so we
+        // remove the about:reader prefix to ensure the Favicon loads properly.
+        final String pageURL = AboutPages.isAboutReader(url) ?
+            ReaderModeUtils.getUrlFromAboutReader(url) : url;
+        mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(getContext(), pageURL, mFaviconListener);
 
         updateDisplayedUrl(url);
     }
 
     /**
      * Update the data displayed by this row.
      * <p>
      * This method must be invoked on the UI thread.
--- a/mobile/android/base/locales/Makefile.in
+++ b/mobile/android/base/locales/Makefile.in
@@ -110,21 +110,23 @@ endef
 
 # L10NBASEDIR is not defined for en-US.
 l10n-srcdir := $(if $(filter en-US,$(AB_CD)),,$(or $(realpath $(L10NBASEDIR)),$(abspath $(L10NBASEDIR)))/$(AB_CD)/mobile/chrome)
 
 $(eval $(call generated_file_template,suggestedsites,suggestedsites.json))
 
 $(suggestedsites-dstdir-raw)/suggestedsites.json: FORCE
 	$(call py_action,generate_suggestedsites, \
+		--verbose \
 		--android-package-name=$(ANDROID_PACKAGE_NAME) \
 		--resources=$(srcdir)/../resources \
 		$(if $(filter en-US,$(AB_CD)),,--srcdir=$(l10n-srcdir)) \
 		--srcdir=$(topsrcdir)/mobile/locales/en-US/chrome \
 		$@)
 
 $(eval $(call generated_file_template,browsersearch,browsersearch.json))
 
 $(browsersearch-dstdir-raw)/browsersearch.json: FORCE
 	$(call py_action,generate_browsersearch, \
+		--verbose \
 		$(if $(filter en-US,$(AB_CD)),,--srcdir=$(l10n-srcdir)) \
 		--srcdir=$(topsrcdir)/mobile/locales/en-US/chrome \
 		$@)
--- a/mobile/android/chrome/content/aboutAddons.js
+++ b/mobile/android/chrome/content/aboutAddons.js
@@ -522,16 +522,31 @@ var Addons = {
       element = this._createItemForAddon(aAddon);
       list.insertBefore(element, list.firstElementChild);
     }
 
     if (needsRestart)
       element.setAttribute("opType", "needs-restart");
   },
 
+  onInstalled: function(aAddon) {
+    let list = document.getElementById("addons-list");
+    let element = this._getElementForAddon(aAddon.id);
+    if (!element) {
+      element = this._createItemForAddon(aAddon);
+      list.insertBefore(element, list.firstElementChild);
+    }
+  },
+
+  onUninstalled: function(aAddon) {
+    let list = document.getElementById("addons-list");
+    let element = this._getElementForAddon(aAddon.id);
+    list.removeChild(element);
+  },
+
   onInstallFailed: function(aInstall) {
   },
 
   onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {
   },
 
   onDownloadFailed: function(aInstall) {
   },
--- a/mobile/android/chrome/content/aboutApps.xhtml
+++ b/mobile/android/chrome/content/aboutApps.xhtml
@@ -30,17 +30,16 @@
   <body dir="&locale.dir;">
 
     <menu type="context" id="appmenu">
       <menuitem id="uninstallLabel" label="&aboutApps.uninstall;"></menuitem>
     </menu>
 
     <div class="header">
       <div>&aboutApps.header;</div>
-      <div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL"/>
     </div>
 
     <div id="main-container" class="hidden">
       <div>
         <div class="spacer" id="spacer1"> </div>
         <div id="appgrid"/>
         <div class="spacer" id="spacer1"> </div>
       </div>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -824,16 +824,23 @@ var BrowserApp = {
     Services.obs.notifyObservers(null, "Passwords:Init", "");
 
     // Migrate user-set "plugins.click_to_play" pref. See bug 884694.
     // Because the default value is true, a user-set pref means that the pref was set to false.
     if (Services.prefs.prefHasUserValue("plugins.click_to_play")) {
       Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED);
       Services.prefs.clearUserPref("plugins.click_to_play");
     }
+
+    // Set the search activity default pref on app upgrade if it has not been set already.
+    if (this._startupStatus === "upgrade" &&
+        !Services.prefs.prefHasUserValue("searchActivity.default.migrated")) {
+      Services.prefs.setBoolPref("searchActivity.default.migrated", true);
+      SearchEngines.migrateSearchActivityDefaultPref();
+    }
   },
 
   shutdown: function shutdown() {
     NativeWindow.uninit();
     LightWeightThemeWebInstaller.uninit();
     FormAssistant.uninit();
     IndexedDB.uninit();
     ViewportHandler.uninit();
@@ -6904,16 +6911,20 @@ var SearchEngines = {
         }
         break;
       default:
         dump("Unexpected message type observed: " + aTopic);
         break;
     }
   },
 
+  migrateSearchActivityDefaultPref: function migrateSearchActivityDefaultPref() {
+    Services.search.init(() => this._setSearchActivityDefaultPref(Services.search.defaultEngine));
+  },
+
   // Updates the search activity pref when the default engine changes.
   _setSearchActivityDefaultPref: function _setSearchActivityDefaultPref(engine) {
     // Helper function copied from nsSearchService.js. This is the logic that is used
     // to create file names for search plugin XML serialized to disk.
     function sanitizeName(aName) {
       const maxLength = 60;
       const minLength = 1;
       let name = aName.toLowerCase();
--- a/mobile/android/themes/core/aboutApps.css
+++ b/mobile/android/themes/core/aboutApps.css
@@ -1,23 +1,16 @@
 /* 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/. */
 
 .app:active {
   background-color: #febc2b;
 }
 
-#header-button {
-  background-image: url("chrome://browser/skin/images/marketplace-logo.png"), url("chrome://browser/skin/images/chevron.png");
-  background-size: 32px 32px, 8px 20px;
-  background-position: left, right 0.5em center;
-  -moz-padding-start: 2.5em;
-}
-
 #main-container {
   padding: 2em;
   background-color: #EEF2F5;
   border-bottom: 1px solid #BAC2AC;
 }
 
 .hidden {
   display: none;
--- a/python/moz.build
+++ b/python/moz.build
@@ -18,16 +18,17 @@ PYTHON_UNIT_TESTS += [
     'mach/mach/test/test_config.py',
     'mach/mach/test/test_entry_point.py',
     'mach/mach/test/test_error_output.py',
     'mach/mach/test/test_logger.py',
     'mozbuild/dumbmake/test/__init__.py',
     'mozbuild/dumbmake/test/test_dumbmake.py',
     'mozbuild/mozbuild/test/__init__.py',
     'mozbuild/mozbuild/test/action/test_buildlist.py',
+    'mozbuild/mozbuild/test/action/test_generate_browsersearch.py',
     'mozbuild/mozbuild/test/backend/__init__.py',
     'mozbuild/mozbuild/test/backend/common.py',
     'mozbuild/mozbuild/test/backend/test_android_eclipse.py',
     'mozbuild/mozbuild/test/backend/test_configenvironment.py',
     'mozbuild/mozbuild/test/backend/test_recursivemake.py',
     'mozbuild/mozbuild/test/backend/test_visualstudio.py',
     'mozbuild/mozbuild/test/common.py',
     'mozbuild/mozbuild/test/compilation/__init__.py',
--- a/python/mozbuild/mozbuild/action/generate_browsersearch.py
+++ b/python/mozbuild/mozbuild/action/generate_browsersearch.py
@@ -17,19 +17,23 @@ 2. Read the default search plugin from t
 3. Read the list of search plugins from the 'browser.search.order.INDEX'
 properties with values identifying particular search plugins by name.
 
 4. Generate a JSON representation of 2. and 3., and write the result to
 browsersearch.json in the locale-specific raw resource directory
 e.g. raw/browsersearch.json, raw-pt-rBR/browsersearch.json.
 '''
 
-from __future__ import print_function
+from __future__ import (
+    print_function,
+    unicode_literals,
+)
 
 import argparse
+import codecs
 import json
 import re
 import sys
 import os
 
 from mozbuild.dotproperties import (
     DotProperties,
 )
@@ -67,18 +71,19 @@ def main(args):
 
     # Use reversed order so that the first srcdir has higher priority to override keys.
     properties = merge_properties('region.properties', reversed(opts.srcdir))
 
     default = properties.get('browser.search.defaultenginename')
     engines = properties.get_list('browser.search.order')
 
     if opts.verbose:
-        print('Read {len} engines: {engines}'.format(len=len(engines), engines=engines))
-        print("Default engine is '{default}'.".format(default=default))
+        writer = codecs.getwriter('utf-8')(sys.stdout)
+        print('Read {len} engines: {engines}'.format(len=len(engines), engines=engines), file=writer)
+        print("Default engine is '{default}'.".format(default=default), file=writer)
 
     browsersearch = {}
     browsersearch['default'] = default
     browsersearch['engines'] = engines
 
     # FileAvoidWrite creates its parent directories.
     output = os.path.abspath(opts.output)
     fh = FileAvoidWrite(output)
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/invalid/region.properties
@@ -0,0 +1,12 @@
+# A region.properties file with invalid unicode byte sequences.  The
+# sequences were cribbed from Markus Kuhn's "UTF-8 decoder capability
+# and stress test", available at
+# http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
+
+# 3.5  Impossible bytes                                                         |
+#                                                                               |
+# The following two bytes cannot appear in a correct UTF-8 string               |
+#                                                                               |
+# 3.5.1  fe = ""                                                               |
+# 3.5.2  ff = ""                                                               |
+# 3.5.3  fe fe ff ff = ""                                                   |
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/data/valid-zh-CN/region.properties
@@ -0,0 +1,37 @@
+# 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/.
+
+# Default search engine
+browser.search.defaultenginename=百度
+
+# Search engine order (order displayed in the search bar dropdown)s
+browser.search.order.1=百度
+browser.search.order.2=Google
+
+# This is the default set of web based feed handlers shown in the reader
+# selection UI
+browser.contentHandlers.types.0.title=Bloglines
+browser.contentHandlers.types.0.uri=http://www.bloglines.com/login?r=/sub/%s
+
+# increment this number when anything gets changed in the list below.  This will
+# cause Firefox to re-read these prefs and inject any new handlers into the
+# profile database.  Note that "new" is defined as "has a different URL"; this
+# means that it's not possible to update the name of existing handler, so
+# don't make any spelling errors here.
+gecko.handlerService.defaultHandlersVersion=3
+
+# The default set of protocol handlers for webcal:
+gecko.handlerService.schemes.webcal.0.name=30 Boxes
+gecko.handlerService.schemes.webcal.0.uriTemplate=https://30boxes.com/external/widget?refer=ff&url=%s
+
+# The default set of protocol handlers for mailto:
+gecko.handlerService.schemes.mailto.0.name=Yahoo! 邮件
+gecko.handlerService.schemes.mailto.0.uriTemplate=https://compose.mail.yahoo.com/?To=%s
+gecko.handlerService.schemes.mailto.1.name=Gmail
+gecko.handlerService.schemes.mailto.1.uriTemplate=https://mail.google.com/mail/?extsrc=mailto&url=%s
+
+# This is the default set of web based feed handlers shown in the reader
+# selection UI
+browser.contentHandlers.types.0.title=My Yahoo!
+browser.contentHandlers.types.0.uri=http://www.bloglines.com/login?r=/sub/%s
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_generate_browsersearch.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import unicode_literals
+
+import json
+import os
+import unittest
+
+import mozunit
+
+import mozbuild.action.generate_browsersearch as generate_browsersearch
+
+from mozfile.mozfile import (
+    NamedTemporaryFile,
+    TemporaryDirectory,
+)
+
+import mozpack.path as mozpath
+
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, 'data')
+
+
+class TestGenerateBrowserSearch(unittest.TestCase):
+    """
+    Unit tests for generate_browsersearch.py.
+    """
+
+    def _test_one(self, name):
+        with TemporaryDirectory() as tmpdir:
+            with NamedTemporaryFile(mode='r+') as temp:
+                srcdir = os.path.join(test_data_path, name)
+
+                generate_browsersearch.main([
+                    '--verbose',
+                    '--srcdir', srcdir,
+                    temp.name])
+                return json.load(temp)
+
+    def test_valid_unicode(self):
+        o = self._test_one('valid-zh-CN')
+        self.assertEquals(o['default'], '百度')
+        self.assertEquals(o['engines'], ['百度', 'Google'])
+
+    def test_invalid_unicode(self):
+        with self.assertRaises(UnicodeDecodeError):
+            self._test_one('invalid')
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
+++ b/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
@@ -59,16 +59,17 @@ const SearchAutocompleteProviderInternal
 
   initialized: false,
 
   observe: function (subject, topic, data) {
     switch (data) {
       case "engine-added":
       case "engine-changed":
       case "engine-removed":
+      case "engine-current":
         this._refresh();
     }
   },
 
   _refresh: function () {
     this.priorityMatches = [];
     this.aliasMatches = [];
     this.defaultMatch = null;
--- a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_current.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_current.js
@@ -26,10 +26,22 @@ add_task(function*() {
 
   do_log_info("search engine, multiple words");
   yield check_autocomplete({
     search: "mozzarella cheese",
     searchParam: "enable-actions",
     matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "mozzarella cheese", searchQuery: "mozzarella cheese"}), title: "MozSearch" }, ]
   });
 
+  do_log_info("search engine, after current engine has changed");
+  Services.search.addEngineWithDetails("MozSearch2", "", "", "", "GET",
+                                       "http://s.example.com/search2");
+  engine = Services.search.getEngineByName("MozSearch2");
+  notEqual(Services.search.currentEngine, engine, "New engine shouldn't be the current engine yet");
+  Services.search.currentEngine = engine;
+  yield check_autocomplete({
+    search: "mozilla",
+    searchParam: "enable-actions",
+    matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch2", input: "mozilla", searchQuery: "mozilla"}), title: "MozSearch2" }, ]
+  });
+
   yield cleanup();
 });
\ No newline at end of file