Merge fx-team to m-c a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Mon, 06 Oct 2014 19:29:41 -0700
changeset 209072 9ee9e193fc48b0c7e6f27337c64fe4780842ea87
parent 209041 0208159db84df29d10dd18705a53c6d5334db613 (current diff)
parent 209071 650603771acde4563e4b8b20d3efd213c746d9cb (diff)
child 209091 f65726bd726555bd7cab954c3347931f4c9d4c00
child 209096 896e255af48ebaf6fc0b5581865fcdbd53e07733
child 209104 be8ee7774bb95b714ec5880adf7bfb6224374e29
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmerge
milestone35.0a1
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