merge fx-team into m-c
authorGavin Sharp <gavin@gavinsharp.com>
Thu, 15 Mar 2012 21:39:33 -0700
changeset 89456 e5f6caa40409afb037163325f6a5d7bf581f94ae
parent 89446 9d19b56f39c5e77305aeb67892a4c43cb0f1303c (current diff)
parent 89455 6987476adb876be9ccf6e3e9109268ec7f879226 (diff)
child 89457 d2d5ffdd5ef646a4da4ac7a3683196f84812fec0
child 89589 34a7a331811fa7663e7cad9ea84fd4d4b27ba00a
push id22256
push usergsharp@mozilla.com
push dateFri, 16 Mar 2012 04:41:46 +0000
treeherdermozilla-central@e5f6caa40409 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone14.0a1
first release with
nightly linux32
e5f6caa40409 / 14.0a1 / 20120316031151 / files
nightly linux64
e5f6caa40409 / 14.0a1 / 20120316031151 / files
nightly mac
e5f6caa40409 / 14.0a1 / 20120316031151 / files
nightly win32
e5f6caa40409 / 14.0a1 / 20120316031151 / files
nightly win64
e5f6caa40409 / 14.0a1 / 20120316031151 / 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 into m-c
browser/base/content/aboutHome-restore-icon-small.png
browser/base/content/aboutHome-restore-icon.png
browser/base/content/aboutHome-snippet1.png
browser/base/content/aboutHome-snippet2.png
browser/base/content/aboutHome.css
browser/base/content/aboutHome.js
browser/base/content/aboutHome.xhtml
browser/base/content/browser.js
deleted file mode 100644
index e6b80682e5f4a46023977fb26b65e36095832cce..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 82b2bb7f51fe39a9f2fce05d7e310c2378f46785..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
rename from browser/base/content/aboutHome.css
rename to browser/base/content/abouthome/aboutHome.css
--- a/browser/base/content/aboutHome.css
+++ b/browser/base/content/abouthome/aboutHome.css
@@ -19,16 +19,17 @@
  * The Initial Developer of the Original Code is the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Marco Bonardo <mak77@bonardo.net> (original author)
  *   Mihai Sucan <mihai.sucan@gmail.com>
  *   Stephen Horlander <shorlander@mozilla.com>
+ *   Frank Yan <fyan@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -36,349 +37,326 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 %endif
 
 html {
-  font-family: sans-serif;
-  background: -moz-Field;
-  color: -moz-FieldText;
+  font: message-box;
+  font-size: 100%;
+  background-color: hsl(0,0%,90%);
+  background-image: url(chrome://browser/content/abouthome/noise.png),
+                    -moz-linear-gradient(hsla(0,0%,100%,.7), hsla(0,0%,100%,.4));
+  background-attachment: fixed;
+  color: #000;
   height: 100%;
 }
 
 body {
-  display: inline-block;
-  position: relative;
   margin: 0;
+  height: 100%;
+}
+
+#container {
+  display: -moz-box;
+  -moz-box-orient: vertical;
   width: 100%;
   height: 100%;
 }
 
+input,
+button {
+  font-size: inherit;
+  font-family: inherit;
+}
+
 a {
+  color: -moz-nativehyperlinktext;
   text-decoration: none;
 }
 
-a:hover {
-  text-decoration: underline;
+.spacer {
+  -moz-box-flex: 1;
 }
 
-#brandStart {
+#topSection {
   text-align: center;
-  height: 19%;
-  max-height: 256px;
-  min-height: 92px;
 }
 
-#brandStartSpacer {
-  height: 6.5%;
-}
-
-#brandStartLogo {
-  height: 100%;
+#brandLogo {
+  height: 154px;
+  margin: 22px 0 31px;
 }
 
-#searchContainer {
-  height: 15%;
-  min-height: 90px;
-}
-
-#searchContainer::before {
-  content: " ";
-  display: block;
-  height: 23%;
+#searchForm,
+#snippets {
+  width: 470px;
 }
 
 #searchForm {
-  display: table;
-  width: 100%;
-  max-width: 1830px;
-  margin: 0 auto;
-}
-
-@media all and (max-height: 700px) {
-  #searchContainer { height: 20% }
-}
-
-@media all and (max-height: 500px) {
-  #searchContainer { height: 25% }
-}
-
-@media all and (max-height: 370px) {
-  #searchContainer { height: 30% }
+  display: -moz-box;
 }
 
 #searchLogoContainer {
-  display: table-cell;
-  width: 30%;
-  text-align: end;
-  line-height: 32px;
+  display: -moz-box;
+  -moz-box-align: center;
+  padding-top: 2px;
+  -moz-padding-end: 8px;
 }
 
 #searchEngineLogo {
-  -moz-margin-end: 2.5%;
-  vertical-align: middle;
-}
-
-#searchInputContainer {
-  display: table-cell;
-  width: 38%;
-  max-width: 700px;
-  min-width: 150px;
+  display: inline-block;
 }
 
 #searchText {
-  width: 100%;
-  height: 24px;
-  padding: 3px 6px;
-  border-radius: 2px;
-  border: 1px solid rgb(150,150,150);
-  border-top-color: rgb(100,100,100);
-  box-shadow: 0 1px 0 rgba(255,255,255,0.5);
-  font-size: 1.2em;
-}
-
-#searchButtons {
-  display: table-cell;
-  width: 31%;
-  -moz-padding-start: 13px;
-  vertical-align: top;
+  -moz-box-flex: 1;
+  padding: 6px 8px;
+  background: hsla(0,0%,100%,.9) padding-box;
+  border: 1px solid;
+  border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+  box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
+              0 0 2px hsla(210,65%,9%,.1) inset,
+              0 1px 0 hsla(0,0%,100%,.2);
+  border-radius: 2.5px 0 0 2.5px;
 }
 
-@media all and (max-width: 470px) {
-  #searchLogoContainer { width: 10% }
-  #searchButtons { width: 11% }
-  #searchInputContainer { width: 40% }
+body[dir=rtl] #searchText {
+  border-radius: 0 2.5px 2.5px 0;
 }
 
-@media all and (min-width: 470px) and (max-width: 600px) {
-  #searchLogoContainer { width: 15% }
-  #searchButtons { width: 16%; white-space: nowrap }
-  #searchInputContainer { width: 45% }
-}
-
-@media all and (min-width: 600px) and (max-width: 850px) {
-  #searchLogoContainer { width: 20% }
-  #searchButtons { width: 21%; white-space: nowrap }
-  #searchInputContainer { width: 49% }
+#searchText:focus {
+  border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
 }
 
 #searchSubmit {
-  background: -moz-linear-gradient(#f1f1f1, #dfdfdf);
-  padding: 4px 8px;
-  height: 32px;
-  border: 1px solid #ccc;
-  border-top-color: #ccc;
-  border-bottom-color: #999;
-  -moz-border-start-color: #afafaf;
-  -moz-border-end-color: #999;
-  box-shadow: 1px 1px 0 #e7e7e7,
-              0 1px 0 #fcfcfc inset,
-              0 -1px 0 #d7d7d7 inset;
-  font-size: 1em;
-  color: #000;
+  -moz-margin-start: -1px;
+  background: -moz-linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+  padding: 0 9px;
+  border: 1px solid;
+  border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+  -moz-border-start: 1px solid transparent;
+  border-radius: 0 2.5px 2.5px 0;
+  box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
+              0 1px 0 hsla(0,0%,100%,.2);
   cursor: pointer;
+  -moz-transition-property: background-color, border-color, box-shadow;
+  -moz-transition-duration: 150ms;
 }
 
 body[dir=rtl] #searchSubmit {
-  box-shadow: -1px 1px 0 #e7e7e7,
-              0 1px 0 #fcfcfc inset,
-              0 -1px 0 #d7d7d7 inset;
+  border-radius: 2.5px 0 0 2.5px;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText + #searchSubmit:hover {
+  border-color: #59b5fc #45a3e7 #3294d5;
+  color: white;
 }
 
-#searchSubmit:active {
-  background: -moz-linear-gradient(#c5c5c5, #c5c5c5);
-  box-shadow: 1px 1px 0 #e7e7e7;
+#searchText:focus + #searchSubmit {
+  background-image: -moz-linear-gradient(#4cb1ff, #1793e5);
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+              0 0 0 1px hsla(0,0%,100%,.1) inset,
+              0 1px 0 hsla(210,54%,20%,.03);
 }
 
-body[dir=rtl] #searchSubmit:active {
-  box-shadow: -1px 1px 0 #e7e7e7;
+#searchText + #searchSubmit:hover {
+  background-image: -moz-linear-gradient(#66bdff, #0d9eff);
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+              0 0 0 1px hsla(0,0%,100%,.1) inset,
+              0 1px 0 hsla(210,54%,20%,.03),
+              0 0 4px hsla(206,100%,20%,.2);
 }
 
-#contentContainer {
-  height: 30%;
-  background-image: -moz-radial-gradient(center top, ellipse farthest-side, rgba(16,83,130,.5), rgba(16,83,130,0) 75%),
-                    -moz-radial-gradient(center top, ellipse farthest-side, rgba(180,218,244,.5), rgba(180,218,244,0)),
-                    -moz-radial-gradient(center top, ellipse farthest-side, rgba(180,218,244,.3), rgba(180,218,244,0));
-  background-size: 100% 5px,
-                   100% 50px,
-                   100% 100%;
-  background-repeat: no-repeat;
+#searchText + #searchSubmit:hover:active {
+  box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
+              0 0 1px hsla(211,79%,6%,.2) inset;
+  -moz-transition-duration: 0ms;
 }
 
-@media all and (max-height: 400px) {
-  #contentContainer { height: 20% }
+#defaultSnippet1,
+#defaultSnippet2 {
+  display: block;
+  min-height: 38px;
+  background: 30px center no-repeat;
+  padding: 6px 0;
+  -moz-padding-start: 79px;
 }
 
-#snippetContainer {
-  position: relative;
-  top: -24px;
-  text-align: center;
+body[dir=rtl] #defaultSnippet1,
+body[dir=rtl] #defaultSnippet2 {
+  background-position: right 30px center;
+}
+
+#defaultSnippet1 {
+  background-image: url("chrome://browser/content/abouthome/snippet1.png");
+}
+
+#defaultSnippet2 {
+  background-image: url("chrome://browser/content/abouthome/snippet2.png");
 }
 
 #snippets {
   display: inline-block;
-  padding: 14px;
-  width: 30%;
-  max-width: 600px;
-  background-image: -moz-linear-gradient(rgba(255,255,255,.8), rgba(255,255,255,.1));
-  background-color: rgb(250,250,250);
-  border-radius: 4px;
-  box-shadow: 0 1px 0 rgba(255,255,255,.8) inset,
-              0 -2px 0 rgba(0,0,0,.1) inset,
-              0 0 10px rgba(255,255,255,.5) inset,
-              0 0 0 1px rgba(0,0,0,.1),
-              0 2px 4px rgba(0,0,0,.2);
-  color: rgb(60,60,60);
-  font-size: .85em;
-  cursor: pointer;
+  text-align: start;
+  margin: 12px 0;
+  color: #3c3c3c;
+  font-size: 75%;
+}
+
+#launcher {
+  display: -moz-box;
+  -moz-box-align: center;
+  -moz-box-pack: center;
+  width: 100%;
+  background-color: hsla(0,0%,0%,.03);
+  border-top: 1px solid hsla(0,0%,0%,.03);
+  box-shadow: 0 1px 2px hsla(0,0%,0%,.02) inset,
+              0 -1px 0 hsla(0,0%,100%,.25);
 }
 
-#snippets:empty {
-  visibility: hidden;
-}
-
-@media all and (max-width: 470px) {
-  #snippets { width: 65% }
+#launcher:not([session]),
+body[narrow] #launcher[session] {
+  display: block; /* display separator and restore button on separate lines */
+  text-align: center;
+  white-space: nowrap; /* prevent navigational buttons from wrapping */
 }
 
-@media all and (min-width: 470px) and (max-width: 850px) {
-  #snippets { width: 45% }
-}
-
-#snippets:hover {
-  background-color: rgb(255,255,255);
-  box-shadow: 0 1px 0 rgba(255,255,255,.8) inset,
-              0 -2px 0 rgba(0,0,0,.1) inset,
-              0 0 10px rgba(255,255,255,.5) inset,
-              0 0 5px rgba(0,0,0,.1),
-              0 0 0 1px rgba(0,0,0,.1),
-              0 2px 4px rgba(0,0,0,.2);
+.launchButton {
+  display: -moz-box;
+  -moz-box-orient: vertical;
+  margin: 16px 1px;
+  padding: 14px 6px;
+  min-width: 88px;
+  max-width: 176px;
+  background: transparent padding-box;
+  border: 1px solid transparent;
+  border-radius: 2.5px;
+  color: #525c66;
+  font-size: 75%;
+  cursor: pointer;
+  -moz-transition-property: background-color, border-color, box-shadow;
+  -moz-transition-duration: 150ms;
 }
 
-#snippets:hover:active {
-  background-color: rgb(210,210,210);
-  box-shadow: 0 2px 3px rgba(0,0,0,.3) inset,
-              0 1px 0 rgba(255,255,255,.5);
+body[narrow] #launcher[session] > .launchButton {
+  margin: 4px 1px;
+  max-height: 85px;
+  vertical-align: top;
+  white-space: normal;
 }
 
-#defaultSnippet1,
-#defaultSnippet2 {
-  display: table-row;
-  text-align: start;
+.launchButton:hover {
+  background-color: hsla(211,79%,6%,.03);
+  border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
 }
 
-#defaultSnippet1::before,
-#defaultSnippet2::before {
-  display: table-cell;
-  vertical-align: middle;
-  -moz-padding-end: 1em;
+.launchButton:hover:active {
+  background-image: -moz-linear-gradient(hsla(211,79%,6%,.02), hsla(211,79%,6%,.05));
+  border-color: hsla(210,54%,20%,.2) hsla(210,54%,20%,.23) hsla(210,54%,20%,.25);
+  box-shadow: 0 1px 1px hsla(211,79%,6%,.05) inset,
+              0 0 1px hsla(211,79%,6%,.1) inset;
+  -moz-transition-duration: 0ms;
 }
 
-#defaultSnippet1::before {
-  content: url("chrome://browser/content/aboutHome-snippet1.png");
-}
-#defaultSnippet2::before {
-  content: url("chrome://browser/content/aboutHome-snippet2.png");
+#launcher:not([session]) > #restorePreviousSessionSeparator,
+#launcher:not([session]) > #restorePreviousSession {
+  display: none;
 }
 
-#sessionRestoreContainer {
-  padding-top: 1.5%;
-  text-align: center;
+#restorePreviousSessionSeparator {
+  width: 3px;
+  height: 116px;
+  margin: 0 10px;
+  background-image: -moz-linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+                    -moz-linear-gradient(hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+                    -moz-linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+  background-position: left top, center, right bottom;
+  background-size: 1px auto;
+  background-repeat: no-repeat;
 }
 
-@media all and (max-height: 500px) {
-  #sessionRestoreContainer {
-    position: relative;
-    top: -15px;
-    padding-top: 0;
-  }
+body[narrow] #restorePreviousSessionSeparator {
+  margin: 0 auto;
+  width: 512px;
+  height: 3px;
+  background-image: -moz-linear-gradient(0, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+                    -moz-linear-gradient(0, hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+                    -moz-linear-gradient(0, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+  background-size: auto 1px;
 }
 
 #restorePreviousSession {
-  padding: 10px;
-  border: 0;
-  border-radius: 4px;
-  box-shadow: 0 0 0 1px rgba(9,37,59,0),
-              0 1px 2px rgba(9,37,59,0),
-              0 0 10px rgba(255,255,255,0),
-              0 -3px 0 rgba(180,194,212,0) inset;
-  -moz-transition-property: background-color, box-shadow;
-  -moz-transition-duration: 0.25s;
-  -moz-transition-timing-function: ease-out;
-  background: transparent;
-  color: rgb(50,50,50);
-  font-weight: bold;
-  font-size: 1em;
-  cursor: pointer;
+  max-width: none;
+  font-size: 90%;
+}
+
+body[narrow] #restorePreviousSession {
+  font-size: 80%;
+}
+
+.launchButton::before {
+  display: block;
+  margin-bottom: 6px;
+  line-height: 0; /* remove extra vertical space due to non-zero font-size */
+}
+
+#bookmarks::before {
+  content: url("chrome://browser/content/abouthome/bookmarks.png");
+}
+
+#history::before {
+  content: url("chrome://browser/content/abouthome/history.png");
+}
+
+#settings::before {
+  content: url("chrome://browser/content/abouthome/settings.png");
+}
+
+#addons::before {
+  content: url("chrome://browser/content/abouthome/addons.png");
+}
+
+#downloads::before {
+  content: url("chrome://browser/content/abouthome/downloads.png");
+}
+
+#sync::before {
+  content: url("chrome://browser/content/abouthome/sync.png");
 }
 
 #restorePreviousSession::before {
-  display: inline-block;
-  content: url("chrome://browser/content/aboutHome-restore-icon.png");
-  -moz-margin-end: 10px;
+  content: url("chrome://browser/content/abouthome/restore-large.png");
+  display: inline-block; /* display on same line as text label */
   vertical-align: middle;
-  height: 66px; /* Needed to avoid a blank space under the image */
+  margin-bottom: 0;
+  -moz-margin-end: 8px;
 }
 
 body[dir=rtl] #restorePreviousSession::before {
   -moz-transform: scaleX(-1);
 }
 
-@media all and (max-height: 500px) {
-  #restorePreviousSession::before {
-    content: url("chrome://browser/content/aboutHome-restore-icon-small.png");
-    height: 41px;
-  }
+body[narrow] #restorePreviousSession::before {
+  content: url("chrome://browser/content/abouthome/restore.png");
 }
 
-@media all and (max-width: 500px) {
-  #restorePreviousSession::before { 
-    content: url("chrome://browser/content/aboutHome-restore-icon-small.png");
-    height: 41px;
-  }
-}
-
-#restorePreviousSession:disabled {
-  display: none;
-}
-
-#restorePreviousSession:hover {
-  background-image: -moz-linear-gradient(rgba(255,255,255,.7), rgba(255,255,255,.2));
-  border-radius: 4px;
-  box-shadow: 0 0 0 1px rgba(9,37,59,.2),
-              0 1px 2px rgba(9,37,59,.2),
-              0 0 10px rgba(255,255,255,.4),
-              0 -3px 0 rgba(180,194,212,.3) inset;
+#aboutMozilla {
+  display: block;
+  position: relative; /* pin wordmark to edge of document, not of viewport */
+  -moz-box-ordinal-group: 0;
+  opacity: .5;
+  -moz-transition: opacity 150ms;
 }
 
-#restorePreviousSession:hover:active {
-  background-image: -moz-linear-gradient(rgba(255,255,255,.0), rgba(255,255,255,.2));
-  background-color: rgba(23,75,115,.1);
-  box-shadow: 0 0 0 1px rgba(9,37,59,.2),
-              0 1px 2px rgba(9,37,59,.4) inset,
-              0 1px 5px rgba(9,37,59,.15) inset;
+#aboutMozilla:hover {
+  opacity: 1;
 }
 
-#bottomSection {
+#aboutMozilla::before {
+  content: url("chrome://browser/content/abouthome/mozilla.png");
+  display: block;
   position: absolute;
-  color: rgb(150,150,150);
-  font-size: .8em;
-  width: 100%;
-  text-align: center;
-  bottom: 2%;
+  top: 12px;
+  right: 12px;
 }
-
-#syncLinksContainer {
-  padding-top: 1em;
-}
-
-.sync-link {
-  padding: 1em;
-}
-
-@media all and (max-height: 370px) {
-  #bottomSection {
-    visibility: hidden;
-  }
-}
rename from browser/base/content/aboutHome.js
rename to browser/base/content/abouthome/aboutHome.js
--- a/browser/base/content/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Marco Bonardo <mak77@bonardo.net> (original author)
  *   Mihai Sucan <mihai.sucan@gmail.com>
+ *   Frank Yan <fyan@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -150,16 +151,19 @@ const SNIPPETS_UPDATE_INTERVAL_MS = 8640
 let gSearchEngine;
 
 function onLoad(event)
 {
   setupSearchEngine();
   document.getElementById("searchText").focus();
 
   loadSnippets();
+
+  fitToWidth();
+  window.addEventListener("resize", fitToWidth);
 }
 
 
 function onSearchSubmit(aEvent)
 {
   let searchTerms = document.getElementById("searchText").value;
   if (gSearchEngine && searchTerms.length > 0) {
     const SEARCH_TOKENS = {
@@ -205,23 +209,27 @@ function setupSearchEngine()
 
 function loadSnippets()
 {
   // Check last snippets update.
   let lastUpdate = localStorage["snippets-last-update"];
   let updateURL = localStorage["snippets-update-url"];
   if (updateURL && (!lastUpdate ||
                     Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS)) {
+    // Try to update from network.
+    let xhr = new XMLHttpRequest();
+    try {
+      xhr.open("GET", updateURL, true);
+    } catch (ex) {
+      showSnippets();
+      return;
+    }
     // Even if fetching should fail we don't want to spam the server, thus
     // set the last update time regardless its results.  Will retry tomorrow.
     localStorage["snippets-last-update"] = Date.now();
-
-    // Try to update from network.
-    let xhr = new XMLHttpRequest();
-    xhr.open('GET', updateURL, true);
     xhr.onerror = function (event) {
       showSnippets();
     };
     xhr.onload = function (event)
     {
       if (xhr.status == 200) {
         localStorage["snippets"] = xhr.responseText;
       }
@@ -255,41 +263,31 @@ function showSnippets()
       // Bad content, continue to show default snippets.
     }
   }
 
   // Show default snippets otherwise.
   let defaultSnippetsElt = document.getElementById("defaultSnippets");
   let entries = defaultSnippetsElt.querySelectorAll("span");
   // Choose a random snippet.  Assume there is always at least one.
-  let randIndex = Math.round(Math.random() * (entries.length - 1));
+  let randIndex = Math.floor(Math.random() * entries.length);
   let entry = entries[randIndex];
   // Inject url in the eventual link.
   if (DEFAULT_SNIPPETS_URLS[randIndex]) {
     let links = entry.getElementsByTagName("a");
     // Default snippets can have only one link, otherwise something is messed
     // up in the translation.
     if (links.length == 1) {
       links[0].href = DEFAULT_SNIPPETS_URLS[randIndex];
-      activateSnippetsButtonClick(entry);
     }
   }
   // Move the default snippet to the snippets element.
   snippetsElt.appendChild(entry);
 }
 
-/**
- * Searches a single link element in aElt and binds its href to the click
- * action of the snippets button.
- *
- * @param aElt
- *        Element to search the link into.
- */
-function activateSnippetsButtonClick(aElt) {
-  let links = aElt.getElementsByTagName("a");
-  if (links.length == 1) {
-    document.getElementById("snippets")
-            .addEventListener("click", function(aEvent) {
-      if (aEvent.target.nodeName != "a")
-        window.location = links[0].href;
-    }, false);
+function fitToWidth() {
+  if (window.scrollMaxX) {
+    document.body.setAttribute("narrow", "true");
+  } else if (document.body.hasAttribute("narrow")) {
+    document.body.removeAttribute("narrow");
+    fitToWidth();
   }
 }
rename from browser/base/content/aboutHome.xhtml
rename to browser/base/content/abouthome/aboutHome.xhtml
--- a/browser/base/content/aboutHome.xhtml
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -20,16 +20,17 @@
 # The Initial Developer of the Original Code is the Mozilla Foundation.
 # Portions created by the Initial Developer are Copyright (C) 2010
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Marco Bonardo <mak77@bonardo.net> (original author)
 #   Mihai Sucan <mihai.sucan@gmail.com>
 #   Stephen Horlander <shorlander@mozilla.com>
+#   Frank Yan <fyan@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -55,57 +56,53 @@
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>&abouthome.pageTitle;</title>
 
     <link rel="icon" type="image/png" id="favicon"
           href="chrome://branding/content/icon16.png"/>
     <link rel="stylesheet" type="text/css" media="all"
-          href="chrome://browser/content/aboutHome.css"/>
+          href="chrome://browser/content/abouthome/aboutHome.css"/>
 
     <script type="text/javascript;version=1.8"
-            src="chrome://browser/content/aboutHome.js"/>
+            src="chrome://browser/content/abouthome/aboutHome.js"/>
   </head>
 
   <body dir="&locale.dir;" onload="onLoad(event)">
-    <div id="brandStartSpacer" />
-    <div id="brandStart">
-      <img id="brandStartLogo" src="chrome://branding/content/about-logo.png" alt="" />
-    </div>
+    <div id="container">
+      <div class="spacer"/>
+      <div id="topSection">
+        <img id="brandLogo" src="chrome://branding/content/about-logo.png" alt=""/>
 
-    <div id="searchContainer">
-      <form name="searchForm" id="searchForm" onsubmit="onSearchSubmit(event)">
-        <div id="searchLogoContainer"><img id="searchEngineLogo" /></div>
-        <div id="searchInputContainer">
-          <input type="text" name="q" value="" id="searchText" maxlength="256" />
+        <div id="searchContainer">
+          <form name="searchForm" id="searchForm" onsubmit="onSearchSubmit(event)">
+            <div id="searchLogoContainer"><img id="searchEngineLogo"/></div>
+            <input type="text" name="q" value="" id="searchText" maxlength="256"/>
+            <input id="searchSubmit" type="submit" value="&abouthome.searchEngineButton.label;"/>
+          </form>
         </div>
-        <div id="searchButtons">
-          <input id="searchSubmit" type="submit" value="&abouthome.searchEngineButton.label;" />
-        </div>
-      </form>
-    </div>
 
-    <div id="contentContainer">
-      <div id="snippetContainer">
-        <div id="defaultSnippets" hidden="true">
-          <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span>
-          <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span>
+        <div id="snippetContainer">
+          <div id="defaultSnippets" hidden="true">
+            <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span>
+            <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span>
+          </div>
+          <div id="snippets"/>
         </div>
-        <div id="snippets"/>
+      </div>
+      <div class="spacer"/>
+
+      <div id="launcher" session="true">
+        <button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
+        <button class="launchButton" id="history">&abouthome.historyButton.label;</button>
+        <button class="launchButton" id="settings">&abouthome.settingsButton.label;</button>
+        <button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
+        <button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
+        <button class="launchButton" id="sync">&syncBrand.shortName.label;</button>
+        <div id="restorePreviousSessionSeparator"/>
+        <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
       </div>
 
-      <div id="sessionRestoreContainer">
-        <button id="restorePreviousSession">&historyRestoreLastSession.label;</button>
-      </div>
-    </div>
-
-    <div id="bottomSection">
-      <div id="aboutMozilla">
-        <a href="http://www.mozilla.com/about/">&abouthome.aboutMozilla;</a>
-      </div>
-      <div id="syncLinksContainer">
-        <a href="javascript:void(0);" class="sync-link" id="setupSyncLink">&abouthome.syncSetup.label;</a>
-        <a href="javascript:void(0);" class="sync-link" id="pairDeviceLink">&abouthome.pairDevice.label;</a>
-      </div>
+      <a id="aboutMozilla" href="http://www.mozilla.com/about/"/>
     </div>
   </body>
 </html>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..41519ce4982a0385b7668eaad8c20e1d06070b7b
GIT binary patch
literal 1444
zc$@*G1zY-wP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU%MoC0LR9M5+msw0yR~W}_oBG%{pPQyW
z7}KOSJT$T3N>M}<6&Us)%Z$jtFayIZch);|@66oUR~Q&TmX<|g!$VD46GHI8Hce|y
zO=;g~OoM1`o7Ae2G+)2(hM5i{57%TcDakMQaF%=i=R5!JoIAUA{U830me-vut!i1S
zXt?wUS;O+pqvf^7UdiOe=Niw{EBZif5rA>UM5tWTu7Trc8xHKafCfeHmqwh_KQs;D
z(W@d<?q`EgCszwQE}+NcTIM2S5E+}#Kq4&OLC~|l;EoGOUYRjlak9|JTt+}(cm_=F
z*ux)w{Q2OH3jlz({fY4<VPGn=1r(8)YoGhDZTBlNVmLWrb%sZ9@;MQkZxCF;!OFZ%
zP*hsA|4?al!J$*t1x2OR2eRt-l$>vBRP@;%(5h)02uCOdtt!)R*ruT|g(oZD*zCVI
z{T`%DvVe*@`B3%6Zm5^{LQPu_R5o|Pg{DriELU4-zrl-J5K7I8P?_p3_1S?^v<myv
z7+Gr%)M0wowyB|_UU6Hp0ENc<m7WWOJuoDO*Ww)zp)wa41@GV#8gCkcsTmO}`;wmk
z9~~3NShM=m|3*Fn8WVq0vVhAv_MR(tMSO8SAQ1*<U{<n#E<N{`keGx39x(a9A`F0;
z5BsDGFmV5n0Dtn+ykK(2L2vV#B@5^_@*Bdyq`0s7z~qTTx1J4U&w*J{dE|7B^Kh9=
zi&^u|76EFD=b<}3E}D(-Bqo47K?bBJiA%Rhfjl7BSk{Uw8p?1&S(&W$cCB0kE!{?F
z>eRv6dc{K&v_B)j!iR2Hgao+I)f^r;W>0)Wh>hMFo|v1*=QnnAw}%9h5DCzG^v)2p
zsTgSKGJuM4iD_&1C&i4xh{L(Yj;k2~zuf!vV^?Ts$?T4<dSfGtckcdV#AEg-@;-9h
zpBP(o1cyPu$Vh?2@)$izOEHykQB;?1qq>$Z{iE#j{!OG1IgfmVyp_F0k>rGqaYb?P
zL|)*+qjZRENH(c~^)>ag0a3kFXd5VMEc$_>;YKf|+njimtBl;*mPravT#%tLl7~vG
zn1dzfx45)LsSD~ILGdM05Pjs1J)fLg7@z&@`Vhm1|Ddqb#3YLR;fs%4$xLvhOeU{t
zR>DQ49vVBebdEnp_jaJDg5ziEn$f+_(k7JxO~!|kOSqN$kaySaKWO*GM!v8K@fC|Z
zx-vR7KaTb9Wdzi;cCYl}cb*j|LFJ$^dqgp{co~t4`|W<9OkmN)1cxi(ag{Dg(`_)|
z3i-eQpXIUL$cM=3P4;aGf2)ewpcJ9KqVQ3eXvK7?kooAz`Uh5oXlhxAjVzm7(e<>u
zX-qDCHXkq!U#Ilju-7W?54|uK%3C3lTHaJ%L_GfcNrT;+xSJM{3q?@Fa0=<Sq1OJ~
zfo`SH=3>Na+RO(NIiG2-b9gvlB(@zx0+d>-TDpLK+n;tnxpepijC>gV(0?bp|KYOg
zJ<UqpI^&9LJEqqvz~w%(Q~GVFV^_(5PTc!ksJ^|&1jov2W$FH>sxDqv>KtIh?b&XO
z7Cj*EF{WPPdw~7k<c^~GhF*XTBtdO<gRDil@ZXOW&F=;-B(`lgM&}6NryR2&{l|yF
zd4u+ve2|9HjaF#Et5t=Uhk^Ab)BW{!VUbozZRzyP9!G~(yUO^rWC1#>C)KHA#e65U
zb{{>TFkEQW=81kgK7B1k7gM_b)2Gi4rzYpdvF$s(Bd|_lGGno<wR!I=)Xc4y`SI}q
yBF~|D^KvRi_U80|ZS#~XL29-cTaBEMGWWm2aTm|u=>tjt0000<MNUMnLSTZ;x6I7|
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5c7e194a61c1e669b24c382603269a093989f517
GIT binary patch
literal 1276
zc$@+J1OxktP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F8000ETNkl<Zc-qC5
zYfPJE6vz8b6E9P;O`>KW{op4P6J!B81jcoY%55<2TFRwR3!|l_w9vQhTUvT=DYQVj
z?7|$&V$3o#B%4E+gn({16tkEZql+I*jBKfd#T?IhMS@y{7SIpB0MB{O|NPH$d*K1V
z|4P^i@#zJ;gp2}S;)$ZD)VzvCWFCJ%fMXcjmc21>EUSnoD6Ji8P#8ems)wAC8ZVCh
zHwLKF<8PH8sX5cRX7r2z>mCMCvt|{?qA|Ab2C(~6yuvC;pOSThWAGA(l666TrP#^G
zSbTc^zp-lr*nL^FsJUact7jBkLzlrB7zbJj91Erh$_`<d`)eStLhMu+Twot~kBW2H
z`Y%GO-U)dX_1*kTT!pm!E!d@j6hY~s>LzuNbr0dvlYvVF_dAUVF_w~362*0!mKTcM
z8OX&BTXhbw`p0PRH^Ys|F(xRj(<fvU^5U>q2qHI-oKu3`Yk~%60PMKr4%~vx&=|#p
z()NeRCr=-U--Q9XYOmwtQiekMwEW5|*~QgM<@It<b$P(zJCFN+F(eeD8&Os#1IoeN
zaneAo)1<lOA>?Nk#^C;rpC}S8=a$z)adi_^HYh-JrUN7@BghyVbXfYp+<O+6`T`!h
zv5-*A<Q=8Clta0sL0X(9X=fD)m#7xjd~L%3zG^?!$rzAnZJ;uGpwr?5ovoi+Qd7?e
zboZVEs}BqN$94qGrJOF$2x*WOX_7Wqqqap0f|8mC8wMI%nQv%<3GcS0?>q_Y3#3go
zH0EAtkTc6025!%NUZ*m;e=&N7p}X%K?d85e+Ek;Bb^d(!-d9zd58<<)%+4#@jP4b@
z&opsgKy5mtEAw~0Xa<0%{(cLhZcfi?RIDp#Bon=7_Y~F@0MvdoJ-g6G4R3V~4^DhM
zqi<tfYm{L2j_wJhg_^<1Ter2;=yvbG3s<k->~3S-Yk%B=(Op3rN`q_d!^tTFY9HR|
zOZejD53W1#U_Qp}psN@WluH`#Uz@a`X7sVl!mys=BbJ`P1Y`5#eIAYot<?wi-r=jL
z^)ereA59U2_bq5tFxS+k9_l0_)cABul)5WP*~MJ(*yNn>42Y!d_cdg|6^IDR6-ib1
z60=TG@$e1cxBpjMMmoh2LAlt(Lt241usE`F1Ne`{OSLVXptI9AVK5>nmo)HG9>p0*
zI=OQLr6Orsqe2gQGT;~p1;w_FlSV;BJwG;F165*~tV!JkdPo01U~E26n>_TU2iuLV
zRgJla#zM}Owj1H?26@BL;{skxQufXb2pbiHvQ7(iu>oWCaR*DTwXT`n10Q|=;79Sd
z-~AvlIs0$m*kg+6Z2rIGv~+X>YI);Qb9pguhGF0xiDF8jx8pwgx$ZP(Ds1`9VDFn=
zTE5=`0I|pu$dgFi;+J15bXM<-oUy|Cig3B~xA0x%L_&Igcm}Gaszrs){%6OaHG3BD
zE2hR6A9<PtX<zAn^D_!4=NTk%ZsBf6r^)?UD`Vqw>8DVloX0ug8DMQ*Z=*uDqUmzp
zxivFK?|2IG90^fT659-otv|KTBdIs0K4sgDjzzJYd1Q9^ZQ&XqUPa20H00p=7>R5J
mo$}2_4kPJE1@g*PXW)02(gT@f=I})T0000<MNUMnLSTZFLTTOr
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3d4d10e7abb472690b37dbd8cea26c79ec8205ea
GIT binary patch
literal 898
zc$@)(1AY97P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80009?Nkl<Zc-qyM
z%TE(g6vm116(5QEzqoKEaVH^>D2N6W!B8WlQh~mN&UE^qkLk2SdDHUr0irJ5xR4bD
zO$kUVg|wy>(^6WRX<4`clIuN9N-$1f3Ns6D@=KcD@1FCWd(XX1i;Di2alQ2ZwVM@n
zD|c!dQTZb|FE9rh(28!LyzW`JnRFn6?nji<!wbxT2DG9ZXiyuGW8e*PkGw}+qaS#I
zInaPsbOTUX=kU8+l7j#ZXhk>BN->8f&jVV~4YcT)Ty;VKtpo<(2&M!9T1k3<9hwq^
zBt6hf(1`WE6$EHSH}IUWBbRql5TF&^fI??OPS1oOKr6n1c5Gm9LJ*)8-9S^D6*2gg
z69Q-@Fkr#Y>}x@QR+1*9`(Fv7;3h0CsbotkYBRU0o}e-w9z0c|HcJ<x`(6qHw6KOR
z7UU?sTlc%T^xov2fvS3iU#>MHjfq8CbLT(kY(1#MJ&f$V<AMM!tl^6VITY<yR4r?c
z?irYwo0lmmCT(<ik+pja*?Pt$0b-ljA*9kXTc1A9wCov3rBWAt-~3G)GmA}luy7PF
z7!wtUX=1&22VB|NIiEJ0&0hJd55UCYQc$ijI#UMcAhL9g<Q9nm#Dc<9MkYJ^bwQcQ
zWG?eIV=|e%5LjMq)S6rg12d32OIUmyAO;kqHaIy*&T-tO18+nkkvJb*4a?M&BThN`
zQAg(xvVf5z0BfNTHN|lA3x36+$@3!?i=7LFqK~obHInW_W*i7ADFj$U;p8d<lZNDQ
z#09+#kx1lhFdVK`QOp`)@69z31Qh~k^?3J`lq0>k^j%(X^8F?vk)Jhc0~00iy*A-S
zGJ}x=0F55+no93TL2~G;V?Dv4Q0PqbSG-zlWPa#u-ME_vafKdUV2-4Fk&2=>pqY=I
zJU;?~!0FifdZpIj4C$=c1nz36bU*MNiH}ODrxX6=l?G^LQGbklKHsTiI$f?YFu^v9
z8(*z~T*Ux<hTTz;ws;^ImWiG`Kc=UrPi|~(-Ubsd@VgFY4Og|T!;M-AYaDNTJ=`Ek
z|D=Kmj@v4;yS*P<2|L$9*tc{R_DeXjRx5e({2)p4#P;^~)!p6QD$K*3ot+!dtVfCU
Y2Tou=l3c_Nga7~l07*qoM6N<$f;t<JX#fBK
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae742b1aa8acd6115533186c86f7ac37828681f7
GIT binary patch
literal 1654
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
AM*si-
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f2c348d13566a943593afc93934e5503661deaa9
GIT binary patch
literal 2684
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9ff785adae87b120fa0eb264982c3ea21810745d
GIT binary patch
literal 10972
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
bRqutB)jeuP#r^o7|4rqkm8HrhO}_pg>|_%u
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ef593e6e14c87b6774b39361f9ce31b1c908dbed
GIT binary patch
literal 2841
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5c3d6f4376c24bfe320ccac1e2b09f896c83a2a9
GIT binary patch
literal 1796
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4b0c30990933f748f41f8424ff81bd0622c05b18
GIT binary patch
literal 1557
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
Hu0mjf%%}sx
rename from browser/base/content/aboutHome-snippet1.png
rename to browser/base/content/abouthome/snippet1.png
rename from browser/base/content/aboutHome-snippet2.png
rename to browser/base/content/abouthome/snippet2.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..11e40cc93755ab65b0dcd4a51a58c87f6c096278
GIT binary patch
literal 1879
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
RBdh=b002ovPDHLkV1m-`bcp}}
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -170,31 +170,17 @@ let gSyncUI = {
   },
 
   onLoginFinish: function SUI_onLoginFinish() {
     // Clear out any login failure notifications
     let title = this._stringBundle.GetStringFromName("error.login.title");
     this.clearError(title);
   },
 
-  // Set visibility of "Setup Sync" link
-  showSetupSyncAboutHome: function SUI_showSetupSyncAboutHome(toShow) {
-    let browsers = gBrowser.browsers;
-    for (let i = 0; i < browsers.length; i++) {
-      let b = browsers[i];
-      if ("about:home" == b.currentURI.spec) {
-        b.contentDocument.getElementById("setupSyncLink").hidden = !toShow;
-      }
-    }
-  },
-
   onSetupComplete: function SUI_onSetupComplete() {
-    // Remove "setup sync" link in about:home if it is open. 
-    this.showSetupSyncAboutHome(false);
-
     onLoginFinish();
   },
 
   onLoginError: function SUI_onLoginError() {
     // if login fails, any other notifications are essentially moot
     Weave.Notifications.removeAll();
 
     // if we haven't set up the client, don't show errors
@@ -232,18 +218,16 @@ let gSyncUI = {
   },
 
   onLogout: function SUI_onLogout() {
     this.updateUI();
   },
 
   onStartOver: function SUI_onStartOver() {
     this.clearError();
-    // Make "setup sync" link visible in about:home if it is open. 
-    this.showSetupSyncAboutHome(true);
   },
 
   onQuotaNotice: function onQuotaNotice(subject, data) {
     let title = this._stringBundle.GetStringFromName("warning.sync.quota.label");
     let description = this._stringBundle.GetStringFromName("warning.sync.quota.description");
     let buttons = [];
     buttons.push(new Weave.NotificationButton(
       this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -51,16 +51,17 @@
 #   Dietrich Ayala <dietrich@mozilla.com>
 #   Gavin Sharp <gavin@gavinsharp.com>
 #   Justin Dolske <dolske@mozilla.com>
 #   Rob Campbell <rcampbell@mozilla.com>
 #   David Dahl <ddahl@mozilla.com>
 #   Patrick Walton <pcwalton@mozilla.com>
 #   Mihai Sucan <mihai.sucan@gmail.com>
 #   Victor Porof <vporof@mozilla.com>
+#   Frank Yan <fyan@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -2701,99 +2702,93 @@ function PageProxyClickHandler(aEvent)
  *  Handle load of some pages (about:*) so that we can make modifications
  *  to the DOM for unprivileged pages.
  */
 function BrowserOnAboutPageLoad(document) {
   if (/^about:home$/i.test(document.documentURI)) {
     let ss = Components.classes["@mozilla.org/browser/sessionstore;1"].
              getService(Components.interfaces.nsISessionStore);
     if (!ss.canRestoreLastSession)
-      document.getElementById("sessionRestoreContainer").hidden = true;
-    // Sync-related links
-    if (Services.prefs.prefHasUserValue("services.sync.username")) {
-      document.getElementById("setupSyncLink").hidden = true;
-    }
+      document.getElementById("launcher").removeAttribute("session");
   }
 }
 
 /**
  * Handle command events bubbling up from error page content
  */
 function BrowserOnClick(event) {
-    // Don't trust synthetic events
-    if (!event.isTrusted ||
-        (event.target.localName != "button" &&
-         event.target.className != "sync-link"))
+    if (!event.isTrusted || // Don't trust synthetic events
+        event.button == 2 || event.target.localName != "button")
       return;
 
     var ot = event.originalTarget;
-    var errorDoc = ot.ownerDocument;
+    var ownerDoc = ot.ownerDocument;
 
     // If the event came from an ssl error page, it is probably either the "Add
     // Exception…" or "Get me out of here!" button
-    if (/^about:certerror/.test(errorDoc.documentURI)) {
-      if (ot == errorDoc.getElementById('exceptionDialogButton')) {
+    if (/^about:certerror/.test(ownerDoc.documentURI)) {
+      if (ot == ownerDoc.getElementById('exceptionDialogButton')) {
         var params = { exceptionAdded : false, handlePrivateBrowsing : true };
 
         try {
           switch (gPrefService.getIntPref("browser.ssl_override_behavior")) {
             case 2 : // Pre-fetch & pre-populate
               params.prefetchCert = true;
             case 1 : // Pre-populate
-              params.location = errorDoc.location.href;
+              params.location = ownerDoc.location.href;
           }
         } catch (e) {
           Components.utils.reportError("Couldn't get ssl_override pref: " + e);
         }
 
         window.openDialog('chrome://pippki/content/exceptionDialog.xul',
                           '','chrome,centerscreen,modal', params);
 
         // If the user added the exception cert, attempt to reload the page
         if (params.exceptionAdded)
-          errorDoc.location.reload();
+          ownerDoc.location.reload();
       }
-      else if (ot == errorDoc.getElementById('getMeOutOfHereButton')) {
+      else if (ot == ownerDoc.getElementById('getMeOutOfHereButton')) {
         getMeOutOfHere();
       }
     }
-    else if (/^about:blocked/.test(errorDoc.documentURI)) {
+    else if (/^about:blocked/.test(ownerDoc.documentURI)) {
       // The event came from a button on a malware/phishing block page
       // First check whether it's malware or phishing, so that we can
       // use the right strings/links
-      var isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
-
-      if (ot == errorDoc.getElementById('getMeOutButton')) {
+      var isMalware = /e=malwareBlocked/.test(ownerDoc.documentURI);
+
+      if (ot == ownerDoc.getElementById('getMeOutButton')) {
         getMeOutOfHere();
       }
-      else if (ot == errorDoc.getElementById('reportButton')) {
+      else if (ot == ownerDoc.getElementById('reportButton')) {
         // This is the "Why is this site blocked" button.  For malware,
         // we can fetch a site-specific report, for phishing, we redirect
         // to the generic page describing phishing protection.
 
         if (isMalware) {
           // Get the stop badware "why is this blocked" report url,
           // append the current url, and go there.
           try {
             let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
-            reportURL += errorDoc.location.href;
+            reportURL += ownerDoc.location.href;
             content.location = reportURL;
           } catch (e) {
             Components.utils.reportError("Couldn't get malware report URL: " + e);
           }
         }
         else { // It's a phishing site, not malware
           try {
             content.location = formatURL("browser.safebrowsing.warning.infoURL", true);
           } catch (e) {
             Components.utils.reportError("Couldn't get phishing info URL: " + e);
           }
         }
       }
-      else if (ot == errorDoc.getElementById('ignoreWarningButton')) {
+      else if (ot == ownerDoc.getElementById('ignoreWarningButton')) {
         // Allow users to override and continue through to the site,
         // but add a notify bar as a reminder, so that they don't lose
         // track after, e.g., tab switching.
         gBrowser.loadURIWithFlags(content.location.href,
                                   nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                                   null, null, null);
 
         Services.perms.add(makeURI(content.location.href), "safe-browsing",
@@ -2838,33 +2833,41 @@ function BrowserOnClick(event) {
           title,
           value,
           "chrome://global/skin/icons/blacklist_favicon.png",
           notificationBox.PRIORITY_CRITICAL_HIGH,
           buttons
         );
       }
     }
-    else if (/^about:home$/i.test(errorDoc.documentURI)) {
-      if (ot == errorDoc.getElementById("restorePreviousSession")) {
+    else if (/^about:home$/i.test(ownerDoc.documentURI)) {
+      if (ot == ownerDoc.getElementById("restorePreviousSession")) {
         let ss = Cc["@mozilla.org/browser/sessionstore;1"].
                  getService(Ci.nsISessionStore);
         if (ss.canRestoreLastSession)
           ss.restoreLastSession();
-        errorDoc.getElementById("sessionRestoreContainer").hidden = true;
+        ownerDoc.getElementById("launcher").removeAttribute("session");
+      }
+      else if (ot == ownerDoc.getElementById("bookmarks")) {
+        PlacesCommandHook.showPlacesOrganizer("AllBookmarks");
+      }
+      else if (ot == ownerDoc.getElementById("history")) {
+        PlacesCommandHook.showPlacesOrganizer("History");
       }
-      else if (ot == errorDoc.getElementById("pairDeviceLink")) {
-        if (Services.prefs.prefHasUserValue("services.sync.username")) {
-          gSyncUI.openAddDevice();
-        } else {
-          gSyncUI.openSetup("pair");
-        }
+      else if (ot == ownerDoc.getElementById("settings")) {
+        openPreferences();
+      }
+      else if (ot == ownerDoc.getElementById("addons")) {
+        BrowserOpenAddonsMgr();
       }
-      else if (ot == errorDoc.getElementById("setupSyncLink")) {
-        gSyncUI.openSetup(null);
+      else if (ot == ownerDoc.getElementById("downloads")) {
+        BrowserDownloadsUI();
+      }
+      else if (ot == ownerDoc.getElementById("sync")) {
+        openPreferences("paneSync");
       }
     }
 }
 
 /**
  * Re-direct the browser to a known-safe page.  This function is
  * used when, for example, the user browses to a known malware page
  * and is presented with about:blocked.  The "Get me out of here!"
@@ -5115,16 +5118,17 @@ var TabsProgressListener = {
     // Attach a listener to watch for "click" events bubbling up from error
     // pages and other similar page. This lets us fix bugs like 401575 which
     // require error page UI to do privileged things, without letting error
     // pages have any privilege themselves.
     // We can't look for this during onLocationChange since at that point the
     // document URI is not yet the about:-uri of the error page.
 
     if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+        Components.isSuccessCode(aStatus) &&
         /^about:/.test(aWebProgress.DOMWindow.document.documentURI)) {
       aBrowser.addEventListener("click", BrowserOnClick, false);
       aBrowser.addEventListener("pagehide", function () {
         aBrowser.removeEventListener("click", BrowserOnClick, false);
         aBrowser.removeEventListener("pagehide", arguments.callee, true);
       }, true);
 
       // We also want to make changes to page UI for unprivileged about pages.
--- a/browser/base/content/test/browser_aboutHome.js
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -5,19 +5,16 @@
 registerCleanupFunction(function() {
   // Ensure we don't pollute prefs for next tests.
   try {
     Services.prefs.clearUserPref("network.cookies.cookieBehavior");
   } catch (ex) {}
   try {
     Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
   } catch (ex) {}
-  try {
-    Services.prefs.clearUserPref("services.sync.username");
-  } catch (ex) {}
 });
 
 let gTests = [
 
 {
   desc: "Check that rejecting cookies does not prevent page from working",
   setup: function ()
   {
@@ -107,131 +104,21 @@ let gTests = [
   run: function ()
   {
     let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
 
     let snippetsElt = doc.getElementById("snippets");
     ok(snippetsElt, "Found snippets element");
     is(snippetsElt.getElementsByTagName("span").length, 1,
        "A default snippet is visible.");
-
+    let storage = getStorage();
+    storage.removeItem("snippets");
     executeSoon(runNextTest);
   }
 },
-
-{
-  desc: "Check sync links visibility before and after Sync setup",
-  setup: function ()
-  {
-    try {
-      Services.prefs.clearUserPref("services.sync.username");
-    } catch (ex) {}
-    Services.obs.notifyObservers(null, "weave:service:ready", null);
-  },
-  run: function ()
-  {
-    let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
-    let pairLink = doc.getElementById("pairDeviceLink");
-    let setupLink = doc.getElementById("setupSyncLink");
-
-    ok(pairLink, "Found 'Pair Device' link");
-    ok(setupLink, "Found 'Set Up Sync' link");
-    ok(!pairLink.hidden, "'Pair' link is visible before setup");
-    ok(!setupLink.hidden, "'Set Up' link is visible before setup");
-
-    Services.obs.notifyObservers(null, "weave:service:setup-complete", null);
-
-    executeSoon(function () {
-      setupLink = doc.getElementById("setupSyncLink");
-      ok(setupLink.hidden, "'Set Up' link is hidden after setup");
-      ok(!pairLink.hidden, "'Pair' link is visible after setup");
-
-      executeSoon(runNextTest);
-    });
-  }
-},
-
-{
-  desc: "Check sync links visibility before and after Sync unlink",
-  setup: function ()
-  {
-    Services.prefs.setCharPref("services.sync.username", "someuser@domain.com");
-    Services.obs.notifyObservers(null, "weave:service:ready", null);
-  },
-  run: function ()
-  {
-    let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
-    let pairLink = doc.getElementById("pairDeviceLink");
-    let setupLink = doc.getElementById("setupSyncLink");
-
-    ok(!pairLink.hidden, "'Pair' link is visible before unlink");
-    ok(setupLink.hidden, "'Set Up' link is hidden before unlink");
-
-    Services.obs.notifyObservers(null, "weave:service:start-over", null);
-
-    executeSoon(function () {
-      setupLink = doc.getElementById("setupSyncLink");
-      ok(!setupLink.hidden, "'Set Up' link is visible after unlink");
-      ok(!pairLink.hidden, "'Pair' link is visible after unlink");
-      executeSoon(runNextTest);
-    });
-  }
-},
-
-{
-  desc: "Check Pair Device link opens correct dialog with Sync account ",
-  setup: function ()
-  {
-    Services.prefs.setCharPref("services.sync.username", "someuser@domain.com");
-    Services.obs.notifyObservers(null, "weave:service:ready", null);
-  },
-  run: function ()
-  {
-    expectDialogWindow("Sync:AddDevice");
-    let browser = gBrowser.selectedTab.linkedBrowser;
-    let button = browser.contentDocument.getElementById("pairDeviceLink");
-    EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
-  }
-},
-
-{
-  desc: "Check Pair Device link opens correct dialog without Sync account",
-  setup: function ()
-  {
-    try {
-      Services.prefs.clearUserPref("services.sync.username");
-    } catch (ex) {}
-    Services.obs.notifyObservers(null, "weave:service:ready", null);
-  },
-  run: function ()
-  {
-    expectDialogWindow("Weave:AccountSetup");
-    let browser = gBrowser.selectedTab.linkedBrowser;
-    let button = browser.contentDocument.getElementById("pairDeviceLink");
-    EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
-  }
-},
-
-{
-  desc: "Check Sync Setup link opens correct dialog (without Sync account)",
-  setup: function ()
-  {
-    try {
-      Services.prefs.clearUserPref("services.sync.username");
-    } catch (ex) {}
-    Services.obs.notifyObservers(null, "weave:service:ready", null);
-  },
-  run: function ()
-  {
-    expectDialogWindow("Weave:AccountSetup");
-    let browser = gBrowser.selectedTab.linkedBrowser;
-    let button = browser.contentDocument.getElementById("setupSyncLink");
-    EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
-  }
-},
 ];
 
 function test()
 {
   waitForExplicitFinish();
 
   // browser-chrome test harness inits browser specifying an hardcoded page
   // and this causes nsIBrowserHandler.defaultArgs to not be evaluated since
@@ -267,32 +154,16 @@ function runNextTest()
       executeSoon(test.run);
     }, true);
   }
   else {
     finish();
   }
 }
 
-function expectDialogWindow(expectedDialog) {
-  Services.ww.registerNotification(function onWindow(subject, topic) {
-    let win = subject.QueryInterface(Components.interfaces.nsIDOMWindow);
-    win.addEventListener("load", function onLoad() {
-      win.removeEventListener("load", onLoad, false);
-      let wintype = win.document.documentElement.getAttribute("windowtype");
-      if (topic == "domwindowopened" && wintype == expectedDialog) {
-        Services.ww.unregisterNotification(onWindow);
-        // Clean up dialog.
-        win.close();
-        executeSoon(runNextTest);
-      }
-    }, false);
-  });
-}
-
 function getStorage()
 {
   let aboutHomeURI = Services.io.newURI("moz-safe-about:home", null, null);
   let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"].
                   getService(Components.interfaces.nsIScriptSecurityManager).
                   getCodebasePrincipal(Services.io.newURI("about:home", null, null));
   let dsm = Components.classes["@mozilla.org/dom/storagemanager;1"].
             getService(Components.interfaces.nsIDOMStorageManager);
--- a/browser/base/content/test/newtab/browser_newtab_block.js
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -20,41 +20,41 @@ function runTests() {
 
   yield blockCell(cells[4]);
   checkGrid("0,1,2,3,6,7,8,9,");
 
   yield blockCell(cells[4]);
   checkGrid("0,1,2,3,7,8,9,,");
 
   // we removed a pinned site
-  reset();
+  yield restore();
   setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",1");
 
   yield addNewTabPageTab();
   checkGrid("0,1p,2,3,4,5,6,7,8");
 
   yield blockCell(cells[1]);
   checkGrid("0,2,3,4,5,6,7,8,");
 
   // we remove the last site on the grid (which is pinned) and expect the gap
   // to be re-filled and the new site to be unpinned
-  reset();
+  yield restore();
   setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks(",,,,,,,,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8p");
 
   yield blockCell(cells[8]);
   checkGrid("0,1,2,3,4,5,6,7,9");
 
   // we remove the first site on the grid with the last one pinned. all cells
   // but the last one should shift to the left and a new site fades in
-  reset();
+  yield restore();
   setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks(",,,,,,,,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8p");
 
   yield blockCell(cells[0]);
   checkGrid("1,2,3,4,5,6,7,9,8p");
--- a/browser/base/content/test/newtab/browser_newtab_bug734043.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug734043.js
@@ -1,30 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
-  // TODO Bug 735166 - Intermittent timeout in browser_newtab_bug734043.js
-  return;
-
   setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
+  checkGrid("0,1,2,3,4,5,6,7,8");
 
   let receivedError = false;
   let block = cw.document.querySelector(".newtab-control-block");
 
   function onError() {
     receivedError = true;
   }
 
   cw.addEventListener("error", onError);
 
-  for (let i = 0; i < 3; i++) {
+  for (let i = 0; i < 3; i++)
     EventUtils.synthesizeMouseAtCenter(block, {}, cw);
-    yield executeSoon(TestRunner.next);
-  }
 
   yield whenPagesUpdated();
   ok(!receivedError, "we got here without any errors");
   cw.removeEventListener("error", onError);
 }
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -5,18 +5,16 @@ const PREF_NEWTAB_ENABLED = "browser.new
 
 Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
 
 let tmp = {};
 Cu.import("resource:///modules/NewTabUtils.jsm", tmp);
 let NewTabUtils = tmp.NewTabUtils;
 
 registerCleanupFunction(function () {
-  reset();
-
   while (gBrowser.tabs.length > 1)
     gBrowser.removeTab(gBrowser.tabs[1]);
 
   Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
 });
 
 /**
  * Global variables that are accessed by tests.
@@ -52,18 +50,39 @@ let TestRunner = {
 
   /**
    * Runs the next available test or finishes if there's no test left.
    */
   next: function () {
     try {
       TestRunner._iter.next();
     } catch (e if e instanceof StopIteration) {
-      finish();
+      TestRunner.finish();
     }
+  },
+
+  /**
+   * Finishes all tests and cleans up.
+   */
+  finish: function () {
+    function cleanupAndFinish() {
+      // Restore the old provider.
+      NewTabUtils.links._provider = originalProvider;
+
+      whenPagesUpdated(finish);
+      NewTabUtils.restore();
+    }
+
+    let callbacks = NewTabUtils.links._populateCallbacks;
+    let numCallbacks = callbacks.length;
+
+    if (numCallbacks)
+      callbacks.splice(0, numCallbacks, cleanupAndFinish);
+    else
+      cleanupAndFinish();
   }
 };
 
 /**
  * Allows to provide a list of links that is used to construct the grid.
  * @param aLinksPattern the pattern (see below)
  *
  * Example: setLinks("1,2,3")
@@ -101,23 +120,21 @@ function setPinnedLinks(aLinksPattern) {
     pinnedLinks[index] = link;
   });
 
   // Inject the list of pinned links to work with.
   NewTabUtils.pinnedLinks._links = pinnedLinks;
 }
 
 /**
- * Resets the lists of blocked and pinned links and clears the storage.
+ * Restore the grid state.
  */
-function reset() {
-  NewTabUtils.reset();
-
-  // Restore the old provider to prevent memory leaks.
-  NewTabUtils.links._provider = originalProvider;
+function restore() {
+  whenPagesUpdated();
+  NewTabUtils.restore();
 }
 
 /**
  * Creates a new tab containing 'about:newtab'.
  */
 function addNewTabPageTab() {
   let tab = gBrowser.selectedTab = gBrowser.addTab("about:newtab");
   let browser = tab.linkedBrowser;
@@ -264,21 +281,21 @@ function simulateDrop(aDropTarget, aDrag
 
   if (aDragSource)
     cw.gDrag.end(aDragSource.site);
 }
 
 /**
  * Resumes testing when all pages have been updated.
  */
-function whenPagesUpdated() {
+function whenPagesUpdated(aCallback) {
   let page = {
     update: function () {
       NewTabUtils.allPages.unregister(this);
-      executeSoon(TestRunner.next);
+      executeSoon(aCallback || TestRunner.next);
     }
   };
 
   NewTabUtils.allPages.register(page);
   registerCleanupFunction(function () {
     NewTabUtils.allPages.unregister(page);
   });
 }
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -11,23 +11,31 @@ browser.jar:
 %  overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul
 %  overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul
 %  style chrome://global/content/customizeToolbar.xul chrome://browser/content/browser.css
 %  style chrome://global/content/customizeToolbar.xul chrome://browser/skin/
 *       content/browser/aboutDialog.xul               (content/aboutDialog.xul)
 *       content/browser/aboutDialog.js                (content/aboutDialog.js)
 *       content/browser/aboutDialog.css               (content/aboutDialog.css)
 *       content/browser/aboutRobots.xhtml             (content/aboutRobots.xhtml)
-*       content/browser/aboutHome.xhtml               (content/aboutHome.xhtml)
-*       content/browser/aboutHome.js                  (content/aboutHome.js)
-*       content/browser/aboutHome.css                 (content/aboutHome.css)
-        content/browser/aboutHome-restore-icon.png    (content/aboutHome-restore-icon.png)
-        content/browser/aboutHome-restore-icon-small.png    (content/aboutHome-restore-icon-small.png)
-        content/browser/aboutHome-snippet1.png        (content/aboutHome-snippet1.png)
-        content/browser/aboutHome-snippet2.png        (content/aboutHome-snippet2.png)
+*       content/browser/abouthome/aboutHome.xhtml     (content/abouthome/aboutHome.xhtml)
+*       content/browser/abouthome/aboutHome.js        (content/abouthome/aboutHome.js)
+*       content/browser/abouthome/aboutHome.css       (content/abouthome/aboutHome.css)
+        content/browser/abouthome/snippet1.png        (content/abouthome/snippet1.png)
+        content/browser/abouthome/snippet2.png        (content/abouthome/snippet2.png)
+        content/browser/abouthome/bookmarks.png       (content/abouthome/bookmarks.png)
+        content/browser/abouthome/history.png         (content/abouthome/history.png)
+        content/browser/abouthome/settings.png        (content/abouthome/settings.png)
+        content/browser/abouthome/addons.png          (content/abouthome/addons.png)
+        content/browser/abouthome/downloads.png       (content/abouthome/downloads.png)
+        content/browser/abouthome/sync.png            (content/abouthome/sync.png)
+        content/browser/abouthome/restore.png         (content/abouthome/restore.png)
+        content/browser/abouthome/restore-large.png   (content/abouthome/restore-large.png)
+        content/browser/abouthome/mozilla.png         (content/abouthome/mozilla.png)
+        content/browser/abouthome/noise.png           (content/abouthome/noise.png)
         content/browser/aboutRobots-icon.png          (content/aboutRobots-icon.png)
         content/browser/aboutRobots-widget-left.png   (content/aboutRobots-widget-left.png)
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 *       content/browser/content.js                    (content/content.js)
 *       content/browser/newtab/newTab.xul             (content/newtab/newTab.xul)
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -97,17 +97,17 @@ static RedirEntry kRedirMap[] = {
   { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
 #ifdef MOZ_SERVICES_SYNC
   { "sync-progress", "chrome://browser/content/sync/progress.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
   { "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
     nsIAboutModule::ALLOW_SCRIPT },
 #endif
-  { "home", "chrome://browser/content/aboutHome.xhtml",
+  { "home", "chrome://browser/content/abouthome/aboutHome.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT },
   { "newtab", "chrome://browser/content/newtab/newTab.xul",
     nsIAboutModule::ALLOW_SCRIPT },
   { "permissions", "chrome://browser/content/preferences/aboutPermissions.xul",
     nsIAboutModule::ALLOW_SCRIPT },
 };
 static const int kRedirTotal = NS_ARRAY_LENGTH(kRedirMap);
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -882,17 +882,17 @@ let AboutHomeUtils = {
       name: defaultEngine.name
     , searchUrl: submission.uri.spec
     }
     this._storage.setItem("search-engine", JSON.stringify(engine));
   },
 
   loadSnippetsURL: function AHU_loadSnippetsURL()
   {
-    const STARTPAGE_VERSION = 1;
+    const STARTPAGE_VERSION = 2;
     let updateURL = Services.prefs
                             .getCharPref(this.SNIPPETS_URL_PREF)
                             .replace("%STARTPAGE_VERSION%", STARTPAGE_VERSION);
     updateURL = Services.urlFormatter.formatURL(updateURL);
     this._storage.setItem("snippets-update-url", updateURL);
   },
 };
 
--- a/browser/components/sessionstore/test/browser_624727.js
+++ b/browser/components/sessionstore/test/browser_624727.js
@@ -1,12 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
+  waitForExplicitFinish();
+
   let assertNumberOfTabs = function (num, msg) {
     is(gBrowser.tabs.length, num, msg);
   }
 
   let assertNumberOfPinnedTabs = function (num, msg) {
     is(gBrowser._numPinnedTabs, num, msg);
   }
 
@@ -20,16 +22,18 @@ function test() {
 
   let [tab1, tab2] = gBrowser.tabs;
   let linkedBrowser = tab1.linkedBrowser;
   gBrowser.pinTab(tab1);
   gBrowser.pinTab(tab2);
   assertNumberOfPinnedTabs(2, "both tabs are now pinned");
 
   // run the test
-  ss.setBrowserState(JSON.stringify({ windows: [{ tabs: [{ url: "about:blank" }] }] }));
-  assertNumberOfTabs(1, "one tab left after setBrowserState()");
-  assertNumberOfPinnedTabs(0, "there are no pinned tabs");
-  is(gBrowser.tabs[0].linkedBrowser, linkedBrowser, "first tab's browser got re-used");
-
-  waitForExplicitFinish();
-  waitForSaveState(finish);
+  waitForBrowserState(
+    { windows: [{ tabs: [{ url: "about:blank" }] }] },
+    function () {
+      assertNumberOfTabs(1, "one tab left after setBrowserState()");
+      assertNumberOfPinnedTabs(0, "there are no pinned tabs");
+      is(gBrowser.tabs[0].linkedBrowser, linkedBrowser, "first tab's browser got re-used");
+      finish();
+    }
+  );
 }
--- a/browser/components/sessionstore/test/browser_625016.js
+++ b/browser/components/sessionstore/test/browser_625016.js
@@ -9,20 +9,24 @@ function test() {
 
   // We'll test this by opening a new window, waiting for the save event, then
   // closing that window. We'll observe the "sessionstore-state-write" notification
   // and check that the state contains no _closedWindows. We'll then add a new
   // tab and make sure that the state following that was reset and the closed
   // window is now in _closedWindows.
 
   waitForExplicitFinish();
+  requestLongerTimeout(2);
 
   // We speed up the interval between session saves to ensure that the test
   // runs quickly.
-  Services.prefs.setIntPref("browser.sessionstore.interval", 2000);
+  Services.prefs.setIntPref("browser.sessionstore.interval", 4000);
+  registerCleanupFunction(function () {
+    Services.prefs.clearUserPref("browser.sessionstore.interval");
+  });
 
   // We'll clear all closed windows to make sure our state is clean
   // forgetClosedWindow doesn't trigger a delayed save
   while (ss.getClosedWindowCount()) {
     ss.forgetClosedWindow(0);
   }
   is(ss.getClosedWindowCount(), 0, "starting with no closed windows");
 
@@ -92,17 +96,16 @@ function observe2(aSubject, aTopic, aDat
 // We'll open a tab, which should trigger another state save which would wipe
 // the _shouldRestore attribute from the closed window
 function openTab() {
   Services.obs.addObserver(observe2, "sessionstore-state-write", false);
   newTab = gBrowser.addTab("about:mozilla");
 }
 
 function done() {
-  Services.prefs.clearUserPref("browser.sessionstore.interval");
   gBrowser.removeTab(newTab);
   // The API still represents the closed window as closed, so we can clear it
   // with the API, but just to make sure...
   is(ss.getClosedWindowCount(), 1, "1 closed window according to API");
   ss.forgetClosedWindow(0);
   executeSoon(finish);
 }
 
--- a/browser/components/sessionstore/test/browser_705597.js
+++ b/browser/components/sessionstore/test/browser_705597.js
@@ -2,56 +2,63 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let tabState = {
   entries: [{url: "about:home", children: [{url: "about:mozilla"}]}]
 };
 
 function test() {
   waitForExplicitFinish();
+  requestLongerTimeout(2);
+
+  Services.prefs.setIntPref("browser.sessionstore.interval", 4000);
+  registerCleanupFunction(function () {
+    Services.prefs.clearUserPref("browser.sessionstore.interval");
+  });
 
   let tab = gBrowser.addTab("about:blank");
-  registerCleanupFunction(function () gBrowser.removeTab(tab));
 
   let browser = tab.linkedBrowser;
 
   whenBrowserLoaded(browser, function () {
     ss.setTabState(tab, JSON.stringify(tabState));
 
     let sessionHistory = browser.sessionHistory;
     let entry = sessionHistory.getEntryAtIndex(0, false);
 
     whenChildCount(entry, 1, function () {
       whenChildCount(entry, 2, function () {
         whenBrowserLoaded(browser, function () {
           let {entries} = JSON.parse(ss.getTabState(tab));
           is(entries.length, 1, "tab has one history entry");
           ok(!entries[0].children, "history entry has no subframes");
 
-          finish();
+          // Make sure that we reset the state.
+          let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
+          waitForBrowserState(blankState, finish);
         });
 
         // reload the browser to deprecate the subframes
         browser.reload();
       });
 
       // create a dynamic subframe
       let doc = browser.contentDocument;
       let iframe = doc.createElement("iframe");
+      doc.body.appendChild(iframe);
       iframe.setAttribute("src", "about:mozilla");
-      doc.body.appendChild(iframe);
     });
   });
 }
 
 function whenBrowserLoaded(aBrowser, aCallback) {
   aBrowser.addEventListener("load", function onLoad() {
     aBrowser.removeEventListener("load", onLoad, true);
     executeSoon(aCallback);
   }, true);
 }
 
 function whenChildCount(aEntry, aChildCount, aCallback) {
   if (aEntry.childCount == aChildCount)
     aCallback();
   else
-    executeSoon(function () whenChildCount(aEntry, aChildCount, aCallback));
+    setTimeout(function () whenChildCount(aEntry, aChildCount, aCallback), 100);
 }
--- a/browser/components/sessionstore/test/browser_707862.js
+++ b/browser/components/sessionstore/test/browser_707862.js
@@ -2,55 +2,64 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let tabState = {
   entries: [{url: "about:home", children: [{url: "about:mozilla"}]}]
 };
 
 function test() {
   waitForExplicitFinish();
+  requestLongerTimeout(2);
+
+  Services.prefs.setIntPref("browser.sessionstore.interval", 4000);
+  registerCleanupFunction(function () {
+    Services.prefs.clearUserPref("browser.sessionstore.interval");
+  });
 
   let tab = gBrowser.addTab("about:blank");
-  registerCleanupFunction(function () gBrowser.removeTab(tab));
 
   let browser = tab.linkedBrowser;
 
   whenBrowserLoaded(browser, function () {
     ss.setTabState(tab, JSON.stringify(tabState));
 
     let sessionHistory = browser.sessionHistory;
     let entry = sessionHistory.getEntryAtIndex(0, false);
 
     whenChildCount(entry, 1, function () {
       whenChildCount(entry, 2, function () {
         whenBrowserLoaded(browser, function () {
           let sessionHistory = browser.sessionHistory;
           let entry = sessionHistory.getEntryAtIndex(0, false);
 
-          whenChildCount(entry, 0, finish);
+          whenChildCount(entry, 0, function () {
+            // Make sure that we reset the state.
+            let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
+            waitForBrowserState(blankState, finish);
+          });
         });
 
         // reload the browser to deprecate the subframes
         browser.reload();
       });
 
       // create a dynamic subframe
       let doc = browser.contentDocument;
       let iframe = doc.createElement("iframe");
+      doc.body.appendChild(iframe);
       iframe.setAttribute("src", "about:mozilla");
-      doc.body.appendChild(iframe);
     });
   });
 }
 
 function whenBrowserLoaded(aBrowser, aCallback) {
   aBrowser.addEventListener("load", function onLoad() {
     aBrowser.removeEventListener("load", onLoad, true);
     executeSoon(aCallback);
   }, true);
 }
 
 function whenChildCount(aEntry, aChildCount, aCallback) {
   if (aEntry.childCount == aChildCount)
     aCallback();
   else
-    executeSoon(function () whenChildCount(aEntry, aChildCount, aCallback));
+    setTimeout(function () whenChildCount(aEntry, aChildCount, aCallback), 100);
 }
--- a/browser/components/tabview/content.js
+++ b/browser/components/tabview/content.js
@@ -89,21 +89,31 @@ let WindowMessageHandler = {
   // ----------
   // Function: isDocumentLoaded
   // Checks if the currently active document is loaded.
   isDocumentLoaded: function WMH_isDocumentLoaded(cx) {
     let isLoaded = (content.document.readyState == "complete" &&
                     !webProgress.isLoadingDocument);
 
     sendAsyncMessage(cx.name, {isLoaded: isLoaded});
+  },
+
+  // ----------
+  // Function: isImageDocument
+  // Checks if the currently active document is an image document or not.
+  isImageDocument: function WMH_isImageDocument(cx) {
+    let isImageDocument = (content.document instanceof Ci.nsIImageDocument);
+
+    sendAsyncMessage(cx.name, {isImageDocument: isImageDocument});
   }
 };
 
 // add message listeners
 addMessageListener("Panorama:isDocumentLoaded", WindowMessageHandler.isDocumentLoaded);
+addMessageListener("Panorama:isImageDocument", WindowMessageHandler.isImageDocument);
 
 // ----------
 // WebProgressListener
 //
 // Observe the web progress of content pages loaded into this browser. When the
 // state of a page changes we check if we're still allowed to store page
 // information permanently.
 let WebProgressListener = {
--- a/browser/components/tabview/groupitems.js
+++ b/browser/components/tabview/groupitems.js
@@ -229,20 +229,22 @@ function GroupItem(listOfEls, options) {
   // ___ app tabs: create app tab tray and populate it
   let appTabTrayContainer = iQ("<div/>")
     .addClass("appTabTrayContainer")
     .appendTo($container);
   this.$appTabTray = iQ("<div/>")
     .addClass("appTabTray")
     .appendTo(appTabTrayContainer);
 
-  AllTabs.tabs.forEach(function(xulTab) {
+  let pinnedTabCount = gBrowser._numPinnedTabs;
+  AllTabs.tabs.forEach(function (xulTab, index) {
+    // only adjust tray when it's the last app tab.
     if (xulTab.pinned)
-      self.addAppTab(xulTab, {dontAdjustTray: true});
-  });
+      this.addAppTab(xulTab, {dontAdjustTray: index + 1 < pinnedTabCount});
+  }, this);
 
   // ___ Undo Close
   this.$undoContainer = null;
   this._undoButtonTimeoutId = null;
 
   // ___ Superclass initialization
   this._init($container[0]);
 
@@ -753,17 +755,17 @@ GroupItem.prototype = Utils.extend(new I
     let self = this;
 
     let finalize = function () {
       self._children.forEach(function(child) {
         iQ(child.container).show();
       });
 
       UI.setActive(self);
-      self._sendToSubscribers("groupShown", { groupItemId: self.id });
+      self._sendToSubscribers("groupShown");
     };
 
     let $container = iQ(this.container).show();
 
     if (!options || !options.immediately) {
       $container.animate({
         "-moz-transform": "scale(1)",
         "opacity": 1
@@ -922,17 +924,17 @@ GroupItem.prototype = Utils.extend(new I
     setTimeout(function() {
       self.$undoContainer.animate({
         "-moz-transform": "scale(1)",
         "opacity": 1
       }, {
         easing: "tabviewBounce",
         duration: 170,
         complete: function() {
-          self._sendToSubscribers("groupHidden", { groupItemId: self.id });
+          self._sendToSubscribers("groupHidden");
         }
       });
     }, 50);
 
     // add click handlers
     this.$undoContainer.click(function(e) {
       // don't do anything if the close button is clicked.
       if (e.target == undoClose[0])
@@ -1047,17 +1049,17 @@ GroupItem.prototype = Utils.extend(new I
             (!GroupItems.getActiveGroupItem() && !item.tab.hidden))
           UI.setActive(this);
       }
 
       if (!options.dontArrange)
         this.arrange({animate: !options.immediately});
 
       this._unfreezeItemSize({dontArrange: true});
-      this._sendToSubscribers("childAdded",{ groupItemId: this.id, item: item });
+      this._sendToSubscribers("childAdded", { item: item });
 
       UI.setReorderTabsOnHide(this);
     } catch(e) {
       Utils.log('GroupItem.add error', e);
     }
   },
 
   // ----------
@@ -1150,17 +1152,17 @@ GroupItem.prototype = Utils.extend(new I
           (this._children.length == 0 && !gBrowser._numPinnedTabs &&
            !item.isDragging)) {
         this._makeLastActiveGroupItemActive();
       } else if (!options.dontArrange) {
         this.arrange({animate: !options.immediately});
         this._unfreezeItemSize({dontArrange: true});
       }
 
-      this._sendToSubscribers("childRemoved",{ groupItemId: this.id, item: item });
+      this._sendToSubscribers("childRemoved", { item: item });
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
   // Function: removeAll
   // Removes all of the groupItem's children.
@@ -1176,46 +1178,56 @@ GroupItem.prototype = Utils.extend(new I
       self.remove(child, newOptions);
     });
   },
 
   // ----------
   // Adds the given xul:tab as an app tab in this group's apptab tray
   //
   // Parameters:
+  //   xulTab - the xul:tab.
   //   options - change how the app tab is added.
   //
   // Options:
-  //   dontAdjustTray - (boolean) if true, the $appTabTray size is not adjusted,
-  //                    which means that the adjustAppTabTray() method is not
-  //                    called.
+  //   position - the position of the app tab should be added to.
+  //   dontAdjustTray - (boolean) if true, do not adjust the tray.
   addAppTab: function GroupItem_addAppTab(xulTab, options) {
-    let self = this;
+    GroupItems.getAppTabFavIconUrl(xulTab, function(iconUrl) {
+      let self = this;
+      let $appTab = iQ("<img>")
+        .addClass("appTabIcon")
+        .attr("src", iconUrl)
+        .data("xulTab", xulTab)
+        .mousedown(function GroupItem_addAppTab_onAppTabMousedown(event) {
+          // stop mousedown propagation to disable group dragging on app tabs
+          event.stopPropagation();
+        })
+        .click(function GroupItem_addAppTab_onAppTabClick(event) {
+          if (!Utils.isLeftClick(event))
+            return;
 
-    let iconUrl = GroupItems.getAppTabFavIconUrl(xulTab);
-    let $appTab = iQ("<img>")
-      .addClass("appTabIcon")
-      .attr("src", iconUrl)
-      .data("xulTab", xulTab)
-      .appendTo(this.$appTabTray)
-      .mousedown(function onAppTabMousedown(event) {
-        // stop mousedown propagation to disable group dragging on app tabs
-        event.stopPropagation();
-      })
-      .click(function(event) {
-        if (!Utils.isLeftClick(event))
-          return;
+          UI.setActive(self, { dontSetActiveTabInGroup: true });
+          UI.goToTab(iQ(this).data("xulTab"));
+        });
+
+      if (options && "position" in options) {
+        let children = this.$appTabTray[0].childNodes;
 
-        UI.setActive(self, { dontSetActiveTabInGroup: true });
-        UI.goToTab(iQ(this).data("xulTab"));
-      });
+        if (options.position >= children.length)
+          $appTab.appendTo(this.$appTabTray);
+        else
+          this.$appTabTray[0].insertBefore($appTab[0], children[options.position]);
+      } else {
+        $appTab.appendTo(this.$appTabTray);
+      }
+      if (!options || !options.dontAdjustTray)
+        this.adjustAppTabTray(true);
 
-    // adjust the tray, if needed.
-    if (!options || !options.dontAdjustTray)
-      this.adjustAppTabTray(true);
+      this._sendToSubscribers("appTabIconAdded", { item: $appTab });
+    }.bind(this));
   },
 
   // ----------
   // Removes the given xul:tab as an app tab in this group's apptab tray
   removeAppTab: function GroupItem_removeAppTab(xulTab) {
     // remove the icon
     iQ(".appTabIcon", this.$appTabTray).each(function(icon) {
       let $icon = iQ(icon);
@@ -2073,42 +2085,32 @@ let GroupItems = {
 
   // ----------
   // Function: _updateAppTabIcons
   // Update images of any apptab icons that point to passed in xultab 
   _updateAppTabIcons: function GroupItems__updateAppTabIcons(xulTab) {
     if (!xulTab.pinned)
       return;
 
-    let iconUrl = this.getAppTabFavIconUrl(xulTab);
-    this.groupItems.forEach(function(groupItem) {
-      iQ(".appTabIcon", groupItem.$appTabTray).each(function(icon) {
-        let $icon = iQ(icon);
-        if ($icon.data("xulTab") != xulTab)
-          return true;
-
-        if (iconUrl != $icon.attr("src"))
-          $icon.attr("src", iconUrl);
-        return false;
+    this.getAppTabFavIconUrl(xulTab, function(iconUrl) {
+      iQ(".appTabIcon").each(function GroupItems__updateAppTabIcons_forEach(icon) {
+         let $icon = iQ(icon);
+         if ($icon.data("xulTab") == xulTab && iconUrl != $icon.attr("src"))
+           $icon.attr("src", iconUrl);
       });
     });
   },
 
   // ----------
   // Function: getAppTabFavIconUrl
   // Gets the fav icon url for app tab.
-  getAppTabFavIconUrl: function GroupItems_getAppTabFavIconUrl(xulTab) {
-    let iconUrl;
-
-    if (UI.shouldLoadFavIcon(xulTab.linkedBrowser))
-      iconUrl = UI.getFavIconUrlForTab(xulTab);
-    else
-      iconUrl = gFavIconService.defaultFavicon.spec;
-
-    return iconUrl;
+  getAppTabFavIconUrl: function GroupItems_getAppTabFavIconUrl(xulTab, callback) {
+    UI.getFavIconUrlForTab(xulTab, function GroupItems_getAppTabFavIconUrl_getFavIconUrlForTab(iconUrl) {
+      callback(iconUrl || gFavIconService.defaultFavicon.spec);
+    });
   },
 
   // ----------
   // Function: addAppTab
   // Adds the given xul:tab to the app tab tray in all groups
   addAppTab: function GroupItems_addAppTab(xulTab) {
     this.groupItems.forEach(function(groupItem) {
       groupItem.addAppTab(xulTab);
--- a/browser/components/tabview/tabitems.js
+++ b/browser/components/tabview/tabitems.js
@@ -980,30 +980,31 @@ let TabItems = {
 
       Utils.assertThrow(tab, "tab");
 
       // ___ get the TabItem
       Utils.assertThrow(tab._tabViewTabItem, "must already be linked");
       let tabItem = tab._tabViewTabItem;
 
       // Even if the page hasn't loaded, display the favicon and title
-
       // ___ icon
-      if (UI.shouldLoadFavIcon(tab.linkedBrowser)) {
-        let iconUrl = UI.getFavIconUrlForTab(tab);
-
-        if (tabItem.$favImage[0].src != iconUrl)
-          tabItem.$favImage[0].src = iconUrl;
-
-        iQ(tabItem.$fav[0]).show();
-      } else {
-        if (tabItem.$favImage[0].hasAttribute("src"))
-          tabItem.$favImage[0].removeAttribute("src");
-        iQ(tabItem.$fav[0]).hide();
-      }
+      UI.getFavIconUrlForTab(tab, function TabItems__update_getFavIconUrlCallback(iconUrl) {
+        let favImage = tabItem.$favImage[0];
+        let fav = tabItem.$fav;
+        if (iconUrl) {
+          if (favImage.src != iconUrl)
+            favImage.src = iconUrl;
+          fav.show();
+        } else {
+          if (favImage.hasAttribute("src"))
+            favImage.removeAttribute("src");
+          fav.hide();
+        }
+        tabItem._sendToSubscribers("iconUpdated");
+      });
 
       // ___ label
       let label = tab.label;
       let $name = tabItem.$tabTitle;
       if ($name.text() != label)
         $name.text(label);
 
       // ___ remove from waiting list now that we have no other
--- a/browser/components/tabview/test/Makefile.in
+++ b/browser/components/tabview/test/Makefile.in
@@ -153,16 +153,17 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_bug656913.js \
                  browser_tabview_bug662266.js \
                  browser_tabview_bug663421.js \
                  browser_tabview_bug665502.js \
                  browser_tabview_bug669694.js \
                  browser_tabview_bug673196.js \
                  browser_tabview_bug673729.js \
                  browser_tabview_bug677310.js \
+                 browser_tabview_bug678374.js \
                  browser_tabview_bug679853.js \
                  browser_tabview_bug681599.js \
                  browser_tabview_bug685476.js \
                  browser_tabview_bug685692.js \
                  browser_tabview_bug686654.js \
                  browser_tabview_bug696602.js \
                  browser_tabview_bug697390.js \
                  browser_tabview_bug705621.js \
@@ -187,14 +188,16 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_thumbnail_storage.js \
                  browser_tabview_undo_group.js \
                  dummy_page.html \
                  head.js \
                  search1.html \
                  search2.html \
                  test_bug600645.html \
                  test_bug644097.html \
+                 test_bug678374.html \
+                 test_bug678374_icon16.png \
                  $(NULL)
 
 # browser_tabview_bug597980.js is disabled for leaking, see bug 711907
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/components/tabview/test/browser_tabview_apptabs.js
+++ b/browser/components/tabview/test/browser_tabview_apptabs.js
@@ -1,23 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
-  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
-  TabView.toggle();
+  showTabView(onTabViewWindowLoaded);
 }
 
 function onTabViewWindowLoaded() {
-  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
   ok(TabView.isVisible(), "Tab View is visible");
 
-  let contentWindow = document.getElementById("tab-view").contentWindow;
+  let contentWindow = TabView.getContentWindow();
 
   // establish initial state
   is(contentWindow.GroupItems.groupItems.length, 1,
       "we start with one group (the default)");
   is(gBrowser.tabs.length, 1, "we start with one tab");
   let originalTab = gBrowser.tabs[0];
 
   // create a group
@@ -31,67 +29,68 @@ function onTabViewWindowLoaded() {
   let xulTab = gBrowser.loadOneTab("about:blank");
   is(gBrowser.tabs.length, 2, "we now have two tabs");
   is(groupItemOne._children.length, 1, "the new tab was added to the group");
 
   // make sure the group has no app tabs
   is(appTabCount(groupItemOne), 0, "there are no app tab icons");
 
   // pin the tab, make sure the TabItem goes away and the icon comes on
-  gBrowser.pinTab(xulTab);
-  is(groupItemOne._children.length, 0,
-      "the app tab's TabItem was removed from the group");
-  is(appTabCount(groupItemOne), 1, "there's now one app tab icon");
+  whenAppTabIconAdded(function() {
+    is(groupItemOne._children.length, 0,
+       "the app tab's TabItem was removed from the group");
+    is(appTabCount(groupItemOne), 1, "there's now one app tab icon");
 
-  // create a second group and make sure it gets the icon too
-  box.offset(box.width + 20, 0);
-  let groupItemTwo = new contentWindow.GroupItem([],
-      { bounds: box, title: "test2" });
-  is(contentWindow.GroupItems.groupItems.length, 3, "we now have three groups");
-  is(appTabCount(groupItemTwo), 1,
-      "there's an app tab icon in the second group");
+    // create a second group and make sure it gets the icon too
+    box.offset(box.width + 20, 0);
+    let groupItemTwo = new contentWindow.GroupItem([],
+        { bounds: box, title: "test2" });
+    whenAppTabIconAdded(function() {
+      is(contentWindow.GroupItems.groupItems.length, 3, "we now have three groups");
+      is(appTabCount(groupItemTwo), 1,
+         "there's an app tab icon in the second group");
 
-  // When the tab was pinned, the last active group with an item got the focus.
-  // Therefore, switching the focus back to group item one.
-  contentWindow.UI.setActive(groupItemOne);
+      // When the tab was pinned, the last active group with an item got the focus.
+      // Therefore, switching the focus back to group item one.
+      contentWindow.UI.setActive(groupItemOne);
 
-  // unpin the tab, make sure the icon goes away and the TabItem comes on
-  gBrowser.unpinTab(xulTab);
-  is(groupItemOne._children.length, 1, "the app tab's TabItem is back");
-  is(appTabCount(groupItemOne), 0, "the icon is gone from group one");
-  is(appTabCount(groupItemTwo), 0, "the icon is gone from group 2");
+      // unpin the tab, make sure the icon goes away and the TabItem comes on
+      gBrowser.unpinTab(xulTab);
+      is(groupItemOne._children.length, 1, "the app tab's TabItem is back");
+      is(appTabCount(groupItemOne), 0, "the icon is gone from group one");
+      is(appTabCount(groupItemTwo), 0, "the icon is gone from group two");
 
-  // pin the tab again
-  gBrowser.pinTab(xulTab);
+      whenAppTabIconAdded(function() {
+        // close the second group
+        groupItemTwo.close();
 
-  // close the second group
-  groupItemTwo.close();
+        // find app tab in group and hit it
+        whenTabViewIsHidden(function() {
+          ok(!TabView.isVisible(),
+             "Tab View is hidden because we clicked on the app tab");
 
-  // find app tab in group and hit it
-  let onTabViewHidden = function() {
-    window.removeEventListener("tabviewhidden", onTabViewHidden, false);
-    ok(!TabView.isVisible(),
-        "Tab View is hidden because we clicked on the app tab");
+          // delete the app tab and make sure its icon goes away
+          gBrowser.removeTab(xulTab);
+          is(appTabCount(groupItemOne), 0, "closing app tab removes its icon");
 
-    // delete the app tab and make sure its icon goes away
-    gBrowser.removeTab(xulTab);
-    is(appTabCount(groupItemOne), 0, "closing app tab removes its icon");
+          // clean up
+          groupItemOne.close();
 
-    // clean up
-    groupItemOne.close();
+          is(contentWindow.GroupItems.groupItems.length, 1,
+             "we finish with one group");
+          is(gBrowser.tabs.length, 1, "we finish with one tab");
+          ok(!TabView.isVisible(), "we finish with Tab View not visible");
 
-    is(contentWindow.GroupItems.groupItems.length, 1,
-        "we finish with one group");
-    is(gBrowser.tabs.length, 1, "we finish with one tab");
-    ok(!TabView.isVisible(), "we finish with Tab View not visible");
+          finish();
+        });
 
-    finish();
-  };
-
-  window.addEventListener("tabviewhidden", onTabViewHidden, false);
-
-  let appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
-  EventUtils.sendMouseEvent({ type: "click" }, appTabIcons[0], contentWindow);
+        let appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon");
+        EventUtils.sendMouseEvent({ type: "click" }, appTabIcons[0], contentWindow);
+      });
+      gBrowser.pinTab(xulTab);
+    });
+  });
+  gBrowser.pinTab(xulTab);
 }
 
 function appTabCount(groupItem) {
   return groupItem.container.getElementsByClassName("appTabIcon").length;
 }
--- a/browser/components/tabview/test/browser_tabview_bug587503.js
+++ b/browser/components/tabview/test/browser_tabview_bug587503.js
@@ -1,13 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
+  requestLongerTimeout(2);
 
   newWindowWithTabView(onTabViewWindowLoaded);
 }
 
 function onTabViewWindowLoaded(win) {
   ok(win.TabView.isVisible(), "Tab View is visible");
 
   let contentWindow = win.document.getElementById("tab-view").contentWindow;
@@ -100,18 +101,18 @@ function onTabViewWindowLoaded(win) {
             is(index, 4, "Tab 5 is back and again the fifth tab.");
             contentWindow.Utils.log('dropSpaceActiveValues',dropSpaceActiveValues);
             is(dropSpaceActiveValues[0], false, "The group began by not showing a dropSpace");
             is(dropSpaceActiveValues[dropSpaceActiveValues.length - 1], true, "In the end, the group was showing a dropSpace");
             
             // Close the window and we're done!
             win.close();
             finish();
-          }, 6000, false);
-        },1000);
+          }, 10000, false);
+        }, 2000);
         
       });
     
     });
 
   });
 }
 
--- a/browser/components/tabview/test/browser_tabview_bug595965.js
+++ b/browser/components/tabview/test/browser_tabview_bug595965.js
@@ -48,95 +48,106 @@ function onTabViewShown(win) {
 
   let tray = groupItem.$appTabTray;
   let trayContainer = iQ(tray[0].parentNode);
 
   is(parseInt(trayContainer.css("width")), 0,
      "$appTabTray container is not visible");
 
   // pin the tab, make sure the TabItem goes away and the icon comes on
-  gBrowser.pinTab(xulTabs[0]);
-  is(groupItem._children.length, 0,
-     "the app tab's TabItem was removed from the group");
-  is(appTabCount(groupItem), 1, "there's now one app tab icon");
+  whenAppTabIconAdded(function() {
+    is(groupItem._children.length, 0,
+       "the app tab's TabItem was removed from the group");
+    is(appTabCount(groupItem), 1, "there's now one app tab icon");
+
+    is(tray.css("-moz-column-count"), 1,
+       "$appTabTray column count is 1");
+    isnot(parseInt(trayContainer.css("width")), 0,
+       "$appTabTray container is visible");
+
 
-  is(tray.css("-moz-column-count"), 1,
-     "$appTabTray column count is 1");
-  isnot(parseInt(trayContainer.css("width")), 0,
-     "$appTabTray container is visible");
+    let iconHeight = iQ(iQ(".appTabIcon", tray)[0]).height();
+    let trayHeight = parseInt(trayContainer.css("height"));
+    let rows = Math.floor(trayHeight / iconHeight);
+    let icons = rows * 2;
 
-  let iconHeight = iQ(iQ(".appTabIcon", tray)[0]).height();
-  let trayHeight = parseInt(trayContainer.css("height"));
-  let rows = Math.floor(trayHeight / iconHeight);
-  let icons = rows * 2;
+    function pinnedSomeTabs() {
+      is(appTabCount(groupItem), icons, "number of app tab icons is correct");
+
+      is(tray.css("-moz-column-count"), 2,
+         "$appTabTray column count is 2");
 
-  // add enough tabs to have two columns
-  for (let i = 1; i < icons; i++) {
-    xulTabs.push(gBrowser.loadOneTab("about:blank"));
-    gBrowser.pinTab(xulTabs[i]);
-  }
+      ok(!trayContainer.hasClass("appTabTrayContainerTruncated"),
+         "$appTabTray container does not have .appTabTrayContainerTruncated");
 
-  is(appTabCount(groupItem), icons, "number of app tab icons is correct");
+      // add one more tab
+      xulTabs.push(gBrowser.loadOneTab("about:blank"));
+      whenAppTabIconAdded(function() {
+        is(tray.css("-moz-column-count"), 3,
+           "$appTabTray column count is 3");
 
-  is(tray.css("-moz-column-count"), 2,
-     "$appTabTray column count is 2");
+        ok(trayContainer.hasClass("appTabTrayContainerTruncated"),
+           "$appTabTray container hasClass .appTabTrayContainerTruncated");
 
-  ok(!trayContainer.hasClass("appTabTrayContainerTruncated"),
-     "$appTabTray container does not have .appTabTrayContainerTruncated");
+        // remove all but one app tabs
+        for (let i = 1; i < xulTabs.length; i++)
+          gBrowser.removeTab(xulTabs[i]);
 
-  // add one more tab
-  xulTabs.push(gBrowser.loadOneTab("about:blank"));
-  gBrowser.pinTab(xulTabs[xulTabs.length-1]);
+        is(tray.css("-moz-column-count"), 1,
+           "$appTabTray column count is 1");
 
-  is(tray.css("-moz-column-count"), 3,
-     "$appTabTray column count is 3");
+        is(appTabCount(groupItem), 1, "there's now one app tab icon");
 
-  ok(trayContainer.hasClass("appTabTrayContainerTruncated"),
-     "$appTabTray container hasClass .appTabTrayContainerTruncated");
+        ok(!trayContainer.hasClass("appTabTrayContainerTruncated"),
+           "$appTabTray container does not have .appTabTrayContainerTruncated");
 
-  // remove all but one app tabs
-  for (let i = 1; i < xulTabs.length; i++)
-    gBrowser.removeTab(xulTabs[i]);
+        // unpin the last remaining tab
+        gBrowser.unpinTab(xulTabs[0]);
 
-  is(tray.css("-moz-column-count"), 1,
-     "$appTabTray column count is 1");
+        is(parseInt(trayContainer.css("width")), 0,
+           "$appTabTray container is not visible");
 
-  is(appTabCount(groupItem), 1, "there's now one app tab icon");
+        // When the tab was pinned, the last active group with an item got the focus.
+        // Therefore, switching the focus back to group item one.
+        contentWindow.UI.setActive(groupItem);
 
-  ok(!trayContainer.hasClass("appTabTrayContainerTruncated"),
-     "$appTabTray container does not have .appTabTrayContainerTruncated");
+        is(appTabCount(groupItem), 0, "there are no app tab icons");
+
+        is(groupItem._children.length, 1, "the normal tab shows in the group");
+
+        gBrowser.removeTab(xulTabs[0]);
 
-  // When the tab was pinned, the last active group with an item got the focus.
-  // Therefore, switching the focus back to group item one.
-  contentWindow.UI.setActive(groupItem);
+        // close the group
+        groupItem.close();
 
-  // unpin the last remaining tab
-  gBrowser.unpinTab(xulTabs[0]);
+        hideTabView(function() {
+          ok(!TabView.isVisible(), "Tab View is hidden");
 
-  is(parseInt(trayContainer.css("width")), 0,
-     "$appTabTray container is not visible");
+          is(contentWindow.GroupItems.groupItems.length, 1,
+             "we finish with one group");
+          is(gBrowser.tabs.length, 1, "we finish with one tab");
 
-  is(appTabCount(groupItem), 0, "there are no app tab icons");
+          win.close();
 
-  is(groupItem._children.length, 1, "the normal tab shows in the group");
-
-  gBrowser.removeTab(xulTabs[0]);
-
-  // close the group
-  groupItem.close();
+          executeSoon(finish);
+        }, win);
+      }, win);
+      win.gBrowser.pinTab(xulTabs[xulTabs.length-1]);
+    };
 
-  hideTabView(function() {
-    ok(!TabView.isVisible(), "Tab View is hidden");
-
-    is(contentWindow.GroupItems.groupItems.length, 1,
-       "we finish with one group");
-    is(gBrowser.tabs.length, 1, "we finish with one tab");
-
-    win.close();
-
-    executeSoon(finish);
+    // add enough tabs to have two columns
+    let returnCount = 0;
+    for (let i = 1; i < icons; i++) {
+      xulTabs.push(gBrowser.loadOneTab("about:blank"));
+      whenAppTabIconAdded(function() {
+        if (++returnCount == (icons - 1))
+          executeSoon(pinnedSomeTabs);
+      }, win);
+      win.gBrowser.pinTab(xulTabs[i]);
+    }
   }, win);
+  win.gBrowser.pinTab(xulTabs[0]);
 }
 
 function appTabCount(groupItem) {
   return groupItem.container.getElementsByClassName("appTabIcon").length;
 }
 
--- a/browser/components/tabview/test/browser_tabview_bug600645.js
+++ b/browser/components/tabview/test/browser_tabview_bug600645.js
@@ -5,25 +5,24 @@ const fi = Cc["@mozilla.org/browser/favi
            getService(Ci.nsIFaviconService);
 
 let newTab;
 
 function test() {
   waitForExplicitFinish();
 
   newTab = gBrowser.addTab();
-  gBrowser.pinTab(newTab);
 
-  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
-  TabView.toggle();
+  showTabView(function() {
+    whenAppTabIconAdded(onTabPinned);
+    gBrowser.pinTab(newTab);
+  })
 }
 
-function onTabViewWindowLoaded() {
-  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
-
+function onTabPinned() {
   let contentWindow = document.getElementById("tab-view").contentWindow;
   is(contentWindow.GroupItems.groupItems.length, 1, 
      "There is one group item on startup");
 
   let groupItem = contentWindow.GroupItems.groupItems[0];
   let icon = contentWindow.iQ(".appTabIcon", groupItem.$appTabTray)[0];
   let $icon = contentWindow.iQ(icon);
 
--- a/browser/components/tabview/test/browser_tabview_bug610242.js
+++ b/browser/components/tabview/test/browser_tabview_bug610242.js
@@ -24,16 +24,17 @@ function onTabViewWindowLoaded(win) {
   contentWindow.UI.setActive(group);
   is(contentWindow.GroupItems.getActiveGroupItem(), group, "new group is active");
   
   // Create a bunch of tabs in the group
   let bg = {inBackground: true};
   let datatext = win.gBrowser.loadOneTab("data:text/plain,bug610242", bg);
   let datahtml = win.gBrowser.loadOneTab("data:text/html,<blink>don't blink!</blink>", bg);
   let mozilla  = win.gBrowser.loadOneTab("about:mozilla", bg);
+  let robots   = win.gBrowser.loadOneTab("about:robots", bg);
   let html     = win.gBrowser.loadOneTab("http://example.com", bg);
   let png      = win.gBrowser.loadOneTab("http://mochi.test:8888/browser/browser/base/content/test/moz.png", bg);
   let svg      = win.gBrowser.loadOneTab("http://mochi.test:8888/browser/browser/base/content/test/title_test.svg", bg);
   
   ok(!group.shouldStack(group._children.length), "Group should not stack.");
   
   // PREPARE FINISH:
   group.addSubscriber("close", function onClose() {
@@ -41,44 +42,59 @@ function onTabViewWindowLoaded(win) {
 
     ok(group.isEmpty(), "The group is empty again");
 
     contentWindow.UI.setActive(currentGroup);
     isnot(contentWindow.GroupItems.getActiveGroupItem(), null, "There is an active group");
     is(win.gBrowser.tabs.length, 1, "There is only one tab left");
     is(win.gBrowser.visibleTabs.length, 1, "There is also only one visible tab");
 
-    let onTabViewHidden = function() {
-      win.removeEventListener("tabviewhidden", onTabViewHidden, false);
+    whenTabViewIsHidden(function() {
       win.close();
       ok(win.closed, "new window is closed");
       finish();
-    };
-    win.addEventListener("tabviewhidden", onTabViewHidden, false);
+    }, win);
     win.gBrowser.selectedTab = originalTab;
 
     win.TabView.hide();
   });
 
   function check(tab, label, visible) {
     let display = contentWindow.getComputedStyle(tab._tabViewTabItem.$fav[0], null).getPropertyValue("display");
     if (visible) {
       is(display, "block", label + " has favicon");
     } else {
       is(display, "none", label + " has no favicon");
     }
   }
 
   afterAllTabsLoaded(function() {
     afterAllTabItemsUpdated(function() {
-      check(datatext, "datatext", false);
-      check(datahtml, "datahtml", false);
-      check(mozilla, "about:mozilla", true);
-      check(html, "html", true);
-      check(png, "png", false);
-      check(svg, "svg", true);
-  
-      // Get rid of the group and its children
-      // The group close will trigger a finish().
-      closeGroupItem(group);
-    }, win);  
+      let children = group.getChildren();
+      let len = children.length;
+      let iconUpdateCounter = 0;
+
+      children.forEach(function(tabItem) {
+        tabItem.addSubscriber("iconUpdated", function onIconUpdated() {
+          // the tab is not loaded completely so ignore it.
+          if (tabItem.tab.linkedBrowser.currentURI.spec == "about:blank")
+            return;
+
+          tabItem.removeSubscriber("iconUpdated", onIconUpdated);
+
+          if (++iconUpdateCounter == len) {
+            check(datatext, "datatext", false);
+            check(datahtml, "datahtml", false);
+            check(mozilla, "about:mozilla", false);
+            check(robots, "about:robots", true);
+            check(html, "html", true);
+            check(png, "png", false);
+            check(svg, "svg", true);
+
+            // Get rid of the group and its children
+            // The group close will trigger a finish().
+            closeGroupItem(group);
+          }
+        });
+      });
+    }, win);
   }, win);
 }
--- a/browser/components/tabview/test/browser_tabview_bug626791.js
+++ b/browser/components/tabview/test/browser_tabview_bug626791.js
@@ -167,11 +167,12 @@ function test() {
         test();
       }, cw);
     };
 
     newWindowWithTabView(onShow, onLoad);
   }
 
   waitForExplicitFinish();
+  requestLongerTimeout(2);
 
   next();
 }
--- a/browser/components/tabview/test/browser_tabview_bug640765.js
+++ b/browser/components/tabview/test/browser_tabview_bug640765.js
@@ -6,63 +6,69 @@ let groupItem;
 
 function test() {
   waitForExplicitFinish();
 
   let newTabOne = gBrowser.addTab();
   let newTabTwo = gBrowser.addTab();
   let newTabThree = gBrowser.addTab();
 
-  gBrowser.pinTab(newTabOne);
-  gBrowser.pinTab(newTabTwo);
-  gBrowser.pinTab(newTabThree);
-
   registerCleanupFunction(function() {
     TabView.hide();
     while (gBrowser.tabs.length > 1)
       gBrowser.removeTab(gBrowser.tabs[0]);
   });
 
   showTabView(function() {
     contentWindow = document.getElementById("tab-view").contentWindow;
     is(contentWindow.GroupItems.groupItems.length, 1, "Has only one group");
 
     groupItem = contentWindow.GroupItems.groupItems[0];
 
-    is(xulTabForAppTabIcon(0), newTabOne,
-       "New tab one matches the first app tab icon in tabview");
-    is(xulTabForAppTabIcon(1), newTabTwo,
-       "New tab two matches the second app tab icon in tabview");
-    is(xulTabForAppTabIcon(2), newTabThree,
-       "New tab three matches the third app tab icon in tabview");
+    whenAppTabIconAdded(function() {
+      whenAppTabIconAdded(function() {
+        whenAppTabIconAdded(function() {
 
-    // move the last tab to the first position
-    gBrowser.moveTabTo(newTabThree, 0);
-    is(xulTabForAppTabIcon(0), newTabThree,
-       "New tab three matches the first app tab icon in tabview");
-    is(xulTabForAppTabIcon(1), newTabOne,
-       "New tab one matches the second app tab icon in tabview");
-    is(xulTabForAppTabIcon(2), newTabTwo,
-       "New tab two matches the third app tab icon in tabview");
+          is(xulTabForAppTabIcon(0), newTabOne,
+            "New tab one matches the first app tab icon in tabview");
+          is(xulTabForAppTabIcon(1), newTabTwo,
+            "New tab two matches the second app tab icon in tabview");
+          is(xulTabForAppTabIcon(2), newTabThree,
+            "New tab three matches the third app tab icon in tabview");
+
+          // move the last tab to the first position
+          gBrowser.moveTabTo(newTabThree, 0);
+          is(xulTabForAppTabIcon(0), newTabThree,
+            "New tab three matches the first app tab icon in tabview");
+          is(xulTabForAppTabIcon(1), newTabOne,
+            "New tab one matches the second app tab icon in tabview");
+          is(xulTabForAppTabIcon(2), newTabTwo,
+            "New tab two matches the third app tab icon in tabview");
 
-    // move the first tab to the second position
-    gBrowser.moveTabTo(newTabThree, 1);
-    is(xulTabForAppTabIcon(0), newTabOne,
-       "New tab one matches the first app tab icon in tabview");
-    is(xulTabForAppTabIcon(1), newTabThree,
-       "New tab three matches the second app tab icon in tabview");
-    is(xulTabForAppTabIcon(2), newTabTwo,
-       "New tab two matches the third app tab icon in tabview");
+          // move the first tab to the second position
+          gBrowser.moveTabTo(newTabThree, 1);
+          is(xulTabForAppTabIcon(0), newTabOne,
+            "New tab one matches the first app tab icon in tabview");
+          is(xulTabForAppTabIcon(1), newTabThree,
+            "New tab three matches the second app tab icon in tabview");
+          is(xulTabForAppTabIcon(2), newTabTwo,
+            "New tab two matches the third app tab icon in tabview");
 
-    hideTabView(function() {
-      gBrowser.removeTab(newTabOne);
-      gBrowser.removeTab(newTabTwo);
-      gBrowser.removeTab(newTabThree);
-      finish();
+          hideTabView(function() {
+            gBrowser.removeTab(newTabOne);
+            gBrowser.removeTab(newTabTwo);
+            gBrowser.removeTab(newTabThree);
+            finish();
+          });
+        });
+        gBrowser.pinTab(newTabThree);
+      });
+      gBrowser.pinTab(newTabTwo);
     });
+    gBrowser.pinTab(newTabOne);
   });
 }
 
 function xulTabForAppTabIcon(index) {
     return contentWindow.iQ(
              contentWindow.iQ(".appTabIcon", 
                               groupItem.$appTabTray)[index]).data("xulTab");
 }
new file mode 100644
--- /dev/null
+++ b/browser/components/tabview/test/browser_tabview_bug678374.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const ICON_URL = "moz-anno:favicon:http://example.com/browser/browser/components/tabview/test/test_bug678374_icon16.png";
+const TEST_URL = "http://example.com/browser/browser/components/tabview/test/test_bug678374.html";
+
+function test() {
+  Services.prefs.setBoolPref("browser.chrome.favicons", false);
+
+  waitForExplicitFinish();
+
+  newWindowWithTabView(function(win) {
+    is(win.gBrowser.tabs.length, 3, "There are 3 tabs")
+
+    let newTabOne = win.gBrowser.tabs[1];
+    let newTabTwo = win.gBrowser.tabs[2];
+    let cw = win.TabView.getContentWindow();
+    let groupItem = cw.GroupItems.groupItems[0];
+
+    // test tab item
+    let newTabItemOne = newTabOne._tabViewTabItem;
+
+    newTabItemOne.addSubscriber("iconUpdated", function onIconUpdated() {
+      newTabItemOne.removeSubscriber("iconUpdated", onIconUpdated);
+      is(newTabItemOne.$favImage[0].src, ICON_URL, "The tab item is showing the right icon.");
+
+      // test pin tab
+      whenAppTabIconAdded(function() {
+        let icon = cw.iQ(".appTabIcon", groupItem.$appTabTray)[0];
+        is(icon.src, ICON_URL, "The app tab is showing the right icon");
+
+        finish();
+      }, win);
+      win.gBrowser.pinTab(newTabTwo);
+    });
+  }, function(win) {
+    registerCleanupFunction(function() { 
+      Services.prefs.clearUserPref("browser.chrome.favicons");
+      win.close(); 
+   });
+
+    win.gBrowser.loadOneTab(TEST_URL);
+    win.gBrowser.loadOneTab(TEST_URL);
+  });
+}
--- a/browser/components/tabview/test/browser_tabview_bug685476.js
+++ b/browser/components/tabview/test/browser_tabview_bug685476.js
@@ -1,24 +1,26 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   showTabView(function () {
     let tab = gBrowser.addTab();
-    gBrowser.pinTab(tab);
     registerCleanupFunction(function () gBrowser.removeTab(tab));
 
-    let cw = TabView.getContentWindow();
-    let body = cw.document.body;
-    let [appTabIcon] = cw.iQ(".appTabTray .appTabIcon");
+    whenAppTabIconAdded(function() {
+      let cw = TabView.getContentWindow();
+      let body = cw.document.body;
+      let [appTabIcon] = cw.iQ(".appTabTray .appTabIcon");
 
-    EventUtils.synthesizeMouseAtCenter(appTabIcon, {type: "mousedown"}, cw);
-    EventUtils.synthesizeMouse(body, 500, 100, {type: "mousemove"}, cw);
-    EventUtils.synthesizeMouse(body, 500, 100, {type: "mouseup"}, cw);
+      EventUtils.synthesizeMouseAtCenter(appTabIcon, {type: "mousedown"}, cw);
+      EventUtils.synthesizeMouse(body, 500, 100, {type: "mousemove"}, cw);
+      EventUtils.synthesizeMouse(body, 500, 100, {type: "mouseup"}, cw);
 
-    ok(TabView.isVisible(), "tabview is still visible");
+      ok(TabView.isVisible(), "tabview is still visible");
 
-    hideTabView(finish);
+      hideTabView(finish);
+    });
+    gBrowser.pinTab(tab);
   });
 }
--- a/browser/components/tabview/test/head.js
+++ b/browser/components/tabview/test/head.js
@@ -380,8 +380,22 @@ function togglePrivateBrowsing(callback)
     executeSoon(function () afterAllTabsLoaded(callback));
   }, topic, false);
 
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
 
   pb.privateBrowsingEnabled = !pb.privateBrowsingEnabled;
 }
+
+// ----------
+function whenAppTabIconAdded(callback, win) {
+  win = win || window;
+
+  let contentWindow = win.TabView.getContentWindow();
+  let groupItems = contentWindow.GroupItems.groupItems;
+  let groupItem = groupItems[(groupItems.length - 1)];
+
+  groupItem.addSubscriber("appTabIconAdded", function onAppTabIconAdded() {
+    groupItem.removeSubscriber("appTabIconAdded", onAppTabIconAdded);
+    callback();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/tabview/test/test_bug678374.html
@@ -0,0 +1,7 @@
+<html>
+  <head>
+    <title>Bug 678374</title>
+    <link rel="icon" type="image/png" id="favicon" href="test_bug678374_icon16.png" />
+  <body>
+  </body>
+</html>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bc317a8d5fc3121c939e2bc9a17f5d024b709a12
GIT binary patch
literal 924
zc$@*817rM&P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000AHNkl<Zc-mc(
zeMsB|7{{NxvwQL8{qFtMd-Hd{J3a3_@AQ0|Zb~^TaRz2987(%p1>1t&a4q(ah$gW%
z8~dwZ5J60-O=1aaVdNh|ZlkDy8`LrwLK_O@pcwJ#7ZFl_e0lzPKHu;6fk%M<E4y&}
z-uS66ZY`c!TK)OMmGwLG_a9#Q{O;!H9}xO?Z{qrWF}`<O{sXUU{QlO!<W&-H&a?C4
zb@qPwJx_nOOwXmS2~J!kw*SI|^H*-Y1jx+VpCadOZW%26@XMTK*CdAWG=&2n(J^(I
z>SO0|Jo7eH{o{oC$B^hSV(sDJiF4<UEN@6+k$((+THl;76ptc~L89F!P_`dKY21gW
zw4Xrfc|wgt#2X(aQE11n77>XdyT@MBCNGKri5)X*Nb_mDp)UMQeK?A{aAjKY*0$iy
zwiC#;<Ew4LSJ#X)QAa%Af<4uRsdczoZv4uW41J8$jNp#uN#sk!3p%I+POS+?s)Q|;
z$5Nfcmu<lsOyLSA@fUiq^&UPgCl?<qi{~gNF;S@mX=v*}Eq7t9&gx)}%6J~Xo~*>P
z_%bE5awie39=BRT5}h|?czkY6#6l>lQE2Z#iKGyZhB=tQ9Livg)Df<4rn&bCwB`<y
zdOLThhN^&yWcoH_;Pu(NA~q5!mF|Hj$+qo4suCF8Nh|>kqo;;Pd@0I(1El{pXei@~
z)*%&M47vW_<j|RKz7pv}O(zK_Yp5+X5l-tjdsR%%gg$F9DlxW&G_+z1s@}lvj$pD!
zFcbz?<-)@1aglz4@j8;VDuHMmHJ8;>Y7tA23TIrOAwAJYEL*~0^&(<Ilr+*hG9wSs
zPimV)#6f+dMoi1-%Zi>5MvPvp&L~yBYCMqy4!?>R6h!P8)4fRltDOSK{EhX4V(TT~
zbK(xf5vkJULJVGPb`QnvWikzU6h*;ev}5*Wsd((jB^f%o^~%pqUps4bw&KzOt2L<0
zhuPxCY<3cfso3@ECNZ-uki|MM`kjnkHp?rYY#F?`v?($&JL<~q!5=Q+^QZ87V|d(A
z?3Dprfm)o!-B<_Tn3coto8_Gqk$3NG8H~*RC~|yBtS^6>wmmgA<=A=Xnyc-(d#<)U
y>yGXND?0drY454WW#6>O3ui^fmPDq$mA?Qdy0Sx%{P*|(0000<MNUMnLSTZyBg9Go
--- a/browser/components/tabview/ui.js
+++ b/browser/components/tabview/ui.js
@@ -46,16 +46,22 @@
 // Title: ui.js
 
 let Keys = { meta: false };
 
 // ##########
 // Class: UI
 // Singleton top-level UI manager.
 let UI = {
+  // Pref that controls whether to display site icons
+  PREF_CHROME_SITE_ICONS: "browser.chrome.site_icons",
+
+  // Pref that controls whether to display fav icons
+  PREF_CHROME_FAVICONS: "browser.chrome.favicons",
+
   // Variable: _frameInitialized
   // True if the Tab View UI frame has been initialized.
   _frameInitialized: false,
 
   // Variable: _pageBounds
   // Stores the page bounds.
   _pageBounds: null,
 
@@ -136,16 +142,22 @@ let UI = {
   // Variable: _lastOpenedTab
   // Used to keep track of the last opened tab.
   _lastOpenedTab: null,
 
   // Variable: _originalSmoothScroll
   // Used to keep track of the tab strip smooth scroll value.
   _originalSmoothScroll: null,
 
+  // Used to keep track of the browser.chrome.site_icons pref value.
+  _prefSiteIcons: null,
+
+  // Used to keep track of the browser.chrome.favicons pref value.
+  _prefFavicons: null,
+
   // ----------
   // Function: toString
   // Prints [UI] for debug use
   toString: function UI_toString() {
     return "[UI]";
   },
 
   // ----------
@@ -236,16 +248,20 @@ let UI = {
       });
 
       // ___ setup key handlers
       this._setTabViewFrameKeyHandlers();
 
       // ___ add tab action handlers
       this._addTabActionHandlers();
 
+      // ___ add preference observers
+      Services.prefs.addObserver(this.PREF_CHROME_SITE_ICONS, this, false);
+      Services.prefs.addObserver(this.PREF_CHROME_FAVICONS, this, false);
+
       // ___ groups
       GroupItems.init();
       GroupItems.pauseArrange();
       let hasGroupItemsData = GroupItems.load();
 
       // ___ tabs
       TabItems.init();
       TabItems.pausePainting();
@@ -309,16 +325,19 @@ let UI = {
     this._cleanupFunctions = [];
 
     // additional clean up
     TabItems.uninit();
     GroupItems.uninit();
     Storage.uninit();
     StoragePolicy.uninit();
 
+    Services.prefs.removeObserver(this.PREF_CHROME_SITE_ICONS, this);
+    Services.prefs.removeObserver(this.PREF_CHROME_FAVICONS, this);
+
     this._removeTabActionHandlers();
     this._currentTab = null;
     this._pageBounds = null;
     this._reorderTabItemsOnShow = null;
     this._reorderTabsOnHide = null;
     this._frameInitialized = false;
   },
 
@@ -847,16 +866,29 @@ let UI = {
   // Function: _removeTabActionHandlers
   // Removes handlers to handle tab actions.
   _removeTabActionHandlers: function UI__removeTabActionHandlers() {
     for (let name in this._eventListeners)
       AllTabs.unregister(name, this._eventListeners[name]);
   },
 
   // ----------
+  // Function: observe
+  // Observes different preference value changes.
+  observe: function UI_observe(subject, topic, data) {
+    if (data == this.PREF_CHROME_SITE_ICONS) {
+      this._prefSiteIcons =
+        Services.prefs.getBoolPref(this.PREF_CHROME_SITE_ICONS);
+    } else if (data == this.PREF_CHROME_FAVICONS) {
+      this._prefFavicons =
+        Services.prefs.getBoolPref(this.PREF_CHROME_FAVICONS);
+    }
+  },
+
+  // ----------
   // Function: goToTab
   // Selects the given xul:tab in the browser.
   goToTab: function UI_goToTab(xulTab) {
     // If it's not focused, the onFocus listener would handle it.
     if (gBrowser.selectedTab == xulTab)
       this.onTabSelect(xulTab);
     else
       gBrowser.selectedTab = xulTab;
@@ -1599,49 +1631,85 @@ let UI = {
 
     if (this._storageSanity(data))
       Storage.saveUIData(gWindow, data);
   },
 
   // ----------
   // Function: _saveAll
   // Saves all data associated with TabView.
-  // TODO: Save info items
   _saveAll: function UI__saveAll() {
     this._save();
     GroupItems.saveAll();
     TabItems.saveAll();
   },
 
   // ----------
-  // Function: shouldLoadFavIcon
-  // Takes a xul:browser and checks whether we should display a favicon for it.
-  shouldLoadFavIcon: function UI_shouldLoadFavIcon(browser) {
-    return !(browser.contentDocument instanceof window.ImageDocument) &&
-            (browser.currentURI.schemeIs("about") ||
-             gBrowser.shouldLoadFavIcon(browser.contentDocument.documentURIObject));
+  // Function: _isImageDocument
+  // Checks whether an image is loaded into the given tab.
+  _isImageDocument: function UI__isImageDocument(tab, callback) {
+    let mm = tab.linkedBrowser.messageManager;
+    let message = "Panorama:isImageDocument";
+
+    mm.addMessageListener(message, function onMessage(cx) {
+      mm.removeMessageListener(cx.name, onMessage);
+      callback(cx.json.isImageDocument);
+    });
+    mm.sendAsyncMessage(message);
+  },
+
+  // ----------
+  // Function: _shouldLoadFavIcon
+  // Checks whether fav icon should be loaded for a given tab.
+  _shouldLoadFavIcon: function UI__shouldLoadFavIcon(tab) {
+    let uri = tab.linkedBrowser.currentURI;
+
+    if (!uri)
+      return false;
+
+    if (this._prefSiteIcons == null)
+      this._prefSiteIcons =
+        Services.prefs.getBoolPref(this.PREF_CHROME_SITE_ICONS);
+
+    if (!this._prefSiteIcons)
+      return false;
+
+    if (this._prefFavicons == null)
+      this._prefFavicons =
+        Services.prefs.getBoolPref(this.PREF_CHROME_FAVICONS);
+
+    return (this._prefFavicons && ("schemeIs" in uri) &&
+            (uri.schemeIs("http") || uri.schemeIs("https")));
   },
 
   // ----------
   // Function: getFavIconUrlForTab
   // Gets fav icon url for the given xul:tab.
-  getFavIconUrlForTab: function UI_getFavIconUrlForTab(tab) {
-    let url;
+  getFavIconUrlForTab: function UI_getFavIconUrlForTab(tab, callback) {
+    this._isImageDocument(tab, function(isImageDoc) {
+      if (isImageDoc) {
+        callback(tab.pinned ? tab.image : null);
+      } else {
+        let tabImage = tab.image;
+        if (tabImage) {
+          // if starts with http/https, fetch icon from favicon service via the moz-anno protocal
+          if (/^https?:/.test(tabImage))
+            tabImage = gFavIconService.getFaviconLinkForIcon(gWindow.makeURI(tab.image)).spec;
 
-    if (tab.image) {
-      // if starts with http/https, fetch icon from favicon service via the moz-anno protocal
-      if (/^https?:/.test(tab.image))
-        url = gFavIconService.getFaviconLinkForIcon(gWindow.makeURI(tab.image)).spec;
-      else
-        url = tab.image;
-    } else {
-      url = gFavIconService.getFaviconImageForPage(tab.linkedBrowser.currentURI).spec;
-    }
-
-    return url;
+          callback(tabImage);
+        } else {
+          // determine to load the default/cached icon or not and also ensure we don't show the default icon
+          // for about:-style error pages
+          let url = null;
+          if (this._shouldLoadFavIcon(tab))
+            url = gFavIconService.getFaviconImageForPage(tab.linkedBrowser.currentURI).spec;
+          callback(url);
+        }
+      }
+    }.bind(this));
   },
 
   // ----------
   // Function: notifySessionRestoreEnabled
   // Notify the user that session restore has been automatically enabled
   // by showing a banner that expects no user interaction. It fades out after
   // some seconds.
   notifySessionRestoreEnabled: function UI_notifySessionRestoreEnabled() {
--- a/browser/locales/en-US/chrome/browser/aboutHome.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutHome.dtd
@@ -1,23 +1,26 @@
-<!ENTITY % brandDTD
-    SYSTEM "chrome://branding/locale/brand.dtd">
-  %brandDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+%syncBrandDTD;
 
 <!-- These strings are used in the about:home page -->
 
 <!ENTITY abouthome.pageTitle "&brandFullName; Start Page">
 
 <!ENTITY abouthome.searchEngineButton.label "Search">
 
-<!ENTITY abouthome.aboutMozilla "About Mozilla">
-
 <!-- LOCALIZATION NOTE (abouthome.defaultSnippet1.v1):
      text in <a/> will be linked to the Firefox features page on mozilla.com
 -->
 <!ENTITY abouthome.defaultSnippet1.v1 "Thanks for choosing Firefox! To get the most out of your browser, learn more about the <a>latest features</a>.">
 <!-- LOCALIZATION NOTE (abouthome.defaultSnippet2.v1):
      text in <a/> will be linked to the featured add-ons on addons.mozilla.org
 -->
 <!ENTITY abouthome.defaultSnippet2.v1 "It's easy to customize your Firefox exactly the way you want it. <a>Choose from thousands of add-ons</a>.">
 
-<!ENTITY abouthome.syncSetup.label   "Set Up Sync">
-<!ENTITY abouthome.pairDevice.label  "Pair a Device">
+<!ENTITY abouthome.bookmarksButton.label "Bookmarks">
+<!ENTITY abouthome.historyButton.label   "History">
+<!ENTITY abouthome.settingsButton.label  "Settings">
+<!ENTITY abouthome.addonsButton.label    "Add-ons">
+<!ENTITY abouthome.appsButton.label      "Marketplace">
+<!ENTITY abouthome.downloadsButton.label "Downloads">
--- a/browser/modules/NewTabUtils.jsm
+++ b/browser/modules/NewTabUtils.jsm
@@ -545,17 +545,17 @@ let Links = {
 
     return pinnedLinks;
   },
 
   /**
    * Resets the links cache.
    */
   resetCache: function Links_resetCache() {
-    this._links = [];
+    this._links = null;
   },
 
   /**
    * Implements the nsIObserver interface to get notified about browser history
    * sanitization.
    */
   observe: function Links_observe(aSubject, aTopic, aData) {
     // Make sure to update open about:newtab instances. If there are no opened
@@ -579,22 +579,26 @@ let Links = {
                                          Ci.nsISupportsWeakReference])
 };
 
 /**
  * Singleton that provides the public API of this JSM.
  */
 let NewTabUtils = {
   /**
-   * Resets the NewTabUtils module, its links and its storage.
+   * Restores all sites that have been removed from the grid.
    */
-  reset: function NewTabUtils_reset() {
+  restore: function NewTabUtils_restore() {
     Storage.clear();
     Links.resetCache();
     PinnedLinks.resetCache();
     BlockedLinks.resetCache();
+
+    Links.populateCache(function () {
+      AllPages.update();
+    }, true);
   },
 
   allPages: AllPages,
   links: Links,
   pinnedLinks: PinnedLinks,
   blockedLinks: BlockedLinks
 };
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -6702,33 +6702,37 @@ AddonInstall.prototype = {
         let self = this;
         XPIDatabase.getAddonInLocation(this.addon.id, this.installLocation.name,
                                        function(a) {
           self.addon = a;
           if (self.addon.bootstrap) {
             XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version,
                                             self.addon.type, file, "install",
                                             reason);
+          }
+
+          AddonManagerPrivate.callAddonListeners("onInstalled",
+                                                 createWrapper(self.addon));
+
+          LOG("Install of " + self.sourceURI.spec + " completed.");
+          self.state = AddonManager.STATE_INSTALLED;
+          AddonManagerPrivate.callInstallListeners("onInstallEnded",
+                                                   self.listeners, self.wrapper,
+                                                   createWrapper(self.addon));
+
+          if (self.addon.bootstrap) {
             if (self.addon.active) {
               XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version,
                                               self.addon.type, file, "startup",
                                               reason);
             }
             else {
               XPIProvider.unloadBootstrapScope(self.addon.id);
             }
           }
-          AddonManagerPrivate.callAddonListeners("onInstalled",
-                                                 createWrapper(self.addon));
-
-          LOG("Install of " + self.sourceURI.spec + " completed.");
-          self.state = AddonManager.STATE_INSTALLED;
-          AddonManagerPrivate.callInstallListeners("onInstallEnded",
-                                                   self.listeners, self.wrapper,
-                                                   createWrapper(self.addon));
         });
       }
     }
     catch (e) {
       WARN("Failed to install", e);
       if (stagedAddon.exists())
         recursiveRemove(stagedAddon);
       this.state = AddonManager.STATE_INSTALL_FAILED;
@@ -7774,31 +7778,45 @@ function AddonWrapper(aAddon) {
 
   this.findUpdates = function(aListener, aReason, aAppVersion, aPlatformVersion) {
     new UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion);
   };
 
   this.hasResource = function(aPath) {
     let bundle = aAddon._sourceBundle.clone();
 
-    if (bundle.isDirectory()) {
+    // Bundle may not exist any more if the addon has just been uninstalled,
+    // but explicitly first checking .exists() results in unneeded file I/O.
+    try {
+      var isDir = bundle.isDirectory();
+    } catch (e) {
+      return false;
+    }
+
+    if (isDir) {
       if (aPath) {
         aPath.split("/").forEach(function(aPart) {
           bundle.append(aPart);
         });
       }
       return bundle.exists();
     }
 
     let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
                     createInstance(Ci.nsIZipReader);
-    zipReader.open(bundle);
-    let result = zipReader.hasEntry(aPath);
-    zipReader.close();
-    return result;
+    try {
+      zipReader.open(bundle);
+      return zipReader.hasEntry(aPath);
+    }
+    catch (e) {
+      return false;
+    }
+    finally {
+      zipReader.close();
+    }
   },
 
   /**
    * Returns a URI to the selected resource or to the add-on bundle if aPath
    * is null. URIs to the bundle will always be file: URIs. URIs to resources
    * will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is
    * still an XPI file.
    *
--- a/toolkit/mozapps/extensions/test/addons/test_bootstrap1_1/bootstrap.js
+++ b/toolkit/mozapps/extensions/test/addons/test_bootstrap1_1/bootstrap.js
@@ -3,16 +3,17 @@ Components.utils.import("resource://gre/
 function install(data, reason) {
   Components.utils.import(data.resourceURI.spec + "version.jsm");
   Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
   Services.prefs.setIntPref("bootstraptest.install_reason", reason);
   Components.utils.unload(data.resourceURI.spec + "version.jsm");
 }
 
 function startup(data, reason) {
+  Components.utils.reportError("bootstrap startup");
   Components.utils.import(data.resourceURI.spec + "version.jsm");
   Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
   Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
   Components.utils.unload(data.resourceURI.spec + "version.jsm");
 }
 
 function shutdown(data, reason) {
   Services.prefs.setIntPref("bootstraptest.active_version", 0);
--- a/toolkit/mozapps/extensions/test/addons/test_getresource/install.rdf
+++ b/toolkit/mozapps/extensions/test/addons/test_getresource/install.rdf
@@ -5,17 +5,17 @@
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>addon1@tests.mozilla.org</em:id>
     <em:version>1.0</em:version>
 
     <!-- Front End MetaData -->
     <em:name>Test 1</em:name>
     <em:description>Test Description</em:description>
-
+    <em:bootstrap>true</em:bootstrap>
     <em:targetApplication>
       <Description>
         <em:id>xpcshell@tests.mozilla.org</em:id>
         <em:minVersion>1</em:minVersion>
         <em:maxVersion>1</em:maxVersion>
       </Description>
     </em:targetApplication>
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
@@ -36,16 +36,24 @@ function resetPrefs() {
   Services.prefs.setIntPref("bootstraptest2.active_version", -1);
   Services.prefs.setIntPref("bootstraptest2.installed_version", -1);
   Services.prefs.setIntPref("bootstraptest.startup_reason", -1);
   Services.prefs.setIntPref("bootstraptest.shutdown_reason", -1);
   Services.prefs.setIntPref("bootstraptest.install_reason", -1);
   Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1);
 }
 
+function waitForPref(aPref, aCallback) {
+  function prefChanged() {
+    Services.prefs.removeObserver(aPref, prefChanged);
+    aCallback();
+  }
+  Services.prefs.addObserver(aPref, prefChanged, false);
+}
+
 function getActiveVersion() {
   return Services.prefs.getIntPref("bootstraptest.active_version");
 }
 
 function getInstalledVersion() {
   return Services.prefs.getIntPref("bootstraptest.installed_version");
 }
 
@@ -163,17 +171,23 @@ function run_test_1() {
         ["onInstalling", false],
         "onInstalled"
       ]
     }, [
       "onInstallStarted",
       "onInstallEnded",
     ], function() {
       do_check_true(addon.hasResource("install.rdf"));
-      check_test_1(addon.syncGUID);
+
+      // startup should not have been called yet.
+      do_check_eq(getActiveVersion(), -1);
+
+      waitForPref("bootstraptest.active_version", function() {
+        check_test_1(addon.syncGUID);
+      });
     });
     install.install();
   });
 }
 
 function check_test_1(installSyncGUID) {
   let file = gProfD.clone();
   file.append("extensions.sqlite");
@@ -359,17 +373,19 @@ function run_test_6() {
     prepare_test({
       "bootstrap1@tests.mozilla.org": [
         ["onInstalling", false],
         "onInstalled"
       ]
     }, [
       "onInstallStarted",
       "onInstallEnded",
-    ], check_test_6);
+    ], function() {
+      waitForPref("bootstraptest.active_version", check_test_6);
+    });
     install.install();
   });
 }
 
 function check_test_6() {
   AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
     do_check_neq(b1, null);
     do_check_eq(b1.version, "2.0");
@@ -489,17 +505,19 @@ function run_test_10() {
     prepare_test({
       "bootstrap1@tests.mozilla.org": [
         ["onInstalling", false],
         "onInstalled"
       ]
     }, [
       "onInstallStarted",
       "onInstallEnded",
-    ], check_test_10_pt1);
+    ], function() {
+      waitForPref("bootstraptest.active_version", check_test_10_pt1);
+    });
     install.install();
   });
 }
 
 function check_test_10_pt1() {
   AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
     do_check_neq(b1, null);
     do_check_eq(b1.version, "2.0");
@@ -530,17 +548,19 @@ function check_test_10_pt1() {
       prepare_test({
         "bootstrap1@tests.mozilla.org": [
           ["onInstalling", false],
           "onInstalled"
         ]
       }, [
         "onInstallStarted",
         "onInstallEnded",
-      ], check_test_10_pt2);
+      ], function() {
+      waitForPref("bootstraptest.active_version", check_test_10_pt2);
+    });
       install.install();
     });
   });
 }
 
 function check_test_10_pt2() {
   AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
     do_check_neq(b1, null);
@@ -1117,84 +1137,88 @@ function check_test_23() {
   });
 }
 
 // Tests that we recover from a broken preference
 function run_test_24() {
   resetPrefs();
   installAllFiles([do_get_addon("test_bootstrap1_1"), do_get_addon("test_bootstrap2_1")],
                   function() {
-    do_check_eq(getInstalledVersion(), 1);
-    do_check_eq(getActiveVersion(), 1);
-    do_check_eq(getInstalledVersion2(), 1);
-    do_check_eq(getActiveVersion2(), 1);
-
-    resetPrefs();
-
-    restartManager();
-
-    do_check_eq(getInstalledVersion(), -1);
-    do_check_eq(getActiveVersion(), 1);
-    do_check_eq(getInstalledVersion2(), -1);
-    do_check_eq(getActiveVersion2(), 1);
-
-    shutdownManager();
-
-    do_check_eq(getInstalledVersion(), -1);
-    do_check_eq(getActiveVersion(), 0);
-    do_check_eq(getInstalledVersion2(), -1);
-    do_check_eq(getActiveVersion2(), 0);
-
-    // Break the preferece
-    let bootstrappedAddons = JSON.parse(Services.prefs.getCharPref("extensions.bootstrappedAddons"));
-    bootstrappedAddons["bootstrap1@tests.mozilla.org"].descriptor += "foo";
-    Services.prefs.setCharPref("extensions.bootstrappedAddons", JSON.stringify(bootstrappedAddons));
-
-    startupManager(false);
-
-    do_check_eq(getInstalledVersion(), -1);
-    do_check_eq(getActiveVersion(), 1);
-    do_check_eq(getInstalledVersion2(), -1);
-    do_check_eq(getActiveVersion2(), 1);
-
-    run_test_25();
+    waitForPref("bootstraptest2.active_version", function() {
+      do_check_eq(getInstalledVersion(), 1);
+      do_check_eq(getActiveVersion(), 1);
+      do_check_eq(getInstalledVersion2(), 1);
+      do_check_eq(getActiveVersion2(), 1);
+  
+      resetPrefs();
+  
+      restartManager();
+  
+      do_check_eq(getInstalledVersion(), -1);
+      do_check_eq(getActiveVersion(), 1);
+      do_check_eq(getInstalledVersion2(), -1);
+      do_check_eq(getActiveVersion2(), 1);
+  
+      shutdownManager();
+  
+      do_check_eq(getInstalledVersion(), -1);
+      do_check_eq(getActiveVersion(), 0);
+      do_check_eq(getInstalledVersion2(), -1);
+      do_check_eq(getActiveVersion2(), 0);
+  
+      // Break the preferece
+      let bootstrappedAddons = JSON.parse(Services.prefs.getCharPref("extensions.bootstrappedAddons"));
+      bootstrappedAddons["bootstrap1@tests.mozilla.org"].descriptor += "foo";
+      Services.prefs.setCharPref("extensions.bootstrappedAddons", JSON.stringify(bootstrappedAddons));
+  
+      startupManager(false);
+  
+      do_check_eq(getInstalledVersion(), -1);
+      do_check_eq(getActiveVersion(), 1);
+      do_check_eq(getInstalledVersion2(), -1);
+      do_check_eq(getActiveVersion2(), 1);
+  
+      run_test_25();
+    });
   });
 }
 
 // Tests that updating from a bootstrappable add-on to a normal add-on calls
 // the uninstall method
 function run_test_25() {
   installAllFiles([do_get_addon("test_bootstrap1_1")], function() {
-    do_check_eq(getInstalledVersion(), 1);
-    do_check_eq(getActiveVersion(), 1);
-
-    installAllFiles([do_get_addon("test_bootstrap1_4")], function() {
-      // Needs a restart to complete this so the old version stays running
+    waitForPref("bootstraptest.active_version", function() {
       do_check_eq(getInstalledVersion(), 1);
       do_check_eq(getActiveVersion(), 1);
-
-      AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
-        do_check_neq(b1, null);
-        do_check_eq(b1.version, "1.0");
-        do_check_true(b1.isActive);
-        do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE));
-
-        restartManager();
-
-        do_check_eq(getInstalledVersion(), 0);
-        do_check_eq(getUninstallReason(), ADDON_UPGRADE);
-        do_check_eq(getActiveVersion(), 0);
-
+  
+      installAllFiles([do_get_addon("test_bootstrap1_4")], function() {
+        // Needs a restart to complete this so the old version stays running
+        do_check_eq(getInstalledVersion(), 1);
+        do_check_eq(getActiveVersion(), 1);
+  
         AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
           do_check_neq(b1, null);
-          do_check_eq(b1.version, "4.0");
+          do_check_eq(b1.version, "1.0");
           do_check_true(b1.isActive);
-          do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
-
-          run_test_26();
+          do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE));
+  
+          restartManager();
+  
+          do_check_eq(getInstalledVersion(), 0);
+          do_check_eq(getUninstallReason(), ADDON_UPGRADE);
+          do_check_eq(getActiveVersion(), 0);
+  
+          AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
+            do_check_neq(b1, null);
+            do_check_eq(b1.version, "4.0");
+            do_check_true(b1.isActive);
+            do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
+  
+            run_test_26();
+          });
         });
       });
     });
   });
 }
 
 // Tests that updating from a normal add-on to a bootstrappable add-on calls
 // the install method
--- a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js
@@ -23,16 +23,17 @@ var testserver;
 
 /**
  * This object is both a factory and an mozISpellCheckingEngine implementation (so, it
  * is de-facto a service). It's also an interface requestor that gives out
  * itself when asked for mozISpellCheckingEngine.
  */
 var HunspellEngine = {
   dictionaryDirs: [],
+  listener: null,
   
   QueryInterface: function hunspell_qi(iid) {
     if (iid.equals(Components.interfaces.nsISupports) ||
         iid.equals(Components.interfaces.nsIFactory) ||
         iid.equals(Components.interfaces.mozISpellCheckingEngine))
       return this;
     throw Components.results.NS_ERROR_NO_INTERFACE;
   },
@@ -42,20 +43,24 @@ var HunspellEngine = {
     return this.QueryInterface(iid);
   },
   lockFactory: function hunspell_lockf(lock) {
     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   },
 
   addDirectory: function hunspell_addDirectory(dir) {
     this.dictionaryDirs.push(dir);
+    if (this.listener)
+      this.listener("addDirectory");
   },
 
   removeDirectory: function hunspell_addDirectory(dir) {
     this.dictionaryDirs.splice(this.dictionaryDirs.indexOf(dir), 1);
+    if (this.listener)
+      this.listener("removeDirectory");
   },
 
   getInterface: function hunspell_gi(iid) {
     if (iid.equals(Components.interfaces.mozISpellCheckingEngine))
       return this;
     throw Components.results.NS_ERROR_NO_INTERFACE;
   },
 
@@ -131,17 +136,21 @@ function run_test_1() {
         ["onInstalling", false],
         "onInstalled"
       ]
     }, [
       "onInstallStarted",
       "onInstallEnded",
     ], function() {
       do_check_true(addon.hasResource("install.rdf"));
-      check_test_1();
+      HunspellEngine.listener = function(aEvent) {
+        HunspellEngine.listener = null;
+        do_check_eq(aEvent, "addDirectory");
+        check_test_1();
+      };
     });
     install.install();
   });
 }
 
 function check_test_1() {
   AddonManager.getAllInstalls(function(installs) {
     // There should be no active installs now since the install completed and
@@ -561,42 +570,46 @@ function check_test_23() {
     });
   });
 }
 
 // Tests that updating from a bootstrappable add-on to a normal add-on calls
 // the uninstall method
 function run_test_25() {
   installAllFiles([do_get_addon("test_dictionary")], function() {
-    do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
-
-    installAllFiles([do_get_addon("test_dictionary_2")], function() {
-      // Needs a restart to complete this so the old version stays running
+    HunspellEngine.listener = function(aEvent) {
+      HunspellEngine.listener = null;
+      do_check_eq(aEvent, "addDirectory");
       do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
-
-      AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
-        do_check_neq(b1, null);
-        do_check_eq(b1.version, "1.0");
-        do_check_true(b1.isActive);
-        do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE));
-
-        restartManager();
-
-        do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
-
+  
+      installAllFiles([do_get_addon("test_dictionary_2")], function() {
+        // Needs a restart to complete this so the old version stays running
+        do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
+  
         AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
           do_check_neq(b1, null);
-          do_check_eq(b1.version, "2.0");
+          do_check_eq(b1.version, "1.0");
           do_check_true(b1.isActive);
-          do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
-
-          run_test_26();
+          do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE));
+  
+          restartManager();
+  
+          do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic"));
+  
+          AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) {
+            do_check_neq(b1, null);
+            do_check_eq(b1.version, "2.0");
+            do_check_true(b1.isActive);
+            do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE);
+  
+            run_test_26();
+          });
         });
       });
-    });
+    };
   });
 }
 
 // Tests that updating from a normal add-on to a bootstrappable add-on calls
 // the install method
 function run_test_26() {
   installAllFiles([do_get_addon("test_dictionary")], function() {
     // Needs a restart to complete this
--- a/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // install.rdf size, icon.png size, subfile.txt size
-const ADDON_SIZE = 635 + 15 + 26;
+const ADDON_SIZE = 672 + 15 + 26;
 
 // This verifies the functionality of getResourceURI
 // There are two cases - with a filename it returns an nsIFileURL to the filename
 // and with no parameters, it returns an nsIFileURL to the root of the addon
 
 function run_test() {
   do_test_pending();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
@@ -63,16 +63,23 @@ function run_test() {
         do_check_eq(uri.spec, file);
 
         do_check_false(a1.hasResource("subdir/missing.txt"));
 
         do_check_eq(a1.size, ADDON_SIZE);
 
         a1.uninstall();
 
+        try {
+          // hasResource should never throw an exception.
+          do_check_false(a1.hasResource("icon.png"));
+        } catch (e) {
+          do_check_true(false);
+        }
+
         restartManager();
 
         AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) {
           do_check_eq(newa1, null);
 
           do_test_finished();
         });
       });