Merge m-c to s-c
authorNick Alexander <nalexander@mozilla.com>
Tue, 02 Jul 2013 14:59:21 -0700
changeset 137196 50901fae80b0747b23257137d5b4cf82b936b54e
parent 137195 6d814b7c4af08c2a5db8baa6b655009ab50f510e (current diff)
parent 137188 7469440b076bb5ed98e5b602bd9b20ed939417ee (diff)
child 137197 c193fdeb4932c8ba65fea39f43644f338d2f42bf
push id1823
push userryanvm@gmail.com
push dateWed, 03 Jul 2013 13:15:22 +0000
treeherderfx-team@2cae857c17cb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone25.0a1
Merge m-c to s-c
browser/metro/base/content/pages/aboutCrash.xhtml
configure.in
modules/freetype2/src/sfnt/ttsbit0.c
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -777,18 +777,19 @@ HyperTextAccessible::GetRelativeOffset(n
       -- hyperTextOffset;
     }
   }
 
   return hyperTextOffset;
 }
 
 int32_t
-HyperTextAccessible::FindWordBoundary(int32_t aOffset, nsDirection aDirection,
-                                      EWordMovementType aWordMovementType)
+HyperTextAccessible::FindBoundary(int32_t aOffset, nsDirection aDirection,
+                                  nsSelectionAmount aAmount,
+                                  EWordMovementType aWordMovementType)
 {
   // Convert hypertext offset to frame-relative offset.
   int32_t offsetInFrame = aOffset, notUsedOffset = aOffset;
   nsRefPtr<Accessible> accAtOffset;
   nsIFrame* frameAtOffset =
     GetPosAndText(offsetInFrame, notUsedOffset, nullptr, nullptr,
                   nullptr, getter_AddRefs(accAtOffset));
   if (!frameAtOffset) {
@@ -802,18 +803,18 @@ HyperTextAccessible::FindWordBoundary(in
       return -1;
 
     // We're on the last continuation since we're on the last character.
     frameAtOffset = frameAtOffset->GetLastContinuation();
   }
 
   // Return hypertext offset of the boundary of the found word.
   return GetRelativeOffset(mDoc->PresShell(), frameAtOffset, offsetInFrame,
-                           accAtOffset, eSelectWord, aDirection,
-                           (aWordMovementType == eStartWord),
+                           accAtOffset, aAmount, aDirection,
+                           (aWordMovementType == eStartWord || aAmount == eSelectBeginLine),
                            aWordMovementType);
 }
 
 /*
 Gets the specified text relative to aBoundaryType, which means:
 BOUNDARY_CHAR             The character before/at/after the offset is returned.
 BOUNDARY_WORD_START       From the word start before/at/after the offset to the next word start.
 BOUNDARY_WORD_END         From the word end before/at/after the offset to the next work end.
@@ -1075,18 +1076,39 @@ HyperTextAccessible::GetTextAtOffset(int
     case BOUNDARY_WORD_END:
       // Ignore the spec and follow what WebKitGtk does because Orca expects it,
       // i.e. return a next word at word end offset of the current word
       // (WebKitGtk behavior) instead the current word (AKT spec).
       *aEndOffset = FindWordBoundary(offset, eDirNext, eEndWord);
       *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
       return GetText(*aStartOffset, *aEndOffset, aText);
 
-    case BOUNDARY_LINE_START:
-    case BOUNDARY_LINE_END:
+    case BOUNDARY_LINE_START: {
+      // Home key, arrow down and if not on last line then home key.
+      *aStartOffset = FindLineBoundary(offset, eDirPrevious, eSelectBeginLine);
+      *aEndOffset = FindLineBoundary(offset, eDirNext, eSelectLine);
+      int32_t tmpOffset = FindLineBoundary(*aEndOffset, eDirPrevious, eSelectBeginLine);
+      if (tmpOffset != *aStartOffset)
+        *aEndOffset = tmpOffset;
+
+      return GetText(*aStartOffset, *aEndOffset, aText);
+    }
+
+    case BOUNDARY_LINE_END: {
+      // In contrast to word end boundary we follow the spec here. End key,
+      // then up arrow and if not on first line then end key.
+      *aEndOffset = FindLineBoundary(offset, eDirNext, eSelectEndLine);
+      int32_t tmpOffset = FindLineBoundary(offset, eDirPrevious, eSelectLine);
+      *aStartOffset = FindLineBoundary(tmpOffset, eDirNext, eSelectEndLine);
+      if (*aStartOffset == *aEndOffset)
+        *aStartOffset = 0;
+
+      return GetText(*aStartOffset, *aEndOffset, aText);
+    }
+
     case BOUNDARY_ATTRIBUTE_RANGE:
       return GetTextHelper(eGetAt, aBoundaryType, aOffset,
                            aStartOffset, aEndOffset, aText);
 
     default:
       return NS_ERROR_INVALID_ARG;
   }
 }
--- a/accessible/src/generic/HyperTextAccessible.h
+++ b/accessible/src/generic/HyperTextAccessible.h
@@ -262,17 +262,36 @@ protected:
 
     return aOffset;
   }
 
   /**
    * Return an offset of the found word boundary.
    */
   int32_t FindWordBoundary(int32_t aOffset, nsDirection aDirection,
-                           EWordMovementType aWordMovementType);
+                           EWordMovementType aWordMovementType)
+  {
+    return FindBoundary(aOffset, aDirection, eSelectWord, aWordMovementType);
+  }
+
+  /**
+   * Return an offset of the found line boundary.
+   */
+  int32_t FindLineBoundary(int32_t aOffset, nsDirection aDirection,
+                           nsSelectionAmount aAmount)
+  {
+    return FindBoundary(aOffset, aDirection, aAmount, eDefaultBehavior);
+  }
+
+  /**
+   * Return an offset of the found word or line boundary. Helper.
+   */
+  int32_t FindBoundary(int32_t aOffset, nsDirection aDirection,
+                       nsSelectionAmount aAmount,
+                       EWordMovementType aWordMovementType = eDefaultBehavior);
 
   /*
    * This does the work for nsIAccessibleText::GetText[At|Before|After]Offset
    * @param aType, eGetBefore, eGetAt, eGetAfter
    * @param aBoundaryType, char/word-start/word-end/line-start/line-end/paragraph/attribute
    * @param aOffset, offset into the hypertext to start from
    * @param *aStartOffset, the resulting start offset for the returned substring
    * @param *aEndOffset, the resulting end offset for the returned substring
--- a/accessible/src/jsat/EventManager.jsm
+++ b/accessible/src/jsat/EventManager.jsm
@@ -2,16 +2,28 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
+const EVENT_VIRTUALCURSOR_CHANGED = Ci.nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
+const EVENT_STATE_CHANGE = Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE;
+const EVENT_SCROLLING_START = Ci.nsIAccessibleEvent.EVENT_SCROLLING_START;
+const EVENT_TEXT_CARET_MOVED = Ci.nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
+const EVENT_TEXT_INSERTED = Ci.nsIAccessibleEvent.EVENT_TEXT_INSERTED;
+const EVENT_TEXT_REMOVED = Ci.nsIAccessibleEvent.EVENT_TEXT_REMOVED;
+const EVENT_FOCUS = Ci.nsIAccessibleEvent.EVENT_FOCUS;
+
+const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
+const ROLE_DOCUMENT = Ci.nsIAccessibleRole.ROLE_DOCUMENT;
+const ROLE_CHROME_WINDOW = Ci.nsIAccessibleRole.ROLE_CHROME_WINDOW;
+
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Services',
   'resource://gre/modules/Services.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
@@ -122,60 +134,60 @@ this.EventManager.prototype = {
 
   handleAccEvent: function handleAccEvent(aEvent) {
     if (Logger.logLevel >= Logger.DEBUG)
       Logger.debug('A11yEvent', Logger.eventToString(aEvent),
                    Logger.accessibleToString(aEvent.accessible));
 
     // Don't bother with non-content events in firefox.
     if (Utils.MozBuildApp == 'browser' &&
-        aEvent.eventType != Ci.nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED &&
+        aEvent.eventType != EVENT_VIRTUALCURSOR_CHANGED &&
         aEvent.accessibleDocument != Utils.CurrentContentDoc) {
       return;
     }
 
     switch (aEvent.eventType) {
-      case Ci.nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED:
+      case EVENT_VIRTUALCURSOR_CHANGED:
       {
         let pivot = aEvent.accessible.
           QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
         let position = pivot.position;
-        if (position && position.role == Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME)
+        if (position && position.role == ROLE_INTERNAL_FRAME)
           break;
         let event = aEvent.
           QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
         let reason = event.reason;
 
         if (this.editState.editing)
           aEvent.accessibleDocument.takeFocus();
 
         this.present(
           Presentation.pivotChanged(position, event.oldAccessible, reason));
 
         break;
       }
-      case Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE:
+      case EVENT_STATE_CHANGE:
       {
         let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
         if (event.state == Ci.nsIAccessibleStates.STATE_CHECKED &&
             !(event.isExtraState)) {
           this.present(
             Presentation.
               actionInvoked(aEvent.accessible,
                             event.isEnabled ? 'check' : 'uncheck'));
         }
         break;
       }
-      case Ci.nsIAccessibleEvent.EVENT_SCROLLING_START:
+      case EVENT_SCROLLING_START:
       {
         let vc = Utils.getVirtualCursor(aEvent.accessibleDocument);
         vc.moveNext(TraversalRules.Simple, aEvent.accessible, true);
         break;
       }
-      case Ci.nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED:
+      case EVENT_TEXT_CARET_MOVED:
       {
         let acc = aEvent.accessible;
         let characterCount = acc.
           QueryInterface(Ci.nsIAccessibleText).characterCount;
         let caretOffset = aEvent.
           QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset;
 
         // Update editing state, both for presenter and other things
@@ -198,18 +210,18 @@ this.EventManager.prototype = {
             editState.multiline != this.editState.multiline ||
             editState.atEnd != this.editState.atEnd ||
             editState.atStart != this.editState.atStart)
           this.sendMsgFunc("AccessFu:Input", editState);
 
         this.editState = editState;
         break;
       }
-      case Ci.nsIAccessibleEvent.EVENT_TEXT_INSERTED:
-      case Ci.nsIAccessibleEvent.EVENT_TEXT_REMOVED:
+      case EVENT_TEXT_INSERTED:
+      case EVENT_TEXT_REMOVED:
       {
         if (aEvent.isFromUserInput) {
           // XXX support live regions as well.
           let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
           let isInserted = event.isInserted;
           let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
 
           let text = '';
@@ -223,23 +235,22 @@ this.EventManager.prototype = {
               throw x;
           }
           this.present(Presentation.textChanged(
                          isInserted, event.start, event.length,
                          text, event.modifiedText));
         }
         break;
       }
-      case Ci.nsIAccessibleEvent.EVENT_FOCUS:
+      case EVENT_FOCUS:
       {
         // Put vc where the focus is at
         let acc = aEvent.accessible;
         let doc = aEvent.accessibleDocument;
-        if (acc.role != Ci.nsIAccessibleRole.ROLE_DOCUMENT &&
-            doc.role != Ci.nsIAccessibleRole.ROLE_CHROME_WINDOW) {
+        if (acc.role != ROLE_DOCUMENT && doc.role != ROLE_CHROME_WINDOW) {
           let vc = Utils.getVirtualCursor(doc);
           vc.moveNext(TraversalRules.Simple, acc, true);
         }
         break;
       }
     }
   },
 
--- a/accessible/src/jsat/OutputGenerator.jsm
+++ b/accessible/src/jsat/OutputGenerator.jsm
@@ -12,16 +12,20 @@ const Cr = Components.results;
 const INCLUDE_DESC = 0x01;
 const INCLUDE_NAME = 0x02;
 const INCLUDE_CUSTOM = 0x04;
 const NAME_FROM_SUBTREE_RULE = 0x08;
 
 const OUTPUT_DESC_FIRST = 0;
 const OUTPUT_DESC_LAST = 1;
 
+const ROLE_LISTITEM = Ci.nsIAccessibleRole.ROLE_LISTITEM;
+const ROLE_STATICTEXT = Ci.nsIAccessibleRole.ROLE_STATICTEXT;
+const ROLE_LINK = Ci.nsIAccessibleRole.ROLE_LINK;
+
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'PluralForm',
@@ -561,18 +565,18 @@ this.BrailleGenerator = {
   objectOutputFunctions: {
 
     __proto__: OutputGenerator.objectOutputFunctions,
 
     defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
       let braille = this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
 
       if (aAccessible.indexInParent === 1 &&
-          aAccessible.parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM &&
-          aAccessible.previousSibling.role == Ci.nsIAccessibleRole.ROLE_STATICTEXT) {
+          aAccessible.parent.role == ROLE_LISTITEM &&
+          aAccessible.previousSibling.role == ROLE_STATICTEXT) {
         if (aAccessible.parent.parent && aAccessible.parent.parent.DOMNode &&
             aAccessible.parent.parent.DOMNode.nodeName == 'UL') {
           braille.unshift('*');
         } else {
           braille.unshift(aAccessible.previousSibling.name);
         }
       }
 
@@ -617,17 +621,17 @@ this.BrailleGenerator = {
 
     rowheader: function rowheader() {
       return this.objectOutputFunctions.cell.apply(this, arguments);
     },
 
     statictext: function statictext(aAccessible, aRoleStr, aStates, aFlags) {
       // Since we customize the list bullet's output, we add the static
       // text from the first node in each listitem, so skip it here.
-      if (aAccessible.parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM) {
+      if (aAccessible.parent.role == ROLE_LISTITEM) {
         return [];
       }
 
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     },
 
     _useStateNotRole: function _useStateNotRole(aAccessible, aRoleStr, aStates, aFlags) {
       let braille = [];
@@ -649,17 +653,17 @@ this.BrailleGenerator = {
     },
 
     togglebutton: function radiobutton(aAccessible, aRoleStr, aStates, aFlags) {
       return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
     }
   },
 
   _getContextStart: function _getContextStart(aContext) {
-    if (aContext.accessible.parent.role == Ci.nsIAccessibleRole.ROLE_LINK) {
+    if (aContext.accessible.parent.role == ROLE_LINK) {
       return [aContext.accessible.parent];
     }
 
     return [];
   },
 
   _getOutputName: function _getOutputName(aName) {
     return OutputGenerator._getOutputName(aName) + 'Abbr';
--- a/accessible/src/jsat/TraversalRules.jsm
+++ b/accessible/src/jsat/TraversalRules.jsm
@@ -8,16 +8,47 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 const FILTER_IGNORE = Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
 const FILTER_MATCH = Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
 const FILTER_IGNORE_SUBTREE = Ci.nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
 
+const ROLE_MENUITEM = Ci.nsIAccessibleRole.ROLE_MENUITEM;
+const ROLE_LINK = Ci.nsIAccessibleRole.ROLE_LINK;
+const ROLE_PAGETAB = Ci.nsIAccessibleRole.ROLE_PAGETAB;
+const ROLE_GRAPHIC = Ci.nsIAccessibleRole.ROLE_GRAPHIC;
+const ROLE_STATICTEXT = Ci.nsIAccessibleRole.ROLE_STATICTEXT;
+const ROLE_TEXT_LEAF = Ci.nsIAccessibleRole.ROLE_TEXT_LEAF;
+const ROLE_PUSHBUTTON = Ci.nsIAccessibleRole.ROLE_PUSHBUTTON;
+const ROLE_SPINBUTTON = Ci.nsIAccessibleRole.ROLE_SPINBUTTON;
+const ROLE_CHECKBUTTON = Ci.nsIAccessibleRole.ROLE_CHECKBUTTON;
+const ROLE_RADIOBUTTON = Ci.nsIAccessibleRole.ROLE_RADIOBUTTON;
+const ROLE_COMBOBOX = Ci.nsIAccessibleRole.ROLE_COMBOBOX;
+const ROLE_PROGRESSBAR = Ci.nsIAccessibleRole.ROLE_PROGRESSBAR;
+const ROLE_BUTTONDROPDOWN = Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWN;
+const ROLE_BUTTONMENU = Ci.nsIAccessibleRole.ROLE_BUTTONMENU;
+const ROLE_CHECK_MENU_ITEM = Ci.nsIAccessibleRole.ROLE_CHECK_MENU_ITEM;
+const ROLE_PASSWORD_TEXT = Ci.nsIAccessibleRole.ROLE_PASSWORD_TEXT;
+const ROLE_RADIO_MENU_ITEM = Ci.nsIAccessibleRole.ROLE_RADIO_MENU_ITEM;
+const ROLE_TOGGLE_BUTTON = Ci.nsIAccessibleRole.ROLE_TOGGLE_BUTTON;
+const ROLE_ENTRY = Ci.nsIAccessibleRole.ROLE_ENTRY;
+const ROLE_LIST = Ci.nsIAccessibleRole.ROLE_LIST;
+const ROLE_DEFINITION_LIST = Ci.nsIAccessibleRole.ROLE_DEFINITION_LIST;
+const ROLE_LISTITEM = Ci.nsIAccessibleRole.ROLE_LISTITEM;
+const ROLE_BUTTONDROPDOWNGRID = Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID;
+const ROLE_LISTBOX = Ci.nsIAccessibleRole.ROLE_LISTBOX;
+const ROLE_SLIDER = Ci.nsIAccessibleRole.ROLE_SLIDER;
+const ROLE_HEADING = Ci.nsIAccessibleRole.ROLE_HEADING;
+const ROLE_TERM = Ci.nsIAccessibleRole.ROLE_TERM;
+const ROLE_SEPARATOR = Ci.nsIAccessibleRole.ROLE_SEPARATOR;
+const ROLE_TABLE = Ci.nsIAccessibleRole.ROLE_TABLE;
+const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
+
 this.EXPORTED_SYMBOLS = ['TraversalRules'];
 
 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
 let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
 
 function BaseTraversalRule(aRoles, aMatchFunc) {
@@ -32,88 +63,88 @@ BaseTraversalRule.prototype = {
     },
 
     preFilter: Ci.nsIAccessibleTraversalRule.PREFILTER_DEFUNCT |
     Ci.nsIAccessibleTraversalRule.PREFILTER_INVISIBLE |
     Ci.nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN,
 
     match: function BaseTraversalRule_match(aAccessible)
     {
-      if (aAccessible.role == Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME) {
+      if (aAccessible.role == ROLE_INTERNAL_FRAME) {
         return (Utils.getMessageManager(aAccessible.DOMNode)) ?
           FILTER_MATCH  | FILTER_IGNORE_SUBTREE : FILTER_IGNORE;
       }
 
       if (this._matchFunc)
         return this._matchFunc(aAccessible);
 
       return FILTER_MATCH;
     },
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessibleTraversalRule])
 };
 
 var gSimpleTraversalRoles =
-  [Ci.nsIAccessibleRole.ROLE_MENUITEM,
-   Ci.nsIAccessibleRole.ROLE_LINK,
-   Ci.nsIAccessibleRole.ROLE_PAGETAB,
-   Ci.nsIAccessibleRole.ROLE_GRAPHIC,
-   Ci.nsIAccessibleRole.ROLE_STATICTEXT,
-   Ci.nsIAccessibleRole.ROLE_TEXT_LEAF,
-   Ci.nsIAccessibleRole.ROLE_PUSHBUTTON,
-   Ci.nsIAccessibleRole.ROLE_CHECKBUTTON,
-   Ci.nsIAccessibleRole.ROLE_RADIOBUTTON,
-   Ci.nsIAccessibleRole.ROLE_COMBOBOX,
-   Ci.nsIAccessibleRole.ROLE_PROGRESSBAR,
-   Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWN,
-   Ci.nsIAccessibleRole.ROLE_BUTTONMENU,
-   Ci.nsIAccessibleRole.ROLE_CHECK_MENU_ITEM,
-   Ci.nsIAccessibleRole.ROLE_PASSWORD_TEXT,
-   Ci.nsIAccessibleRole.ROLE_RADIO_MENU_ITEM,
-   Ci.nsIAccessibleRole.ROLE_TOGGLE_BUTTON,
-   Ci.nsIAccessibleRole.ROLE_ENTRY,
+  [ROLE_MENUITEM,
+   ROLE_LINK,
+   ROLE_PAGETAB,
+   ROLE_GRAPHIC,
+   ROLE_STATICTEXT,
+   ROLE_TEXT_LEAF,
+   ROLE_PUSHBUTTON,
+   ROLE_CHECKBUTTON,
+   ROLE_RADIOBUTTON,
+   ROLE_COMBOBOX,
+   ROLE_PROGRESSBAR,
+   ROLE_BUTTONDROPDOWN,
+   ROLE_BUTTONMENU,
+   ROLE_CHECK_MENU_ITEM,
+   ROLE_PASSWORD_TEXT,
+   ROLE_RADIO_MENU_ITEM,
+   ROLE_TOGGLE_BUTTON,
+   ROLE_ENTRY,
    // Used for traversing in to child OOP frames.
-   Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME];
+   ROLE_INTERNAL_FRAME];
 
 this.TraversalRules = {
   Simple: new BaseTraversalRule(
     gSimpleTraversalRoles,
     function Simple_match(aAccessible) {
       switch (aAccessible.role) {
-      case Ci.nsIAccessibleRole.ROLE_COMBOBOX:
+      case ROLE_COMBOBOX:
         // We don't want to ignore the subtree because this is often
         // where the list box hangs out.
         return FILTER_MATCH;
-      case Ci.nsIAccessibleRole.ROLE_TEXT_LEAF:
+      case ROLE_TEXT_LEAF:
         {
           // Nameless text leaves are boring, skip them.
           let name = aAccessible.name;
           if (name && name.trim())
             return FILTER_MATCH;
           else
             return FILTER_IGNORE;
         }
-      case Ci.nsIAccessibleRole.ROLE_LINK:
+      case ROLE_LINK:
         // If the link has children we should land on them instead.
         // Image map links don't have children so we need to match those.
         if (aAccessible.childCount == 0)
           return FILTER_MATCH;
         else
           return FILTER_IGNORE;
-      case Ci.nsIAccessibleRole.ROLE_STATICTEXT:
+      case ROLE_STATICTEXT:
         {
           let parent = aAccessible.parent;
           // Ignore prefix static text in list items. They are typically bullets or numbers.
           if (parent.childCount > 1 && aAccessible.indexInParent == 0 &&
-              parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM)
+              parent.role == ROLE_LISTITEM)
             return FILTER_IGNORE;
 
           return FILTER_MATCH;
         }
-      case Ci.nsIAccessibleRole.ROLE_GRAPHIC:
+      case ROLE_GRAPHIC:
         return TraversalRules._shouldSkipImage(aAccessible);
       default:
         // Ignore the subtree, if there is one. So that we don't land on
         // the same content that was already presented by its parent.
         return FILTER_MATCH |
           FILTER_IGNORE_SUBTREE;
       }
     }
@@ -123,110 +154,110 @@ this.TraversalRules = {
     gSimpleTraversalRoles,
     function Simple_match(aAccessible) {
       return FILTER_MATCH |
         FILTER_IGNORE_SUBTREE;
     }
   ),
 
   Anchor: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_LINK],
+    [ROLE_LINK],
     function Anchor_match(aAccessible)
     {
       // We want to ignore links, only focus named anchors.
       let state = {};
       let extraState = {};
       aAccessible.getState(state, extraState);
       if (state.value & Ci.nsIAccessibleStates.STATE_LINKED) {
         return FILTER_IGNORE;
       } else {
         return FILTER_MATCH;
       }
     }),
 
   Button: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_PUSHBUTTON,
-     Ci.nsIAccessibleRole.ROLE_SPINBUTTON,
-     Ci.nsIAccessibleRole.ROLE_TOGGLE_BUTTON,
-     Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWN,
-     Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID]),
+    [ROLE_PUSHBUTTON,
+     ROLE_SPINBUTTON,
+     ROLE_TOGGLE_BUTTON,
+     ROLE_BUTTONDROPDOWN,
+     ROLE_BUTTONDROPDOWNGRID]),
 
   Combobox: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_COMBOBOX,
-     Ci.nsIAccessibleRole.ROLE_LISTBOX]),
+    [ROLE_COMBOBOX,
+     ROLE_LISTBOX]),
 
   Entry: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_ENTRY,
-     Ci.nsIAccessibleRole.ROLE_PASSWORD_TEXT]),
+    [ROLE_ENTRY,
+     ROLE_PASSWORD_TEXT]),
 
   FormElement: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_PUSHBUTTON,
-     Ci.nsIAccessibleRole.ROLE_SPINBUTTON,
-     Ci.nsIAccessibleRole.ROLE_TOGGLE_BUTTON,
-     Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWN,
-     Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID,
-     Ci.nsIAccessibleRole.ROLE_COMBOBOX,
-     Ci.nsIAccessibleRole.ROLE_LISTBOX,
-     Ci.nsIAccessibleRole.ROLE_ENTRY,
-     Ci.nsIAccessibleRole.ROLE_PASSWORD_TEXT,
-     Ci.nsIAccessibleRole.ROLE_PAGETAB,
-     Ci.nsIAccessibleRole.ROLE_RADIOBUTTON,
-     Ci.nsIAccessibleRole.ROLE_RADIO_MENU_ITEM,
-     Ci.nsIAccessibleRole.ROLE_SLIDER,
-     Ci.nsIAccessibleRole.ROLE_CHECKBUTTON,
-     Ci.nsIAccessibleRole.ROLE_CHECK_MENU_ITEM]),
+    [ROLE_PUSHBUTTON,
+     ROLE_SPINBUTTON,
+     ROLE_TOGGLE_BUTTON,
+     ROLE_BUTTONDROPDOWN,
+     ROLE_BUTTONDROPDOWNGRID,
+     ROLE_COMBOBOX,
+     ROLE_LISTBOX,
+     ROLE_ENTRY,
+     ROLE_PASSWORD_TEXT,
+     ROLE_PAGETAB,
+     ROLE_RADIOBUTTON,
+     ROLE_RADIO_MENU_ITEM,
+     ROLE_SLIDER,
+     ROLE_CHECKBUTTON,
+     ROLE_CHECK_MENU_ITEM]),
 
   Graphic: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_GRAPHIC],
+    [ROLE_GRAPHIC],
     function Graphic_match(aAccessible) {
       return TraversalRules._shouldSkipImage(aAccessible);
     }),
 
   Heading: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_HEADING]),
+    [ROLE_HEADING]),
 
   ListItem: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_LISTITEM,
-     Ci.nsIAccessibleRole.ROLE_TERM]),
+    [ROLE_LISTITEM,
+     ROLE_TERM]),
 
   Link: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_LINK],
+    [ROLE_LINK],
     function Link_match(aAccessible)
     {
       // We want to ignore anchors, only focus real links.
       let state = {};
       let extraState = {};
       aAccessible.getState(state, extraState);
       if (state.value & Ci.nsIAccessibleStates.STATE_LINKED) {
         return FILTER_MATCH;
       } else {
         return FILTER_IGNORE;
       }
     }),
 
   List: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_LIST,
-     Ci.nsIAccessibleRole.ROLE_DEFINITION_LIST]),
+    [ROLE_LIST,
+     ROLE_DEFINITION_LIST]),
 
   PageTab: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_PAGETAB]),
+    [ROLE_PAGETAB]),
 
   RadioButton: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_RADIOBUTTON,
-     Ci.nsIAccessibleRole.ROLE_RADIO_MENU_ITEM]),
+    [ROLE_RADIOBUTTON,
+     ROLE_RADIO_MENU_ITEM]),
 
   Separator: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_SEPARATOR]),
+    [ROLE_SEPARATOR]),
 
   Table: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_TABLE]),
+    [ROLE_TABLE]),
 
   Checkbox: new BaseTraversalRule(
-    [Ci.nsIAccessibleRole.ROLE_CHECKBUTTON,
-     Ci.nsIAccessibleRole.ROLE_CHECK_MENU_ITEM]),
+    [ROLE_CHECKBUTTON,
+     ROLE_CHECK_MENU_ITEM]),
 
   _shouldSkipImage: function _shouldSkipImage(aAccessible) {
     if (gSkipEmptyImages.value && aAccessible.name === '') {
       return FILTER_IGNORE;
     }
     return FILTER_MATCH;
   }
 };
--- a/accessible/src/jsat/Utils.jsm
+++ b/accessible/src/jsat/Utils.jsm
@@ -3,16 +3,22 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
+const EVENT_STATE_CHANGE = Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE;
+
+const ROLE_CELL = Ci.nsIAccessibleRole.ROLE_CELL;
+const ROLE_COLUMNHEADER = Ci.nsIAccessibleRole.ROLE_COLUMNHEADER;
+const ROLE_ROWHEADER = Ci.nsIAccessibleRole.ROLE_ROWHEADER;
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, 'Services',
   'resource://gre/modules/Services.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Rect',
   'resource://gre/modules/Geometry.jsm');
 
 this.EXPORTED_SYMBOLS = ['Utils', 'Logger', 'PivotContext', 'PrefCache'];
 
@@ -305,17 +311,17 @@ this.Logger = {
     } catch (x) {
     }
 
     return str;
   },
 
   eventToString: function eventToString(aEvent) {
     let str = Utils.AccRetrieval.getStringEventType(aEvent.eventType);
-    if (aEvent.eventType == Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE) {
+    if (aEvent.eventType == EVENT_STATE_CHANGE) {
       let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
       let stateStrings = event.isExtraState ?
         Utils.AccRetrieval.getStringStates(0, event.state) :
         Utils.AccRetrieval.getStringStates(event.state, 0);
       str += ' (' + stateStrings.item(0) + ')';
     }
 
     return str;
@@ -471,21 +477,19 @@ PivotContext.prototype = {
       return this._cells.get(domNode);
     }
 
     let cellInfo = {};
     let getAccessibleCell = function getAccessibleCell(aAccessible) {
       if (!aAccessible) {
         return null;
       }
-      if ([Ci.nsIAccessibleRole.ROLE_CELL,
-           Ci.nsIAccessibleRole.ROLE_COLUMNHEADER,
-           Ci.nsIAccessibleRole.ROLE_ROWHEADER].indexOf(
-             aAccessible.role) < 0) {
-        return null;
+      if ([ROLE_CELL, ROLE_COLUMNHEADER, ROLE_ROWHEADER].indexOf(
+        aAccessible.role) < 0) {
+          return null;
       }
       try {
         return aAccessible.QueryInterface(Ci.nsIAccessibleTableCell);
       } catch (x) {
         Logger.logException(x);
         return null;
       }
     };
@@ -534,23 +538,22 @@ PivotContext.prototype = {
 
     cellInfo.rowExtent = cellInfo.current.rowExtent;
     cellInfo.columnExtent = cellInfo.current.columnExtent;
     cellInfo.columnIndex = cellInfo.current.columnIndex;
     cellInfo.rowIndex = cellInfo.current.rowIndex;
 
     cellInfo.columnHeaders = [];
     if (cellInfo.columnChanged && cellInfo.current.role !==
-      Ci.nsIAccessibleRole.ROLE_COLUMNHEADER) {
+      ROLE_COLUMNHEADER) {
       cellInfo.columnHeaders = [headers for (headers of getHeaders(
         cellInfo.current.columnHeaderCells))];
     }
     cellInfo.rowHeaders = [];
-    if (cellInfo.rowChanged && cellInfo.current.role ===
-      Ci.nsIAccessibleRole.ROLE_CELL) {
+    if (cellInfo.rowChanged && cellInfo.current.role === ROLE_CELL) {
       cellInfo.rowHeaders = [headers for (headers of getHeaders(
         cellInfo.current.rowHeaderCells))];
     }
 
     this._cells.set(domNode, cellInfo);
     return cellInfo;
   },
 
--- a/accessible/src/jsat/content-script.js
+++ b/accessible/src/jsat/content-script.js
@@ -1,15 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
+const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
+
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
   'resource://gre/modules/accessibility/Presentation.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
   'resource://gre/modules/accessibility/TraversalRules.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
@@ -95,17 +97,17 @@ function virtualCursorControl(aMessage) 
   } catch (x) {
     Logger.logException(x, 'Failed to move virtual cursor');
   }
 }
 
 function forwardMessage(aVirtualCursor, aMessage) {
   try {
     let acc = aVirtualCursor.position;
-    if (acc && acc.role == Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME) {
+    if (acc && acc.role == ROLE_INTERNAL_FRAME) {
       let mm = Utils.getMessageManager(acc.DOMNode);
       mm.addMessageListener(aMessage.name, virtualCursorControl);
       aMessage.json.origin = 'parent';
       if (Utils.isContentProcess) {
         // XXX: OOP content's screen offset is 0,
         // so we remove the real screen offset here.
         aMessage.json.x -= content.mozInnerScreenX;
         aMessage.json.y -= content.mozInnerScreenY;
--- a/accessible/src/mac/Makefile.in
+++ b/accessible/src/mac/Makefile.in
@@ -7,17 +7,17 @@ topsrcdir = @top_srcdir@
 srcdir = @srcdir@
 VPATH = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 EXPORT_LIBRARY = ..
 LIBXUL_LIBRARY = 1
 
-CMMSRCS = \
+DISABLED_CMMSRCS = \
           AccessibleWrap.mm \
           DocAccessibleWrap.mm \
           mozAccessible.mm \
           mozDocAccessible.mm \
           mozActionElements.mm \
           mozTextAccessible.mm \
           mozHTMLAccessible.mm \
           MacUtils.mm \
--- a/accessible/src/mac/moz.build
+++ b/accessible/src/mac/moz.build
@@ -12,8 +12,20 @@ EXPORTS += [
 
 EXPORTS.mozilla.a11y += [
     'AccessibleWrap.h',
     'HyperTextAccessibleWrap.h',
 ]
 
 LIBRARY_NAME = 'accessibility_toolkit_s'
 
+CMMSRCS += [
+    'AccessibleWrap.mm',
+    'DocAccessibleWrap.mm',
+    'MacUtils.mm',
+    'Platform.mm',
+    'RootAccessibleWrap.mm',
+    'mozAccessible.mm',
+    'mozActionElements.mm',
+    'mozDocAccessible.mm',
+    'mozHTMLAccessible.mm',
+    'mozTextAccessible.mm',
+]
--- a/accessible/tests/mochitest/text/test_atcaretoffset.html
+++ b/accessible/tests/mochitest/text/test_atcaretoffset.html
@@ -45,17 +45,17 @@
 
         testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "", 15, 15,
                             "textarea", kOk, kTodo, kTodo);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "", 15, 15,
                          "textarea", kTodo, kTodo, kOk);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
-                         "textarea", kTodo, kTodo, kTodo);
+                         "textarea", kTodo, kTodo, kOk);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
                              "textarea", kOk, kOk, kOk);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
                              "textarea", kTodo, kTodo, kTodo);
       }
 
@@ -108,17 +108,17 @@
 
         testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
                             "textarea", kTodo, kTodo, kTodo);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
                          "textarea", kOk, kOk, kOk);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
-                         "textarea", kTodo, kOk, kTodo);
+                         "textarea", kTodo, kTodo, kTodo);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
                              "textarea", kTodo, kTodo, kOk);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
                              "textarea", kTodo, kTodo, kTodo);
       }
 
@@ -136,17 +136,17 @@
       {
         testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
                             "textarea", kTodo, kTodo, kTodo);
 
         testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
                             "textarea", kTodo, kTodo, kOk);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
-                         "textarea", kOk, kOk, kOk);
+                         "textarea", kTodo, kTodo, kTodo);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
                          "textarea", kTodo, kTodo, kTodo);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
                              "textarea", kTodo, kTodo, kTodo);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
@@ -167,17 +167,17 @@
       {
         testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
                             "textarea", kTodo, kTodo, kTodo);
 
         testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
                             "textarea", kTodo, kOk, kTodo);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
-                         "textarea", kTodo, kOk, kTodo);
+                         "textarea", kOk, kOk, kOk);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "", 0, 0,
                          "textarea", kTodo, kOk, kTodo);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "", 0, 0,
                              "textarea", kOk, kOk, kOk);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "", 0, 0,
@@ -198,20 +198,20 @@
       {
         testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
                             "textarea", kTodo, kTodo, kTodo);
 
         testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
                             "textarea", kTodo, kTodo, kTodo);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
-                         "textarea", kTodo, kOk, kTodo);
+                         "textarea", kOk, kOk, kOk);
 
         testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
-                         "textarea", kOk, kOk, kOk);
+                         "textarea", kTodo, kOk, kTodo);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "", 0, 0,
                              "textarea", kTodo, kOk, kTodo);
 
         testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "", 0, 0,
                              "textarea", kTodo, kOk, kTodo);
       }
 
@@ -219,17 +219,17 @@
       {
         return "move to first line end";
       }
     }
 
     var gQueue = null;
     function doTest()
     {
-      SimpleTest.expectAssertions(7);
+      SimpleTest.expectAssertions(6);
 
       gQueue = new eventQueue();
       gQueue.push(new moveToLastLineEnd());
       gQueue.push(new moveToLastLineStart());
       gQueue.push(new moveToMiddleLineStart());
       gQueue.push(new moveToMiddleLineEnd());
       gQueue.push(new moveToFirstLineStart());
       gQueue.push(new moveToFirstLineEnd());
--- a/accessible/tests/mochitest/text/test_multiline.html
+++ b/accessible/tests/mochitest/text/test_multiline.html
@@ -11,17 +11,17 @@
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../text.js"></script>
   <script type="application/javascript">
 
     function doTest()
     {
-      SimpleTest.expectAssertions(46);
+      SimpleTest.expectAssertions(38);
 
       // __o__n__e__w__o__r__d__\n
       //  0  1  2  3  4  5  6  7
       // __\n
       //  8
       // __t__w__o__ __w__o__r__d__s__\n
       //  9 10 11 12 13 14 15 16 17 18
 
@@ -175,96 +175,66 @@
                            "editable", kTodo, kTodo, kTodo,
                            "editablebr", kTodo, kTodo, kTodo,
                            "textarea", kTodo, kTodo, kTodo);
 
       ////////////////////////////////////////////////////////////////////////
       // getTextAtOffset
 
       // BOUNDARY_LINE_START
-      testTextAtOffset(0, BOUNDARY_LINE_START, "oneword\n", 0, 8,
-                       "div", kTodo, kOk, kTodo,
-                       "divbr", kOk, kOk, kOk,
-                       "editable", kTodo, kOk, kTodo,
-                       "editablebr", kOk, kOk, kOk,
-                       "textarea", kTodo, kOk, kTodo);
-      testTextAtOffset(7, BOUNDARY_LINE_START, "oneword\n", 0, 8,
-                       "div", kOk, kOk, kOk,
-                       "divbr", kOk, kOk, kOk,
-                       "editable", kOk, kOk, kOk,
-                       "editablebr", kOk, kOk, kOk,
-                       "textarea", kOk, kOk, kOk);
-      testTextAtOffset(8, BOUNDARY_LINE_START, "\n", 8, 9,
-                       "div", kOk, kOk, kOk,
-                       "divbr", kOk, kOk, kOk,
-                       "editable", kOk, kOk, kOk,
-                       "editablebr", kOk, kOk, kOk,
-                       "textarea", kOk, kOk, kOk);
-      testTextAtOffset(9, BOUNDARY_LINE_START, "two words\n", 9, 19,
-                       "div", kTodo, kOk, kTodo,
-                       "divbr", kOk, kOk, kOk,
-                       "editable", kTodo, kOk, kTodo,
-                       "editablebr", kOk, kOk, kOk,
-                       "textarea", kTodo, kOk, kTodo);
-      testTextAtOffset(13, BOUNDARY_LINE_START, "two words\n", 9, 19,
-                       "div", kTodo, kOk, kTodo,
-                       "divbr", kOk, kOk, kOk,
-                       "editable", kTodo, kOk, kTodo,
-                       "editablebr", kOk, kOk, kOk,
-                       "textarea", kTodo, kOk, kTodo);
-      testTextAtOffset(18, BOUNDARY_LINE_START, "two words\n", 9, 19,
-                       "div", kOk, kOk, kOk,
-                       "divbr", kOk, kOk, kOk,
-                       "editable", kOk, kOk, kOk,
-                       "editablebr", kOk, kOk, kOk,
-                       "textarea", kOk, kOk, kOk);
+      testTextAtOffset(0, BOUNDARY_LINE_START, "oneword\n", 0, 8, IDs);
+      testTextAtOffset(7, BOUNDARY_LINE_START, "oneword\n", 0, 8, IDs);
+      testTextAtOffset(8, BOUNDARY_LINE_START, "\n", 8, 9, IDs);
+      testTextAtOffset(9, BOUNDARY_LINE_START, "two words\n", 9, 19, IDs);
+      testTextAtOffset(13, BOUNDARY_LINE_START, "two words\n", 9, 19, IDs);
+      testTextAtOffset(18, BOUNDARY_LINE_START, "two words\n", 9, 19, IDs);
       testTextAtOffset(19, BOUNDARY_LINE_START, "", 19, 19,
-                       "div", kTodo, kTodo, kTodo,
+                       "div", kTodo, kTodo, kOk,
                        "divbr", kTodo, kTodo, kOk,
-                       "editable", kTodo, kTodo, kTodo,
+                       "editable", kTodo, kTodo, kOk,
                        "editablebr", kTodo, kTodo, kOk,
-                       "textarea", kTodo, kTodo, kTodo);
+                       "textarea", kTodo, kTodo, kOk);
 
       // BOUNDARY_LINE_END
       testTextAtOffset(0, BOUNDARY_LINE_END, "oneword", 0, 7,
                        "div", kTodo, kOk, kTodo,
                        "divbr", kOk, kOk, kOk,
                        "editable", kTodo, kOk, kTodo,
                        "editablebr", kOk, kOk, kOk,
                        "textarea", kTodo, kOk, kTodo);
       testTextAtOffset(7, BOUNDARY_LINE_END, "oneword", 0, 7,
-                       "div", kTodo, kTodo, kTodo,
-                       "divbr", kTodo, kTodo, kTodo,
-                       "editable", kTodo, kTodo, kTodo,
-                       "editablebr", kTodo, kTodo, kTodo,
-                       "textarea", kTodo, kTodo, kTodo);
-      testTextAtOffset(8, BOUNDARY_LINE_END, "\n", 7, 8,
-                       "div", kTodo, kTodo, kTodo,
-                       "divbr", kTodo, kTodo, kTodo,
-                       "editable", kTodo, kTodo, kTodo,
-                       "editablebr", kTodo, kTodo, kTodo,
-                       "textarea", kTodo, kTodo, kTodo);
-      testTextAtOffset(9, BOUNDARY_LINE_END, "\ntwo words", 8, 18,
                        "div", kTodo, kOk, kTodo,
                        "divbr", kOk, kOk, kOk,
                        "editable", kTodo, kOk, kTodo,
                        "editablebr", kOk, kOk, kOk,
                        "textarea", kTodo, kOk, kTodo);
-      testTextAtOffset(17, BOUNDARY_LINE_END, "\ntwo words", 8, 18,
-                       "div", kOk, kOk, kOk,
+      testTextAtOffset(8, BOUNDARY_LINE_END, "\n", 7, 8,
+                       "div", kTodo, kTodo, kTodo,
                        "divbr", kOk, kOk, kOk,
-                       "editable", kOk, kOk, kOk,
+                       "editable", kTodo, kTodo, kTodo,
+                       "editablebr", kOk, kOk, kOk,
+                       "textarea", kTodo, kTodo, kTodo);
+      testTextAtOffset(9, BOUNDARY_LINE_END, "\ntwo words", 8, 18,
+                       "div", kTodo, kTodo, kTodo,
+                       "divbr", kOk, kOk, kOk,
+                       "editable", kTodo, kTodo, kTodo,
                        "editablebr", kOk, kOk, kOk,
-                       "textarea", kOk, kOk, kOk);
+                       "textarea", kTodo, kTodo, kTodo);
+      testTextAtOffset(17, BOUNDARY_LINE_END, "\ntwo words", 8, 18,
+                       "div", kTodo, kTodo, kTodo,
+                       "divbr", kOk, kOk, kOk,
+                       "editable", kTodo, kTodo, kTodo,
+                       "editablebr", kOk, kOk, kOk,
+                       "textarea", kTodo, kTodo, kTodo);
       testTextAtOffset(18, BOUNDARY_LINE_END, "\ntwo words", 8, 18,
-                       "div", kTodo, kOk, kTodo,
-                       "divbr", kTodo, kOk, kTodo,
-                       "editable", kTodo, kOk, kTodo,
-                       "editablebr", kTodo, kOk, kTodo,
-                       "textarea", kTodo, kOk, kTodo);
+                       "div", kTodo, kTodo, kTodo,
+                       "divbr", kOk, kOk, kOk,
+                       "editable", kTodo, kTodo, kTodo,
+                       "editablebr", kOk, kOk, kOk,
+                       "textarea", kTodo, kTodo, kTodo);
       testTextAtOffset(19, BOUNDARY_LINE_END, "\n", 18, 19,
                        "div", kTodo, kTodo, kTodo,
                        "divbr", kTodo, kTodo, kTodo,
                        "editable", kTodo, kTodo, kTodo,
                        "editablebr", kTodo, kTodo, kTodo,
                        "textarea", kTodo, kTodo, kTodo);
 
       SimpleTest.finish();
--- a/accessible/tests/mochitest/text/test_singleline.html
+++ b/accessible/tests/mochitest/text/test_singleline.html
@@ -7,19 +7,19 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../text.js"></script>
   <script type="application/javascript">
     if (navigator.platform.startsWith("Mac")) {
-      SimpleTest.expectAssertions(0, 16);
+      SimpleTest.expectAssertions(0, 12);
     } else {
-      SimpleTest.expectAssertions(16);
+      SimpleTest.expectAssertions(12);
     }
 
     function doTest()
     {
       // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
       //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
 
       ////////////////////////////////////////////////////////////////////////
@@ -125,36 +125,20 @@
 
       // BOUNDARY_LINE_START
       testTextAtOffset(0, BOUNDARY_LINE_START, "hello my friend", 0, 15, IDs);
       testTextAtOffset(1, BOUNDARY_LINE_START, "hello my friend", 0, 15, IDs);
       testTextAtOffset(14, BOUNDARY_LINE_START, "hello my friend", 0, 15, IDs);
       testTextAtOffset(15, BOUNDARY_LINE_START, "hello my friend", 0, 15, IDs);
 
       // BOUNDARY_LINE_END
-      testTextAtOffset(0, BOUNDARY_LINE_END, "hello my friend", 0, 15,
-		       "input", kOk, kOk, kOk,
-		       "div", kOk, kOk, kOk,
-		       "editable", kOk, kOk, kOk,
-		       "textarea", kOk, kOk, kOk);
-      testTextAtOffset(1, BOUNDARY_LINE_END, "hello my friend", 0, 15,
-		       "input", kOk, kOk, kOk,
-		       "div", kOk, kOk, kOk,
-		       "editable", kOk, kOk, kOk,
-		       "textarea", kOk, kOk, kOk);
-      testTextAtOffset(14, BOUNDARY_LINE_END, "hello my friend", 0, 15,
-		       "input", kOk, kOk, kOk,
-		       "div", kOk, kOk, kOk,
-		       "editable", kOk, kOk, kOk,
-		       "textarea", kOk, kOk, kOk);
-      testTextAtOffset(15, BOUNDARY_LINE_END, "hello my friend", 0, 15,
-		       "input", kTodo, kOk, kTodo,
-		       "div", kTodo, kOk, kTodo,
-		       "editable", kTodo, kOk, kTodo,
-		       "textarea", kTodo, kOk, kTodo);
+      testTextAtOffset(0, BOUNDARY_LINE_END, "hello my friend", 0, 15, IDs);
+      testTextAtOffset(1, BOUNDARY_LINE_END, "hello my friend", 0, 15, IDs);
+      testTextAtOffset(14, BOUNDARY_LINE_END, "hello my friend", 0, 15, IDs);
+      testTextAtOffset(15, BOUNDARY_LINE_END, "hello my friend", 0, 15, IDs);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
@@ -179,11 +163,12 @@
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <input id="input" value="hello my friend"/>
   <div id="div">hello my friend</div>
   <div id="editable" contenteditable="true">hello my friend</div>
   <textarea id="textarea">hello my friend</textarea>
+  <div>XXX: a hack to make text areas/inputs working</div>
 
 </body>
 </html>
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -477,19 +477,16 @@ var shell = {
       case 'mozbrowserloadstart':
         if (content.document.location == 'about:blank')
           return;
 
         this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
 
         this.reportCrash(true);
 
-        let chromeWindow = window.QueryInterface(Ci.nsIDOMChromeWindow);
-        chromeWindow.browserDOMWindow = new nsBrowserAccess();
-
         Cu.import('resource://gre/modules/Webapps.jsm');
         DOMApplicationRegistry.allAppsLaunchable = true;
 
         this.sendEvent(window, 'ContentStart');
 
         content.addEventListener('load', function shell_homeLoaded() {
           content.removeEventListener('load', shell_homeLoaded);
           shell.isHomeLoaded = true;
@@ -603,47 +600,16 @@ var shell = {
       a.onerror = function() {
         let sender = message.target.QueryInterface(Ci.nsIMessageSender);
         sender.sendAsyncMessage(activity.response, { success: false });
       }
     }
   }
 };
 
-function nsBrowserAccess() {
-}
-
-nsBrowserAccess.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]),
-
-  openURI: function openURI(uri, opener, where, context) {
-    // TODO This should be replaced by an 'open-browser-window' intent
-    let content = shell.contentBrowser.contentWindow;
-    let contentWindow = content.wrappedJSObject;
-    if (!('getApplicationManager' in contentWindow))
-      return null;
-
-    let applicationManager = contentWindow.getApplicationManager();
-    if (!applicationManager)
-      return null;
-
-    let url = uri ? uri.spec : 'about:blank';
-    let window = applicationManager.launch(url, where);
-    return window.contentWindow;
-  },
-
-  openURIInFrame: function openURIInFrame(uri, opener, where, context) {
-    throw new Error('Not Implemented');
-  },
-
-  isTabContentWindow: function isTabContentWindow(contentWindow) {
-    return contentWindow == window;
-  }
-};
-
 // Listen for the request of opening app and relay them to Gaia.
 Services.obs.addObserver(function onSystemMessageOpenApp(subject, topic, data) {
   let msg = JSON.parse(data);
   // Buffer non-activity request until content starts to load for 10 seconds.
   // We'll revisit this later if new kind of requests don't need to be cached.
   if (shell.needBufferOpenAppReq && msg.type !== 'activity') {
     shell.bufferedOpenAppReqs.push(msg);
     return;
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "b781d1e14fd8e5cea74943195b7f35e807212f34", 
+    "revision": "aa2108bb5f5478c4f3a7ce5a2f4c534b60045c95", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -5,17 +5,17 @@
 MOZ_APP_BASENAME=B2G
 MOZ_APP_VENDOR=Mozilla
 
 MOZ_APP_VERSION=25.0a1
 MOZ_APP_UA_NAME=Firefox
 
 MOZ_UA_OS_AGNOSTIC=1
 
-MOZ_B2G_VERSION=2.0.0.0-prerelease
+MOZ_B2G_VERSION=1.2.0.0-prerelease
 MOZ_B2G_OS_NAME=Boot2Gecko
 
 MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
 # MOZ_APP_DISPLAYNAME is set by branding/configure.sh
 
 MOZ_SAFE_BROWSING=
 MOZ_SERVICES_COMMON=1
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -385,17 +385,17 @@ window[chromehidden~="toolbar"] toolbar:
   background-image: -moz-element(#historySwipeAnimationNextPageSnapshot);
 }
 
 /* Identity UI */
 #identity-popup-content-box:not(.chromeUI) > #identity-popup-brandName,
 #identity-popup-content-box:not(.chromeUI) > #identity-popup-chromeLabel,
 #identity-popup-content-box.chromeUI > .identity-popup-label:not(#identity-popup-brandName):not(#identity-popup-chromeLabel),
 #identity-popup-content-box.chromeUI > .identity-popup-description,
-#identity-popup-content-box.chromeUI > #identity-popup-button-container,
+#identity-popup.chromeUI > #identity-popup-button-container,
 #identity-popup-content-box.unknownIdentity > #identity-popup-connectedToLabel ,
 #identity-popup-content-box.unknownIdentity > #identity-popup-runByLabel ,
 #identity-popup-content-box.unknownIdentity > #identity-popup-content-host ,
 #identity-popup-content-box.unknownIdentity > #identity-popup-content-owner ,
 #identity-popup-content-box.verifiedIdentity > #identity-popup-connectedToLabel2 ,
 #identity-popup-content-box.verifiedDomain > #identity-popup-connectedToLabel2 {
   display: none;
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4,16 +4,19 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/RecentWindow.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
 const nsIWebNavigation = Ci.nsIWebNavigation;
 
 var gCharsetMenu = null;
 var gLastBrowserCharset = null;
 var gPrevCharset = null;
 var gProxyFavIcon = null;
 var gLastValidURLStr = "";
 var gInPrintPreviewMode = false;
@@ -133,16 +136,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
   "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
+  "resource:///modules/SitePermissions.jsm");
+
 let gInitialPages = [
   "about:blank",
   "about:newtab",
   "about:home",
   "about:privatebrowsing",
   "about:welcomeback",
   "about:sessionrestore"
 ];
@@ -813,24 +819,16 @@ var gBrowserInit = {
 
     // hook up UI through progress listener
     gBrowser.addProgressListener(window.XULBrowserWindow);
     gBrowser.addTabsProgressListener(window.TabsProgressListener);
 
     // setup our common DOMLinkAdded listener
     gBrowser.addEventListener("DOMLinkAdded", DOMLinkHandler, false);
 
-    // setup our MozApplicationManifest listener
-    gBrowser.addEventListener("MozApplicationManifest",
-                              OfflineApps, false);
-    // listen for offline apps on social
-    let socialBrowser = document.getElementById("social-sidebar-browser");
-    socialBrowser.addEventListener("MozApplicationManifest",
-                              OfflineApps, false);
-
     // setup simple gestures support
     gGestureSupport.init(true);
 
     // setup history swipe animation
     gHistorySwipeAnimation.init();
 
     if (window.opener && !window.opener.closed) {
       let openerSidebarBox = window.opener.document.getElementById("sidebar-box");
@@ -967,16 +965,27 @@ var gBrowserInit = {
   _delayedStartup: function(uriToLoad, mustLoadSidebar) {
     let tmp = {};
     Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp);
     let TelemetryTimestamps = tmp.TelemetryTimestamps;
     TelemetryTimestamps.add("delayedStartupStarted");
 
     this._cancelDelayedStartup();
 
+    // We need to set the MozApplicationManifest event listeners up
+    // before we start loading the home pages in case a document has
+    // a "manifest" attribute, in which the MozApplicationManifest event
+    // will be fired.
+    gBrowser.addEventListener("MozApplicationManifest",
+                              OfflineApps, false);
+    // listen for offline apps on social
+    let socialBrowser = document.getElementById("social-sidebar-browser");
+    socialBrowser.addEventListener("MozApplicationManifest",
+                              OfflineApps, false);
+
     var isLoadingBlank = isBlankPageURL(uriToLoad);
 
     // This pageshow listener needs to be registered before we may call
     // swapBrowsersAndCloseOther() to receive pageshow events fired by that.
     gBrowser.addEventListener("pageshow", function(event) {
       // Filter out events that are not about the document load we are interested in
       if (content && event.target == content.document)
         setTimeout(pageShowEventHandlers, 0, event.persisted);
@@ -1920,98 +1929,99 @@ function loadURI(uri, referrer, postData
   if (allowThirdPartyFixup)
     flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
 
   try {
     gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData);
   } catch (e) {}
 }
 
-function getShortcutOrURI(aURL, aPostDataRef, aMayInheritPrincipal) {
-  // Initialize outparam to false
-  if (aMayInheritPrincipal)
-    aMayInheritPrincipal.value = false;
-
-  var shortcutURL = null;
-  var keyword = aURL;
-  var param = "";
-
-  var offset = aURL.indexOf(" ");
-  if (offset > 0) {
-    keyword = aURL.substr(0, offset);
-    param = aURL.substr(offset + 1);
-  }
-
-  if (!aPostDataRef)
-    aPostDataRef = {};
-
-  var engine = Services.search.getEngineByAlias(keyword);
-  if (engine) {
-    var submission = engine.getSubmission(param);
-    aPostDataRef.value = submission.postData;
-    return submission.uri.spec;
-  }
-
-  [shortcutURL, aPostDataRef.value] =
-    PlacesUtils.getURLAndPostDataForKeyword(keyword);
-
-  if (!shortcutURL)
-    return aURL;
-
-  var postData = "";
-  if (aPostDataRef.value)
-    postData = unescape(aPostDataRef.value);
-
-  if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) {
-    var charset = "";
-    const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
-    var matches = shortcutURL.match(re);
-    if (matches)
-      [, shortcutURL, charset] = matches;
-    else {
-      // Try to get the saved character-set.
-      try {
-        // makeURI throws if URI is invalid.
-        // Will return an empty string if character-set is not found.
-        charset = PlacesUtils.history.getCharsetForURI(makeURI(shortcutURL));
-      } catch (e) {}
-    }
-
-    // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
-    // escape() works in those cases, but it doesn't uri-encode +, @, and /.
-    // Therefore we need to manually replace these ASCII characters by their
-    // encodeURIComponent result, to match the behavior of nsEscape() with
-    // url_XPAlphas
-    var encodedParam = "";
-    if (charset && charset != "UTF-8")
-      encodedParam = escape(convertFromUnicode(charset, param)).
-                     replace(/[+@\/]+/g, encodeURIComponent);
-    else // Default charset is UTF-8
-      encodedParam = encodeURIComponent(param);
-
-    shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
-
-    if (/%s/i.test(postData)) // POST keyword
-      aPostDataRef.value = getPostDataStream(postData, param, encodedParam,
-                                             "application/x-www-form-urlencoded");
-  }
-  else if (param) {
-    // This keyword doesn't take a parameter, but one was provided. Just return
-    // the original URL.
-    aPostDataRef.value = null;
-
-    return aURL;
-  }
-
-  // This URL came from a bookmark, so it's safe to let it inherit the current
-  // document's principal.
-  if (aMayInheritPrincipal)
-    aMayInheritPrincipal.value = true;
-
-  return shortcutURL;
+function getShortcutOrURIAndPostData(aURL) {
+  return Task.spawn(function() {
+    let mayInheritPrincipal = false;
+    let postData = null;
+    let shortcutURL = null;
+    let keyword = aURL;
+    let param = "";
+
+    let offset = aURL.indexOf(" ");
+    if (offset > 0) {
+      keyword = aURL.substr(0, offset);
+      param = aURL.substr(offset + 1);
+    }
+
+    let engine = Services.search.getEngineByAlias(keyword);
+    if (engine) {
+      let submission = engine.getSubmission(param);
+      postData = submission.postData;
+      throw new Task.Result({ postData: submission.postData,
+                              url: submission.uri.spec,
+                              mayInheritPrincipal: mayInheritPrincipal });
+    }
+
+    [shortcutURL, postData] =
+      PlacesUtils.getURLAndPostDataForKeyword(keyword);
+
+    if (!shortcutURL)
+      throw new Task.Result({ postData: postData, url: aURL,
+                              mayInheritPrincipal: mayInheritPrincipal });
+
+    let escapedPostData = "";
+    if (postData)
+      escapedPostData = unescape(postData);
+
+    if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) {
+      let charset = "";
+      const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
+      let matches = shortcutURL.match(re);
+      if (matches)
+        [, shortcutURL, charset] = matches;
+      else {
+        // Try to get the saved character-set.
+        try {
+          // makeURI throws if URI is invalid.
+          // Will return an empty string if character-set is not found.
+          charset = yield PlacesUtils.getCharsetForURI(makeURI(shortcutURL));
+        } catch (e) {}
+      }
+
+      // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
+      // escape() works in those cases, but it doesn't uri-encode +, @, and /.
+      // Therefore we need to manually replace these ASCII characters by their
+      // encodeURIComponent result, to match the behavior of nsEscape() with
+      // url_XPAlphas
+      let encodedParam = "";
+      if (charset && charset != "UTF-8")
+        encodedParam = escape(convertFromUnicode(charset, param)).
+                       replace(/[+@\/]+/g, encodeURIComponent);
+      else // Default charset is UTF-8
+        encodedParam = encodeURIComponent(param);
+
+      shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
+
+      if (/%s/i.test(escapedPostData)) // POST keyword
+        postData = getPostDataStream(escapedPostData, param, encodedParam,
+                                               "application/x-www-form-urlencoded");
+    }
+    else if (param) {
+      // This keyword doesn't take a parameter, but one was provided. Just return
+      // the original URL.
+      postData = null;
+
+      throw new Task.Result({ postData: postData, url: aURL,
+                              mayInheritPrincipal: mayInheritPrincipal });
+    }
+
+    // This URL came from a bookmark, so it's safe to let it inherit the current
+    // document's principal.
+    mayInheritPrincipal = true;
+
+    throw new Task.Result({ postData: postData, url: shortcutURL,
+                            mayInheritPrincipal: mayInheritPrincipal });
+  });
 }
 
 function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
   var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].
                    createInstance(Ci.nsIStringInputStream);
   aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
   dataStream.data = aStringData;
 
@@ -2838,42 +2848,44 @@ var newTabButtonObserver = {
 
   onDragExit: function (aEvent)
   {
   },
 
   onDrop: function (aEvent)
   {
     let url = browserDragAndDrop.drop(aEvent, { });
-    var postData = {};
-    url = getShortcutOrURI(url, postData);
-    if (url) {
-      // allow third-party services to fixup this URL
-      openNewTabWith(url, null, postData.value, aEvent, true);
-    }
+    Task.spawn(function() {
+      let data = yield getShortcutOrURIAndPostData(url);
+      if (data.url) {
+        // allow third-party services to fixup this URL
+        openNewTabWith(data.url, null, data.postData, aEvent, true);
+      }
+    });
   }
 }
 
 var newWindowButtonObserver = {
   onDragOver: function (aEvent)
   {
     browserDragAndDrop.dragOver(aEvent);
   },
   onDragExit: function (aEvent)
   {
   },
   onDrop: function (aEvent)
   {
     let url = browserDragAndDrop.drop(aEvent, { });
-    var postData = {};
-    url = getShortcutOrURI(url, postData);
-    if (url) {
-      // allow third-party services to fixup this URL
-      openNewWindowWith(url, null, postData.value, true);
-    }
+    Task.spawn(function() {
+      let data = yield getShortcutOrURIAndPostData(url);
+      if (data.url) {
+        // allow third-party services to fixup this URL
+        openNewWindowWith(data.url, null, data.postData, true);
+      }
+    });
   }
 }
 
 const DOMLinkHandler = {
   handleEvent: function (event) {
     switch (event.type) {
       case "DOMLinkAdded":
         this.onLinkAdded(event);
@@ -5234,46 +5246,62 @@ function middleMousePaste(event) {
   let clipboard = readFromClipboard();
   if (!clipboard)
     return;
 
   // Strip embedded newlines and surrounding whitespace, to match the URL
   // bar's behavior (stripsurroundingwhitespace)
   clipboard = clipboard.replace(/\s*\n\s*/g, "");
 
-  let mayInheritPrincipal = { value: false };
-  let url = getShortcutOrURI(clipboard, mayInheritPrincipal);
-  try {
-    makeURI(url);
-  } catch (ex) {
-    // Not a valid URI.
-    return;
+  // if it's not the current tab, we don't need to do anything because the 
+  // browser doesn't exist.
+  let where = whereToOpenLink(event, true, false);
+  let lastLocationChange;
+  if (where == "current") {
+    lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
   }
 
-  try {
-    addToUrlbarHistory(url);
-  } catch (ex) {
-    // Things may go wrong when adding url to session history,
-    // but don't let that interfere with the loading of the url.
-    Cu.reportError(ex);
-  }
-
-  openUILink(url, event,
-             { ignoreButton: true,
-               disallowInheritPrincipal: !mayInheritPrincipal.value });
+  Task.spawn(function() {
+    let data = yield getShortcutOrURIAndPostData(clipboard);
+    try {
+      makeURI(data.url);
+    } catch (ex) {
+      // Not a valid URI.
+      return;
+    }
+
+    try {
+      addToUrlbarHistory(data.url);
+    } catch (ex) {
+      // Things may go wrong when adding url to session history,
+      // but don't let that interfere with the loading of the url.
+      Cu.reportError(ex);
+    }
+
+    if (where != "current" ||
+        lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+      openUILink(data.url, event,
+                 { ignoreButton: true,
+                   disallowInheritPrincipal: !data.mayInheritPrincipal });
+    }
+  });
 
   event.stopPropagation();
 }
 
 function handleDroppedLink(event, url, name)
 {
-  let postData = { };
-  let uri = getShortcutOrURI(url, postData);
-  if (uri)
-    loadURI(uri, null, postData.value, false);
+  let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+
+  Task.spawn(function() {
+    let data = yield getShortcutOrURIAndPostData(url);
+    if (data.url &&
+        lastLocationChange == gBrowser.selectedBrowser.lastLocationChange)
+      loadURI(data.url, null, data.postData, false);
+  });
 
   // Keep the event from being handled by the dragDrop listeners
   // built-in to gecko if they happen to be above us.
   event.preventDefault();
 };
 
 function MultiplexHandler(event)
 { try {
@@ -5386,17 +5414,16 @@ function charsetLoadListener() {
     if (!gCharsetMenu)
       gCharsetMenu = Cc['@mozilla.org/rdf/datasource;1?name=charset-menu'].getService(Ci.nsICurrentCharsetListener);
     gCharsetMenu.SetCurrentCharset(charset);
     gPrevCharset = gLastBrowserCharset;
     gLastBrowserCharset = charset;
   }
 }
 
-
 var gPageStyleMenu = {
 
   _getAllStyleSheets: function (frameset) {
     var styleSheetsArray = Array.slice(frameset.document.styleSheets);
     for (let i = 0; i < frameset.frames.length; i++) {
       let frameSheets = this._getAllStyleSheets(frameset.frames[i]);
       styleSheetsArray = styleSheetsArray.concat(frameSheets);
     }
@@ -6164,27 +6191,25 @@ function BrowserOpenAddonsMgr(aView) {
     // found the window above.
     Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
       Services.obs.removeObserver(observer, aTopic);
       aSubject.loadView(aView);
     }, "EM-loaded", false);
   }
 }
 
-function AddKeywordForSearchField() {
-  var node = document.popupNode;
-
+function GetSearchFieldBookmarkData(node) {
   var charset = node.ownerDocument.characterSet;
 
-  var docURI = makeURI(node.ownerDocument.URL,
-                       charset);
+  var formBaseURI = makeURI(node.form.baseURI,
+                            charset);
 
   var formURI = makeURI(node.form.getAttribute("action"),
                         charset,
-                        docURI);
+                        formBaseURI);
 
   var spec = formURI.spec;
 
   var isURLEncoded =
                (node.form.method.toUpperCase() == "POST"
                 && (node.form.enctype == "application/x-www-form-urlencoded" ||
                     node.form.enctype == ""));
 
@@ -6229,24 +6254,37 @@ function AddKeywordForSearchField() {
 
   var postData;
 
   if (isURLEncoded)
     postData = formData.join("&");
   else
     spec += "?" + formData.join("&");
 
+  return {
+    spec: spec,
+    title: title,
+    description: description,
+    postData: postData,
+    charSet: charset
+  };
+}
+
+
+function AddKeywordForSearchField() {
+  bookmarkData = GetSearchFieldBookmarkData(document.popupNode);
+
   PlacesUIUtils.showBookmarkDialog({ action: "add"
                                    , type: "bookmark"
-                                   , uri: makeURI(spec)
-                                   , title: title
-                                   , description: description
+                                   , uri: makeURI(bookmarkData.spec)
+                                   , title: bookmarkData.title
+                                   , description: bookmarkData.description
                                    , keyword: ""
-                                   , postData: postData
-                                   , charSet: charset
+                                   , postData: bookmarkData.postData
+                                   , charSet: bookmarkData.charset
                                    , hiddenRows: [ "location"
                                                  , "description"
                                                  , "tags"
                                                  , "loadInSidebar" ]
                                    }, window);
 }
 
 function SwitchDocumentDirection(aWindow) {
@@ -6446,39 +6484,52 @@ var gIdentityHandler = {
   get _identityIconCountryLabel () {
     delete this._identityIconCountryLabel;
     return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
   },
   get _identityIcon () {
     delete this._identityIcon;
     return this._identityIcon = document.getElementById("page-proxy-favicon");
   },
+  get _permissionsContainer () {
+    delete this._permissionsContainer;
+    return this._permissionsContainer = document.getElementById("identity-popup-permissions");
+  },
+  get _permissionList () {
+    delete this._permissionList;
+    return this._permissionList = document.getElementById("identity-popup-permission-list");
+  },
 
   /**
    * Rebuild cache of the elements that may or may not exist depending
    * on whether there's a location bar.
    */
   _cacheElements : function() {
     delete this._identityBox;
     delete this._identityIconLabel;
     delete this._identityIconCountryLabel;
     delete this._identityIcon;
+    delete this._permissionsContainer;
+    delete this._permissionList;
     this._identityBox = document.getElementById("identity-box");
     this._identityIconLabel = document.getElementById("identity-icon-label");
     this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
     this._identityIcon = document.getElementById("page-proxy-favicon");
+    this._permissionsContainer = document.getElementById("identity-popup-permissions");
+    this._permissionList = document.getElementById("identity-popup-permission-list");
   },
 
   /**
    * Handler for mouseclicks on the "More Information" button in the
    * "identity-popup" panel.
    */
   handleMoreInfoClick : function(event) {
     displaySecurityInfo();
     event.stopPropagation();
+    this._identityPopup.hidePopup();
   },
 
   /**
    * Helper to parse out the important parts of _lastStatus (of the SSL cert in
    * particular) for use in constructing identity UI strings
   */
   getIdentityData : function() {
     var result = {};
@@ -6610,16 +6661,17 @@ var gIdentityHandler = {
    */
   setMode : function(newMode) {
     if (!this._identityBox) {
       // No identity box means the identity box is not visible, in which
       // case there's nothing to do.
       return;
     }
 
+    this._identityPopup.className = newMode;
     this._identityBox.className = newMode;
     this.setIdentityMessages(newMode);
 
     // Update the popup too, if it's open
     if (this._identityPopup.state == "open")
       this.setPopupMessages(newMode);
 
     this._mode = newMode;
@@ -6752,20 +6804,16 @@ var gIdentityHandler = {
 
     // Push the appropriate strings out to the UI
     this._identityPopupContentHost.textContent = host;
     this._identityPopupContentOwner.textContent = owner;
     this._identityPopupContentSupp.textContent = supplemental;
     this._identityPopupContentVerif.textContent = verifier;
   },
 
-  hideIdentityPopup : function() {
-    this._identityPopup.hidePopup();
-  },
-
   /**
    * Click handler for the identity-box element in primary chrome.
    */
   handleIdentityButtonEvent : function(event) {
     TelemetryStopwatch.start("FX_IDENTITY_POPUP_OPEN_MS");
     event.stopPropagation();
 
     if ((event.type == "click" && event.button != 0) ||
@@ -6783,47 +6831,119 @@ var gIdentityHandler = {
 
     // Make sure that the display:none style we set in xul is removed now that
     // the popup is actually needed
     this._identityPopup.hidden = false;
 
     // Update the popup strings
     this.setPopupMessages(this._identityBox.className);
 
+    this.updateSitePermissions();
+
     // Add the "open" attribute to the identity box for styling
     this._identityBox.setAttribute("open", "true");
     var self = this;
     this._identityPopup.addEventListener("popuphidden", function onPopupHidden(e) {
       e.currentTarget.removeEventListener("popuphidden", onPopupHidden, false);
       self._identityBox.removeAttribute("open");
     }, false);
 
     // Now open the popup, anchored off the primary chrome element
     this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
   },
 
   onPopupShown : function(event) {
     TelemetryStopwatch.finish("FX_IDENTITY_POPUP_OPEN_MS");
+
     document.getElementById('identity-popup-more-info-button').focus();
+
+    this._identityPopup.addEventListener("blur", this, true);
+    this._identityPopup.addEventListener("popuphidden", this);
   },
 
   onDragStart: function (event) {
     if (gURLBar.getAttribute("pageproxystate") != "valid")
       return;
 
     var value = content.location.href;
     var urlString = value + "\n" + content.document.title;
     var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
 
     var dt = event.dataTransfer;
     dt.setData("text/x-moz-url", urlString);
     dt.setData("text/uri-list", value);
     dt.setData("text/plain", value);
     dt.setData("text/html", htmlString);
     dt.setDragImage(gProxyFavIcon, 16, 16);
+  },
+ 
+  handleEvent: function (event) {
+    switch (event.type) {
+      case "blur":
+        // Focus hasn't moved yet, need to wait until after the blur event.
+        setTimeout(() => {
+          if (document.activeElement &&
+              document.activeElement.compareDocumentPosition(this._identityPopup) &
+                Node.DOCUMENT_POSITION_CONTAINS)
+            return;
+
+          this._identityPopup.hidePopup();
+        }, 0);
+        break;
+      case "popuphidden":
+        this._identityPopup.removeEventListener("blur", this, true);
+        this._identityPopup.removeEventListener("popuphidden", this);
+        break;
+    }
+  },
+
+  updateSitePermissions: function () {
+    while (this._permissionList.hasChildNodes())
+      this._permissionList.removeChild(this._permissionList.lastChild);
+
+    let uri = gBrowser.currentURI;
+
+    for (let permission of SitePermissions.listPermissions()) {
+      let state = SitePermissions.get(uri, permission);
+
+      if (state == SitePermissions.UNKNOWN)
+        continue;
+
+      let item = this._createPermissionItem(permission, state);
+      this._permissionList.appendChild(item);
+    }
+
+    this._permissionsContainer.hidden = !this._permissionList.hasChildNodes();
+  },
+
+  _createPermissionItem: function (aPermission, aState) {
+    let menulist = document.createElement("menulist");
+    let menupopup = document.createElement("menupopup");
+    for (let state of SitePermissions.getAvailableStates(aPermission)) {
+      let menuitem = document.createElement("menuitem");
+      menuitem.setAttribute("value", state);
+      menuitem.setAttribute("label", SitePermissions.getStateLabel(state));
+      menupopup.appendChild(menuitem);
+    }
+    menulist.appendChild(menupopup);
+    menulist.setAttribute("value", aState);
+    menulist.setAttribute("oncommand", "SitePermissions.set(gBrowser.currentURI, '" +
+                                       aPermission + "', this.value)");
+    menulist.setAttribute("id", "identity-popup-permission:" + aPermission);
+
+    let label = document.createElement("label");
+    label.setAttribute("flex", "1");
+    label.setAttribute("control", menulist.getAttribute("id"));
+    label.setAttribute("value", SitePermissions.getPermissionLabel(aPermission));
+
+    let container = document.createElement("hbox");
+    container.setAttribute("align", "center");
+    container.appendChild(label);
+    container.appendChild(menulist);
+    return container;
   }
 };
 
 function getNotificationBox(aWindow) {
   var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
   if (foundBrowser)
     return gBrowser.getNotificationBox(foundBrowser)
   return null;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -288,17 +288,19 @@
     <menupopup id="placesContext"/>
 
     <!-- Popup for site identity information -->
     <panel id="identity-popup"
            type="arrow"
            hidden="true"
            noautofocus="true"
            consumeoutsideclicks="true"
-           onpopupshown="gIdentityHandler.onPopupShown(event);"
+           onpopupshown="if (event.target == this)
+                           gIdentityHandler.onPopupShown(event);"
+           orient="vertical"
            level="top">
       <hbox id="identity-popup-container" align="top">
         <image id="identity-popup-icon"/>
         <vbox id="identity-popup-content-box">
           <label id="identity-popup-brandName"
                  class="identity-popup-label"
                  value="&brandFullName;"/>
           <label id="identity-popup-chromeLabel"
@@ -322,25 +324,30 @@
                        class="identity-popup-description"/>
           <hbox id="identity-popup-encryption" flex="1">
             <vbox>
               <image id="identity-popup-encryption-icon"/>
             </vbox>
             <description id="identity-popup-encryption-label" flex="1"
                          class="identity-popup-description"/>
           </hbox>
-          <!-- Footer button to open security page info -->
-          <hbox id="identity-popup-button-container" pack="end">
-            <button id="identity-popup-more-info-button"
-                    label="&identity.moreInfoLinkText;"
-                    onblur="gIdentityHandler.hideIdentityPopup();"
-                    oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
-          </hbox>
+          <vbox id="identity-popup-permissions">
+            <separator class="thin"/>
+            <label class="identity-popup-label header"
+                   value="&identity.permissions;"/>
+            <vbox id="identity-popup-permission-list" class="indent"/>
+          </vbox>
         </vbox>
       </hbox>
+      <!-- Footer button to open security page info -->
+      <hbox id="identity-popup-button-container" pack="end">
+        <button id="identity-popup-more-info-button"
+                label="&identity.moreInfoLinkText;"
+                oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
+      </hbox>
     </panel>
 
     <panel id="ctrlTab-panel" class="KUI-panel" hidden="true" norestorefocus="true" level="top">
       <hbox>
         <button class="ctrlTab-preview" flex="1"/>
         <button class="ctrlTab-preview" flex="1"/>
         <button class="ctrlTab-preview" flex="1"/>
         <button class="ctrlTab-preview" flex="1"/>
--- a/browser/base/content/openLocation.js
+++ b/browser/base/content/openLocation.js
@@ -11,16 +11,17 @@ let openLocationModule = {};
 try {
   pref = Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefBranch);
 } catch (ex) {
   // not critical, remain silent
 }
 
 Components.utils.import("resource:///modules/openLocationLastURL.jsm", openLocationModule);
+Components.utils.import("resource://gre/modules/Task.jsm");
 let gOpenLocationLastURL = new openLocationModule.OpenLocationLastURL(window.opener);
 
 function onLoad()
 {
   dialog.input         = document.getElementById("dialog.input");
   dialog.open          = document.documentElement.getButton("accept");
   dialog.openWhereList = document.getElementById("openWhereList");
   dialog.openTopWindow = document.getElementById("currentWindow");
@@ -56,54 +57,62 @@ function onLoad()
 
 function doEnabling()
 {
     dialog.open.disabled = !dialog.input.value;
 }
 
 function open()
 {
-  var url;
-  var postData = {};
-  var mayInheritPrincipal = {value: false};
-  if (browser)
-    url = browser.getShortcutOrURI(dialog.input.value, postData, mayInheritPrincipal);
-  else
-    url = dialog.input.value;
+  Task.spawn(function() {
+    let url;
+    let postData = null;
+    let mayInheritPrincipal = false;
+
+    if (browser) {
+      let data = yield browser.getShortcutOrURIAndPostData(dialog.input.value);
+      url = data.url;
+      postData = data.postData;
+      mayInheritPrincipal = data.mayInheritPrincipal;
+    } else {
+      url = dialog.input.value;
+    }
 
-  try {
-    // Whichever target we use for the load, we allow third-party services to
-    // fixup the URI
-    switch (dialog.openWhereList.value) {
-      case "0":
-        var webNav = Components.interfaces.nsIWebNavigation;
-        var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
-        if (!mayInheritPrincipal.value)
-          flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
-        browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData.value);
-        break;
-      case "1":
-        window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
-                                        url, postData.value, null, null, true);
-        break;
-      case "3":
-        browser.delayedOpenTab(url, null, null, postData.value, true);
-        break;
+    try {
+      // Whichever target we use for the load, we allow third-party services to
+      // fixup the URI
+      switch (dialog.openWhereList.value) {
+        case "0":
+          var webNav = Components.interfaces.nsIWebNavigation;
+          var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+          if (!mayInheritPrincipal)
+            flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+          browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData);
+          break;
+        case "1":
+          window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
+                                          url, postData, null, null, true);
+          break;
+        case "3":
+          browser.delayedOpenTab(url, null, null, postData, true);
+          break;
+      }
     }
-  }
-  catch(exception) {
-  }
+    catch(exception) {
+    }
 
-  if (pref) {
-    gOpenLocationLastURL.value = dialog.input.value;
-    pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value);
-  }
+    if (pref) {
+      gOpenLocationLastURL.value = dialog.input.value;
+      pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value);
+    }
 
-  // Delay closing slightly to avoid timing bug on Linux.
-  window.close();
+    // Delay closing slightly to avoid timing bug on Linux.
+    window.close();
+  });
+
   return false;
 }
 
 function createInstance(contractid, iidName)
 {
   var iid = Components.interfaces[iidName];
   return Components.classes[contractid].createInstance(iid);
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -674,18 +674,20 @@
               }
 
               if (!this.mBlank) {
                 this._callProgressListeners("onLocationChange",
                                             [aWebProgress, aRequest, aLocation,
                                              aFlags]);
               }
 
-              if (topLevel)
+              if (topLevel) {
                 this.mBrowser.lastURI = aLocation;
+                this.mBrowser.lastLocationChange = Date.now();
+              }
             },
 
             onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
               if (this.mBlank)
                 return;
 
               this._callProgressListeners("onStatusChange",
                                           [aWebProgress, aRequest, aStatus, aMessage]);
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -321,16 +321,17 @@ MOCHITEST_BROWSER_FILES = \
                  pluginCrashCommentAndURL.html \
                  browser_private_no_prompt.js \
                  browser_blob-channelname.js \
                  browser_aboutHealthReport.js \
                  healthreport_testRemoteCommands.html \
                  browser_offlineQuotaNotification.js \
                  offlineQuotaNotification.html \
                  offlineQuotaNotification.cacheManifest \
+                 browser_addKeywordSearch.js \
                  $(NULL)
 
 # Disable tests on Windows due to frequent failures (bugs 825739, 841341)
 ifneq (windows,$(MOZ_WIDGET_TOOLKIT))
 MOCHITEST_BROWSER_FILES += \
                  browser_bookmark_titles.js \
                  browser_popupNotification.js \
                  $(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_addKeywordSearch.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab("http://example.org/browser/browser/base/content/test/dummy_page.html");
+
+  gBrowser.selectedBrowser.addEventListener("load", function runTests() {
+    gBrowser.selectedBrowser.removeEventListener("load", runTests, true);
+
+    let doc = gBrowser.contentDocument;
+    let base = doc.createElement("base");
+    doc.head.appendChild(base);
+
+    let check = function (baseURI, fieldName, expected) {
+      base.href = baseURI;
+
+      let form = doc.createElement("form");
+      let element = doc.createElement("input");
+      element.setAttribute("type", "text");
+      element.setAttribute("name", fieldName);
+      form.appendChild(element);
+      doc.body.appendChild(form);
+
+      let data = GetSearchFieldBookmarkData(element);
+      is(data.spec, expected, "Bookmark spec for search field named " + fieldName + " and baseURI " + baseURI + " incorrect");
+
+      doc.body.removeChild(form);
+    }
+
+    let testData = [
+    /* baseURI, field name, expected */
+      [ 'http://example.com/', 'q', 'http://example.com/?q=%s' ],
+      [ 'http://example.com/new-path-here/', 'q', 'http://example.com/new-path-here/?q=%s' ],
+      [ '', 'q', 'http://example.org/browser/browser/base/content/test/dummy_page.html?q=%s' ],
+    ]
+
+    for (let data of testData) {
+      check(data[0], data[1], data[2]);
+    }
+
+    // cleanup
+    gBrowser.removeCurrentTab();
+    finish();
+  }, true);
+}
--- a/browser/base/content/test/browser_contentAreaClick.js
+++ b/browser/base/content/test/browser_contentAreaClick.js
@@ -162,17 +162,17 @@ let gTests = [
 // Array of method names that will be replaced in the new window.
 let gReplacedMethods = [
   "middleMousePaste",
   "urlSecurityCheck",
   "loadURI",
   "gatherTextUnder",
   "saveURL",
   "openLinkIn",
-  "getShortcutOrURI",
+  "getShortcutOrURIAndPostData",
 ];
 
 // Reference to the new window.
 let gTestWin = null;
 
 // List of methods invoked by a specific call to contentAreaClick.
 let gInvokedMethods = [];
 
@@ -227,18 +227,18 @@ let gClickHandler = {
     executeSoon(runNextTest);
   }
 }
 
 // Wraps around the methods' replacement mock function.
 function wrapperMethod(aInvokedMethods, aMethodName) {
   return function () {
     aInvokedMethods.push(aMethodName);
-    // At least getShortcutOrURI requires to return url that is the first param.
-    return arguments[0];
+    // At least getShortcutOrURIAndPostData requires to return url
+    return (aMethodName == "getShortcutOrURIAndPostData") ? arguments.url : arguments[0];
   }
 }
 
 function setupTestBrowserWindow() {
   // Steal click events and don't propagate them.
   gTestWin.addEventListener("click", gClickHandler, true);
 
   // Replace methods.
--- a/browser/base/content/test/browser_getshortcutoruri.js
+++ b/browser/base/content/test/browser_getshortcutoruri.js
@@ -80,43 +80,43 @@ var testData = [
   // UTF-8 default
   [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "+/@"),
    new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
   // Explicitly-defined ISO-8859-1
   [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "+/@"),
    new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
 
   // Test using a non-bmKeywordData object, to test the behavior of
-  // getShortcutOrURI for non-keywords (setupKeywords only adds keywords for
+  // getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for
   // bmKeywordData objects)
   [{keyword: "http://gavinsharp.com"},
    new keywordResult(null, null, true)]
 ];
 
 function test() {
+  waitForExplicitFinish();
+
   setupKeywords();
 
-  for each (var item in testData) {
-    var [data, result] = item;
+  Task.spawn(function() {
+    for each (var item in testData) {
+      let [data, result] = item;
 
-    var postData = {};
-    var query = data.keyword;
-    if (data.searchWord)
-      query += " " + data.searchWord;
-    var mayInheritPrincipal = {};
-    var url = getShortcutOrURI(query, postData, mayInheritPrincipal);
-
-    // null result.url means we should expect the same query we sent in
-    var expected = result.url || query;
-    is(url, expected, "got correct URL for " + data.keyword);
-    is(getPostDataString(postData.value), result.postData, "got correct postData for " + data.keyword);
-    is(mayInheritPrincipal.value, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
-  }
-
-  cleanupKeywords();
+      let query = data.keyword;
+      if (data.searchWord)
+        query += " " + data.searchWord;
+      let returnedData = yield getShortcutOrURIAndPostData(query);
+      // null result.url means we should expect the same query we sent in
+      let expected = result.url || query;
+      is(returnedData.url, expected, "got correct URL for " + data.keyword);
+      is(getPostDataString(returnedData.postData), result.postData, "got correct postData for " + data.keyword);
+      is(returnedData.mayInheritPrincipal, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
+    }
+    cleanupKeywords();
+  }).then(finish);
 }
 
 var gBMFolder = null;
 var gAddedEngines = [];
 function setupKeywords() {
   gBMFolder = Application.bookmarks.menu.addFolder("keyword-test");
   for each (var item in testData) {
     var data = item[0];
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -253,165 +253,175 @@
           if (aTriggeringEvent instanceof MouseEvent && aTriggeringEvent.button == 2)
             return; // Do nothing for right clicks
 
           var url = this.value;
           var mayInheritPrincipal = false;
           var postData = null;
 
           var action = this._parseActionUrl(url);
-          if (action) {
-            url = action.param;
-            if (this.hasAttribute("actiontype")) {
-              if (action.type == "switchtab") {
-                this.handleRevert();
-                let prevTab = gBrowser.selectedTab;
-                if (switchToTabHavingURI(url) &&
-                    isTabEmpty(prevTab))
-                  gBrowser.removeTab(prevTab);
+          let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+          Task.spawn(function() {
+            let matchLastLocationChange = true;
+            if (action) {
+              url = action.param;
+              if (this.hasAttribute("actiontype")) {
+                if (action.type == "switchtab") {
+                  this.handleRevert();
+                  let prevTab = gBrowser.selectedTab;
+                  if (switchToTabHavingURI(url) &&
+                      isTabEmpty(prevTab))
+                    gBrowser.removeTab(prevTab);
+                }
+                return;
               }
-              return;
             }
-          }
-          else {
-            [url, postData, mayInheritPrincipal] = this._canonizeURL(aTriggeringEvent);
-            if (!url)
-              return;
-          }
+            else {
+              [url, postData, mayInheritPrincipal] = yield this._canonizeURL(aTriggeringEvent);
+              matchLastLocationChange = (lastLocationChange ==
+                                         gBrowser.selectedBrowser.lastLocationChange);
+              if (!url)
+                return;
+            }
 
-          this.value = url;
-          gBrowser.userTypedValue = url;
-          try {
-            addToUrlbarHistory(url);
-          } catch (ex) {
-            // Things may go wrong when adding url to session history,
-            // but don't let that interfere with the loading of the url.
-            Cu.reportError(ex);
-          }
+            this.value = url;
+            gBrowser.userTypedValue = url;
+            try {
+              addToUrlbarHistory(url);
+            } catch (ex) {
+              // Things may go wrong when adding url to session history,
+              // but don't let that interfere with the loading of the url.
+              Cu.reportError(ex);
+            }
 
-          function loadCurrent() {
-            let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
-            // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
-            // inheriting the currently loaded document's principal, unless this
-            // URL is marked as safe to inherit (e.g. came from a bookmark
-            // keyword).
-            if (!mayInheritPrincipal)
-              flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
-            // If the value wasn't typed, we know that we decoded the value as
-            // UTF-8 (see losslessDecodeURI)
-            if (!this.valueIsTyped)
-              flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8;
-            gBrowser.loadURIWithFlags(url, flags, null, null, postData);
-          }
+            function loadCurrent() {
+              let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+              // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
+              // inheriting the currently loaded document's principal, unless this
+              // URL is marked as safe to inherit (e.g. came from a bookmark
+              // keyword).
+              if (!mayInheritPrincipal)
+                flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+              // If the value wasn't typed, we know that we decoded the value as
+              // UTF-8 (see losslessDecodeURI)
+              if (!this.valueIsTyped)
+                flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8;
+              gBrowser.loadURIWithFlags(url, flags, null, null, postData);
+            }
 
-          // Focus the content area before triggering loads, since if the load
-          // occurs in a new tab, we want focus to be restored to the content
-          // area when the current tab is re-selected.
-          gBrowser.selectedBrowser.focus();
+            // Focus the content area before triggering loads, since if the load
+            // occurs in a new tab, we want focus to be restored to the content
+            // area when the current tab is re-selected.
+            gBrowser.selectedBrowser.focus();
 
-          let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
-          let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey;
+            let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
+            let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey;
 
-          if (altEnter) {
-            // XXX This was added a long time ago, and I'm not sure why it is
-            // necessary. Alt+Enter's default action might cause a system beep,
-            // or something like that?
-            aTriggeringEvent.preventDefault();
-            aTriggeringEvent.stopPropagation();
-          }
+            if (altEnter) {
+              // XXX This was added a long time ago, and I'm not sure why it is
+              // necessary. Alt+Enter's default action might cause a system beep,
+              // or something like that?
+              aTriggeringEvent.preventDefault();
+              aTriggeringEvent.stopPropagation();
+            }
 
-          // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
-          altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab);
+            // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
+            altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab);
+
+            if (isMouseEvent || altEnter) {
+              // Use the standard UI link behaviors for clicks or Alt+Enter
+              let where = "tab";
+              if (isMouseEvent)
+                where = whereToOpenLink(aTriggeringEvent, false, false);
 
-          if (isMouseEvent || altEnter) {
-            // Use the standard UI link behaviors for clicks or Alt+Enter
-            let where = "tab";
-            if (isMouseEvent)
-              where = whereToOpenLink(aTriggeringEvent, false, false);
-
-            if (where == "current") {
-              loadCurrent();
+              if (where == "current") {
+                if (matchLastLocationChange) {
+                  loadCurrent();
+                }
+              } else {
+                this.handleRevert();
+                let params = { allowThirdPartyFixup: true,
+                               postData: postData,
+                               initiatingDoc: document };
+                if (!this.valueIsTyped)
+                  params.isUTF8 = true;
+                openUILinkIn(url, where, params);
+              }
             } else {
-              this.handleRevert();
-              let params = { allowThirdPartyFixup: true,
-                             postData: postData,
-                             initiatingDoc: document };
-              if (!this.valueIsTyped)
-                params.isUTF8 = true;
-              openUILinkIn(url, where, params);
+              if (matchLastLocationChange) {
+                loadCurrent();
+              }
             }
-          } else {
-            loadCurrent();
-          }
+          }.bind(this));
         ]]></body>
       </method>
 
       <method name="_canonizeURL">
         <parameter name="aTriggeringEvent"/>
         <body><![CDATA[
-          var url = this.value;
-          if (!url)
-            return ["", null, false];
+          return Task.spawn(function() {
+            var url = this.value;
+            if (!url)
+              throw new Task.Result(["", null, false]);
 
-          // Only add the suffix when the URL bar value isn't already "URL-like",
-          // and only if we get a keyboard event, to match user expectations.
-          if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
-              (aTriggeringEvent instanceof KeyEvent)) {
+            // Only add the suffix when the URL bar value isn't already "URL-like",
+            // and only if we get a keyboard event, to match user expectations.
+            if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
+                (aTriggeringEvent instanceof KeyEvent)) {
 #ifdef XP_MACOSX
-            let accel = aTriggeringEvent.metaKey;
+              let accel = aTriggeringEvent.metaKey;
 #else
-            let accel = aTriggeringEvent.ctrlKey;
+              let accel = aTriggeringEvent.ctrlKey;
 #endif
-            let shift = aTriggeringEvent.shiftKey;
+              let shift = aTriggeringEvent.shiftKey;
 
-            let suffix = "";
+              let suffix = "";
 
-            switch (true) {
-              case (accel && shift):
-                suffix = ".org/";
-                break;
-              case (shift):
-                suffix = ".net/";
-                break;
-              case (accel):
-                try {
-                  suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
-                  if (suffix.charAt(suffix.length - 1) != "/")
-                    suffix += "/";
-                } catch(e) {
-                  suffix = ".com/";
+              switch (true) {
+                case (accel && shift):
+                  suffix = ".org/";
+                  break;
+                case (shift):
+                  suffix = ".net/";
+                  break;
+                case (accel):
+                  try {
+                    suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
+                    if (suffix.charAt(suffix.length - 1) != "/")
+                      suffix += "/";
+                  } catch(e) {
+                    suffix = ".com/";
+                  }
+                  break;
+              }
+
+              if (suffix) {
+                // trim leading/trailing spaces (bug 233205)
+                url = url.trim();
+
+                // Tack www. and suffix on.  If user has appended directories, insert
+                // suffix before them (bug 279035).  Be careful not to get two slashes.
+
+                let firstSlash = url.indexOf("/");
+
+                if (firstSlash >= 0) {
+                  url = url.substring(0, firstSlash) + suffix +
+                        url.substring(firstSlash + 1);
+                } else {
+                  url = url + suffix;
                 }
-                break;
+
+                url = "http://www." + url;
+              }
             }
 
-            if (suffix) {
-              // trim leading/trailing spaces (bug 233205)
-              url = url.trim();
-
-              // Tack www. and suffix on.  If user has appended directories, insert
-              // suffix before them (bug 279035).  Be careful not to get two slashes.
-
-              let firstSlash = url.indexOf("/");
+            let data = yield getShortcutOrURIAndPostData(url);
 
-              if (firstSlash >= 0) {
-                url = url.substring(0, firstSlash) + suffix +
-                      url.substring(firstSlash + 1);
-              } else {
-                url = url + suffix;
-              }
-
-              url = "http://www." + url;
-            }
-          }
-
-          var postData = {};
-          var mayInheritPrincipal = { value: false };
-          url = getShortcutOrURI(url, postData, mayInheritPrincipal);
-
-          return [url, postData.value, mayInheritPrincipal.value];
+            throw new Task.Result([data.url, data.postData, data.mayInheritPrincipal]);
+          }.bind(this));
         ]]></body>
       </method>
 
       <field name="_contentIsCropped">false</field>
 
       <method name="_initURLTooltip">
         <body><![CDATA[
           if (this.focused || !this._contentIsCropped)
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -321,24 +321,42 @@ BrowserGlue.prototype = {
         if (data != "engine-default" && data != "engine-current") {
           break;
         }
         // Enforce that the search service's defaultEngine is always equal to
         // its currentEngine. The search service will notify us any time either
         // of them are changed (either by directly setting the relevant prefs,
         // i.e. if add-ons try to change this directly, or if the
         // nsIBrowserSearchService setters are called).
+        // No need to initialize the search service, since it's guaranteed to be
+        // initialized already when this notification fires.
         let ss = Services.search;
         if (ss.currentEngine.name == ss.defaultEngine.name)
           return;
         if (data == "engine-current")
           ss.defaultEngine = ss.currentEngine;
         else
           ss.currentEngine = ss.defaultEngine;
         break;
+      case "browser-search-service":
+        if (data != "init-complete")
+          return;
+        Services.obs.removeObserver(this, "browser-search-service");
+        this._syncSearchEngines();
+        break;
+    }
+  },
+
+  _syncSearchEngines: function () {
+    // Only do this if the search service is already initialized. This function
+    // gets called in finalUIStartup and from a browser-search-service observer,
+    // to catch both cases (search service initialization occurring before and
+    // after final-ui-startup)
+    if (Services.search.isInitialized) {
+      Services.search.defaultEngine = Services.search.currentEngine;
     }
   },
 
   // initialization (called on application startup) 
   _init: function BG__init() {
     let os = Services.obs;
     os.addObserver(this, "prefservice:after-app-defaults", false);
     os.addObserver(this, "final-ui-startup", false);
@@ -364,16 +382,17 @@ BrowserGlue.prototype = {
     os.addObserver(this, "places-shutdown", false);
     this._isPlacesShutdownObserver = true;
     os.addObserver(this, "handle-xul-text-link", false);
     os.addObserver(this, "profile-before-change", false);
 #ifdef MOZ_SERVICES_HEALTHREPORT
     os.addObserver(this, "keyword-search", false);
 #endif
     os.addObserver(this, "browser-search-engine-modified", false);
+    os.addObserver(this, "browser-search-service", false);
   },
 
   // cleanup (called on application shutdown)
   _dispose: function BG__dispose() {
     let os = Services.obs;
     os.removeObserver(this, "prefservice:after-app-defaults");
     os.removeObserver(this, "final-ui-startup");
     os.removeObserver(this, "sessionstore-windows-restored");
@@ -398,16 +417,20 @@ BrowserGlue.prototype = {
     if (this._isPlacesShutdownObserver)
       os.removeObserver(this, "places-shutdown");
     os.removeObserver(this, "handle-xul-text-link");
     os.removeObserver(this, "profile-before-change");
 #ifdef MOZ_SERVICES_HEALTHREPORT
     os.removeObserver(this, "keyword-search");
 #endif
     os.removeObserver(this, "browser-search-engine-modified");
+    try {
+      os.removeObserver(this, "browser-search-service");
+      // may have already been removed by the observer
+    } catch (ex) {}
   },
 
   _onAppDefaults: function BG__onAppDefaults() {
     // apply distribution customizations (prefs)
     // other customizations are applied in _finalUIStartup()
     this._distributionCustomizer.applyPrefDefaults();
   },
 
@@ -425,16 +448,18 @@ BrowserGlue.prototype = {
     // prefs are applied in _onAppDefaults()
     this._distributionCustomizer.applyCustomizations();
 
     // handle any UI migration
     this._migrateUI();
 
     this._setUpUserAgentOverrides();
 
+    this._syncSearchEngines();
+
     webappsUI.init();
     PageThumbs.init();
     NewTabUtils.init();
     BrowserNewTabPreloader.init();
     SignInToWebsiteUX.init();
     PdfJs.init();
     webrtcUI.init();
 
--- a/browser/components/preferences/advanced.js
+++ b/browser/components/preferences/advanced.js
@@ -245,17 +245,17 @@ var gAdvancedPane = {
 
     let policy = Components.classes["@mozilla.org/datareporting/service;1"]
                                    .getService(Components.interfaces.nsISupports)
                                    .wrappedJSObject
                                    .policy;
 
     let checkbox = document.getElementById("submitHealthReportBox");
 
-    if (!policy) {
+    if (!policy || policy.healthReportUploadLocked) {
       checkbox.setAttribute("disabled", "true");
       return;
     }
 
     checkbox.checked = policy.healthReportUploadEnabled;
   },
 
   /**
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -225,17 +225,17 @@ var gAdvancedPane = {
 
     let policy = Components.classes["@mozilla.org/datareporting/service;1"]
                                    .getService(Components.interfaces.nsISupports)
                                    .wrappedJSObject
                                    .policy;
 
     let checkbox = document.getElementById("submitHealthReportBox");
 
-    if (!policy) {
+    if (!policy || policy.healthReportUploadLocked) {
       checkbox.setAttribute("disabled", "true");
       return;
     }
 
     checkbox.checked = policy.healthReportUploadEnabled;
   },
 
   /**
--- a/browser/components/preferences/in-content/tests/browser_healthreport.js
+++ b/browser/components/preferences/in-content/tests/browser_healthreport.js
@@ -1,48 +1,65 @@
 /* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+function runPaneTest(fn) {
+  open_preferences((win) => {
+    let doc = win.document;
+    win.gotoPref("paneAdvanced");
+    let advancedPrefs = doc.getElementById("advancedPrefs");
+    let tab = doc.getElementById("dataChoicesTab");
+    advancedPrefs.selectedTab = tab;
+
+    let policy = Components.classes["@mozilla.org/datareporting/service;1"]
+                                   .getService(Components.interfaces.nsISupports)
+                                   .wrappedJSObject
+                                   .policy;
+
+    ok(policy, "Policy object is defined.");
+    fn(win, doc, policy);
+  });
+}
+
 function test() {
   waitForExplicitFinish();
   resetPreferences();
   registerCleanupFunction(resetPreferences);
-  open_preferences(runTest);
+  runPaneTest(testBasic);
 }
 
-function runTest(win) {
-  let doc = win.document;
-
-  win.gotoPref("paneAdvanced");
-  let advancedPrefs = doc.getElementById("advancedPrefs");
-  let dataChoicesTab = doc.getElementById("dataChoicesTab");
-  advancedPrefs.selectedTab = dataChoicesTab;
-
-  let policy = Components.classes["@mozilla.org/datareporting/service;1"]
-                                 .getService(Components.interfaces.nsISupports)
-                                 .wrappedJSObject
-                                 .policy;
-  ok(policy);
+function testBasic(win, doc, policy) {
   is(policy.dataSubmissionPolicyAccepted, false, "Data submission policy not accepted.");
   is(policy.healthReportUploadEnabled, true, "Health Report upload enabled on app first run.");
 
   let checkbox = doc.getElementById("submitHealthReportBox");
   ok(checkbox);
   is(checkbox.checked, true, "Health Report checkbox is checked on app first run.");
 
   checkbox.checked = false;
   checkbox.doCommand();
   is(policy.healthReportUploadEnabled, false, "Unchecking checkbox opts out of FHR upload.");
 
   checkbox.checked = true;
   checkbox.doCommand();
   is(policy.healthReportUploadEnabled, true, "Checking checkbox allows FHR upload.");
 
   win.close();
+  Services.prefs.lockPref("datareporting.healthreport.uploadEnabled");
+  runPaneTest(testUploadDisabled);
+}
+
+function testUploadDisabled(win, doc, policy) {
+  ok(policy.healthReportUploadLocked, "Upload enabled flag is locked.");
+  let checkbox = doc.getElementById("submitHealthReportBox");
+  is(checkbox.getAttribute("disabled"), "true", "Checkbox is disabled if upload flag is locked.");
+  policy._healthReportPrefs.unlock("uploadEnabled");
+
+  win.close();
   finish();
 }
 
 function resetPreferences() {
   Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
 }
 
--- a/browser/components/preferences/tests/browser_healthreport.js
+++ b/browser/components/preferences/tests/browser_healthreport.js
@@ -1,35 +1,53 @@
 /* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+function runPaneTest(fn) {
+  function observer(win, topic, data) {
+    Services.obs.removeObserver(observer, "advanced-pane-loaded");
+
+    let policy = Components.classes["@mozilla.org/datareporting/service;1"]
+                                   .getService(Components.interfaces.nsISupports)
+                                   .wrappedJSObject
+                                   .policy;
+    ok(policy, "Policy object defined");
+
+    fn(win, policy);
+  }
+
+  Services.obs.addObserver(observer, "advanced-pane-loaded", false);
+  openDialog("chrome://browser/content/preferences/preferences.xul", "Preferences",
+             "chrome,titlebar,toolbar,centerscreen,dialog=no", "paneAdvanced");
+}
+
 function test() {
   waitForExplicitFinish();
   resetPreferences();
   registerCleanupFunction(resetPreferences);
 
-  function observer(win, topic, data) {
-    Services.obs.removeObserver(observer, "advanced-pane-loaded");
-    runTest(win);
-  }
-  Services.obs.addObserver(observer, "advanced-pane-loaded", false);
-  openDialog("chrome://browser/content/preferences/preferences.xul", "Preferences",
-             "chrome,titlebar,toolbar,centerscreen,dialog=no", "paneAdvanced");
+  Services.prefs.lockPref("datareporting.healthreport.uploadEnabled");
+  runPaneTest(testUploadDisabled);
 }
 
-function runTest(win) {
+function testUploadDisabled(win, policy) {
+  ok(policy.healthReportUploadLocked, "Upload enabled flag is locked.");
+  let checkbox = win.document.getElementById("submitHealthReportBox");
+  is(checkbox.getAttribute("disabled"), "true", "Checkbox is disabled if upload setting is locked.");
+  policy._healthReportPrefs.unlock("uploadEnabled");
+
+  win.close();
+  runPaneTest(testBasic);
+}
+
+function testBasic(win, policy) {
   let doc = win.document;
 
-  let policy = Components.classes["@mozilla.org/datareporting/service;1"]
-                                 .getService(Components.interfaces.nsISupports)
-                                 .wrappedJSObject
-                                 .policy;
-  ok(policy);
   is(policy.dataSubmissionPolicyAccepted, false, "Data submission policy not accepted.");
   is(policy.healthReportUploadEnabled, true, "Health Report upload enabled on app first run.");
 
   let checkbox = doc.getElementById("submitHealthReportBox");
   ok(checkbox);
   is(checkbox.checked, true, "Health Report checkbox is checked on app first run.");
 
   checkbox.checked = false;
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -120,16 +120,22 @@ XPCOMUtils.defineLazyServiceGetter(this,
   "@mozilla.org/xre/app-info;1", "nsICrashReporter");
 #endif
 
 function debug(aMsg) {
   aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
   Services.console.logStringMessage(aMsg);
 }
 
+function notifyAsync(aTopic) {
+  Services.tm.mainThread.dispatch(() => {
+    Services.obs.notifyObservers(null, aTopic, "");
+  }, Ci.nsIThread.DISPATCH_NORMAL);
+}
+
 this.SessionStore = {
   get promiseInitialized() {
     return SessionStoreInternal.promiseInitialized.promise;
   },
 
   get canRestoreLastSession() {
     return SessionStoreInternal.canRestoreLastSession;
   },
@@ -759,17 +765,17 @@ let SessionStoreInternal = {
         if (isPrivateWindow) {
           // We're starting with a single private window. Save the state we
           // actually wanted to restore so that we can do it later in case
           // the user opens another, non-private window.
           this._deferredInitialState = this._initialState;
           this._initialState = null;
 
           // Nothing to restore now, notify observers things are complete.
-          Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
+          notifyAsync(NOTIFY_WINDOWS_RESTORED);
         } else {
           TelemetryTimestamps.add("sessionRestoreRestoring");
           // make sure that the restored tabs are first in the window
           this._initialState._firstTabs = true;
           this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0;
           this.restoreWindow(aWindow, this._initialState,
                              this._isCmdLineEmpty(aWindow, this._initialState));
 
@@ -777,17 +783,17 @@ let SessionStoreInternal = {
           // force a save operation so that crashes happening during startup are correctly counted
           this._initialState.session.state = STATE_RUNNING_STR;
           this._saveStateObject(this._initialState);
           this._initialState = null;
         }
       }
       else {
         // Nothing to restore, notify observers things are complete.
-        Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
+        notifyAsync(NOTIFY_WINDOWS_RESTORED);
 
         // the next delayed save request should execute immediately
         this._lastSaveTime -= this._interval;
       }
     }
     // this window was opened by _openWindowWithState
     else if (!this._isWindowLoaded(aWindow)) {
       let followUp = this._statesToRestore[aWindow.__SS_restoreID].windows.length == 1;
@@ -4312,19 +4318,18 @@ let SessionStoreInternal = {
       return;
     }
 
     // observers were already notified
     if (this._restoreCount == -1)
       return;
 
     // This was the last window restored at startup, notify observers.
-    Services.obs.notifyObservers(null,
-      this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED : NOTIFY_WINDOWS_RESTORED,
-      "");
+    notifyAsync(this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED :
+                                        NOTIFY_WINDOWS_RESTORED);
 
     this._browserSetState = false;
     this._restoreCount = -1;
   },
 
    /**
    * Set the given window's busy state
    * @param aWindow the window
--- a/browser/devtools/debugger/test/browser_dbg_pause-exceptions.js
+++ b/browser/devtools/debugger/test/browser_dbg_pause-exceptions.js
@@ -82,17 +82,17 @@ function testWithFrame()
             is(frames.querySelectorAll(".dbg-stackframe").length, 1,
               "Should have one frame.");
 
             is(scopes.children.length, 3, "Should have 3 variable scopes.");
 
             is(innerNodes[0].querySelector(".name").getAttribute("value"), "<exception>",
               "Should have the right property name for the exception.");
 
-            is(innerNodes[0].querySelector(".value").getAttribute("value"), "[object Error]",
+            is(innerNodes[0].querySelector(".value").getAttribute("value"), "Error",
               "Should have the right property value for the exception.");
 
             // Disable pause on exceptions.
             gDebugger.DebuggerView.Options._pauseOnExceptionsItem.setAttribute("checked", "false");
             gDebugger.DebuggerView.Options._togglePauseOnExceptions();
 
             is(gDebugger.Prefs.pauseOnExceptions, false,
               "The pause-on-exceptions pref should have been set to false.");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-05.js
@@ -37,17 +37,17 @@ function testSimpleCall() {
         "Adding a value property shouldn't add any new tree nodes.");
 
 
       testVar.setGrip({ "type": "object", "class": "Window" });
 
       is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 0,
         "Adding type and class properties shouldn't add any new tree nodes.");
 
-      is(testVar.target.querySelector(".value").getAttribute("value"), "[object Window]",
+      is(testVar.target.querySelector(".value").getAttribute("value"), "Window",
         "The information for the variable wasn't set correctly.");
 
 
       testVar.addItems({ "helloWorld": { "value": "hello world", "enumerable": true } });
 
       is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1,
         "A new detail node should have been added in the variable tree.");
 
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-06.js
@@ -123,62 +123,62 @@ function testSimpleCall() {
       is(localScope.target.querySelector(".nonenum").childNodes.length, 6,
         "The localScope doesn't contain all the created variable elements.");
 
       is(localVar5.target.querySelector(".variables-view-element-details").childNodes.length, 8,
         "The localVar5 doesn't contain all the created properties.");
       is(localVar5.get("someProp5").target.querySelector(".variables-view-element-details").childNodes.length, 8,
         "The localVar5.someProp5 doesn't contain all the created properties.");
 
-      is(windowVar.target.querySelector(".value").getAttribute("value"), "[object Window]",
+      is(windowVar.target.querySelector(".value").getAttribute("value"), "Window",
         "The grip information for the windowVar wasn't set correctly.");
-      is(documentVar.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
+      is(documentVar.target.querySelector(".value").getAttribute("value"), "HTMLDocument",
         "The grip information for the documentVar wasn't set correctly.");
 
       is(localVar0.target.querySelector(".value").getAttribute("value"), "42",
         "The grip information for the localVar0 wasn't set correctly.");
       is(localVar1.target.querySelector(".value").getAttribute("value"), "true",
         "The grip information for the localVar1 wasn't set correctly.");
       is(localVar2.target.querySelector(".value").getAttribute("value"), "\"nasu\"",
         "The grip information for the localVar2 wasn't set correctly.");
       is(localVar3.target.querySelector(".value").getAttribute("value"), "undefined",
         "The grip information for the localVar3 wasn't set correctly.");
       is(localVar4.target.querySelector(".value").getAttribute("value"), "null",
         "The grip information for the localVar4 wasn't set correctly.");
-      is(localVar5.target.querySelector(".value").getAttribute("value"), "[object Object]",
+      is(localVar5.target.querySelector(".value").getAttribute("value"), "Object",
         "The grip information for the localVar5 wasn't set correctly.");
 
       is(localVar5.get("someProp0").target.querySelector(".value").getAttribute("value"), "42",
         "The grip information for the localVar0 wasn't set correctly.");
       is(localVar5.get("someProp1").target.querySelector(".value").getAttribute("value"), "true",
         "The grip information for the localVar1 wasn't set correctly.");
       is(localVar5.get("someProp2").target.querySelector(".value").getAttribute("value"), "\"nasu\"",
         "The grip information for the localVar2 wasn't set correctly.");
       is(localVar5.get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined",
         "The grip information for the localVar3 wasn't set correctly.");
       is(localVar5.get("someProp4").target.querySelector(".value").getAttribute("value"), "null",
         "The grip information for the localVar4 wasn't set correctly.");
-      is(localVar5.get("someProp5").target.querySelector(".value").getAttribute("value"), "[object Object]",
+      is(localVar5.get("someProp5").target.querySelector(".value").getAttribute("value"), "Object",
         "The grip information for the localVar5 wasn't set correctly.");
       is(localVar5.get("someUndefined").target.querySelector(".value").getAttribute("value"), "undefined",
         "The grip information for the someUndefined wasn't set correctly.");
       is(localVar5.get("someAccessor").target.querySelector(".value").getAttribute("value"), "",
         "The grip information for the someAccessor wasn't set correctly.");
 
       is(localVar5.get("someProp5").get("someProp0").target.querySelector(".value").getAttribute("value"), "42",
         "The grip information for the sub-localVar0 wasn't set correctly.");
       is(localVar5.get("someProp5").get("someProp1").target.querySelector(".value").getAttribute("value"), "true",
         "The grip information for the sub-localVar1 wasn't set correctly.");
       is(localVar5.get("someProp5").get("someProp2").target.querySelector(".value").getAttribute("value"), "\"nasu\"",
         "The grip information for the sub-localVar2 wasn't set correctly.");
       is(localVar5.get("someProp5").get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined",
         "The grip information for the sub-localVar3 wasn't set correctly.");
       is(localVar5.get("someProp5").get("someProp4").target.querySelector(".value").getAttribute("value"), "null",
         "The grip information for the sub-localVar4 wasn't set correctly.");
-      is(localVar5.get("someProp5").get("someProp5").target.querySelector(".value").getAttribute("value"), "[object Object]",
+      is(localVar5.get("someProp5").get("someProp5").target.querySelector(".value").getAttribute("value"), "Object",
         "The grip information for the sub-localVar5 wasn't set correctly.");
       is(localVar5.get("someProp5").get("someUndefined").target.querySelector(".value").getAttribute("value"), "undefined",
         "The grip information for the sub-someUndefined wasn't set correctly.");
       is(localVar5.get("someProp5").get("someAccessor").target.querySelector(".value").getAttribute("value"), "",
         "The grip information for the sub-someAccessor wasn't set correctly.");
 
       gDebugger.DebuggerController.activeThread.resume(function() {
         closeDebuggerAndFinish();
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
@@ -37,20 +37,20 @@ function testFrameParameters()
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(localNodes.length, 12,
         "The localScope should contain all the created variable elements.");
 
-      is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Window]",
+      is(localNodes[0].querySelector(".value").getAttribute("value"), "Window",
         "Should have the right property value for 'this'.");
 
-      is(localNodes[1].querySelector(".value").getAttribute("value"), "[object Object]",
+      is(localNodes[1].querySelector(".value").getAttribute("value"), "Object",
         "Should have the right property value for 'aArg'.");
 
       is(localNodes[2].querySelector(".value").getAttribute("value"), '"beta"',
         "Should have the right property value for 'bArg'.");
 
       is(localNodes[3].querySelector(".value").getAttribute("value"), "3",
         "Should have the right property value for 'cArg'.");
 
@@ -61,23 +61,23 @@ function testFrameParameters()
         "Should have the right property value for 'eArg'.");
 
       is(localNodes[6].querySelector(".value").getAttribute("value"), "undefined",
         "Should have the right property value for 'fArg'.");
 
       is(localNodes[7].querySelector(".value").getAttribute("value"), "1",
        "Should have the right property value for 'a'.");
 
-      is(localNodes[8].querySelector(".value").getAttribute("value"), "[object Arguments]",
+      is(localNodes[8].querySelector(".value").getAttribute("value"), "Arguments",
         "Should have the right property value for 'arguments'.");
 
-      is(localNodes[9].querySelector(".value").getAttribute("value"), "[object Object]",
+      is(localNodes[9].querySelector(".value").getAttribute("value"), "Object",
        "Should have the right property value for 'b'.");
 
-      is(localNodes[10].querySelector(".value").getAttribute("value"), "[object Object]",
+      is(localNodes[10].querySelector(".value").getAttribute("value"), "Object",
        "Should have the right property value for 'c'.");
 
       resumeAndFinish();
     }}, 0);
   }, false);
 
   EventUtils.sendMouseEvent({ type: "click" },
     content.document.querySelector("button"),
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -38,21 +38,21 @@ function testFrameParameters()
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(localNodes.length + localNonEnums.length, 12,
         "The localScope and localNonEnums should contain all the created variable elements.");
 
-      is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Window]",
+      is(localNodes[0].querySelector(".value").getAttribute("value"), "Window",
         "Should have the right property value for 'this'.");
-      is(localNodes[8].querySelector(".value").getAttribute("value"), "[object Arguments]",
+      is(localNodes[8].querySelector(".value").getAttribute("value"), "Arguments",
         "Should have the right property value for 'arguments'.");
-      is(localNodes[10].querySelector(".value").getAttribute("value"), "[object Object]",
+      is(localNodes[10].querySelector(".value").getAttribute("value"), "Object",
         "Should have the right property value for 'c'.");
 
 
       let gVars = gDebugger.DebuggerView.Variables;
 
       is(gVars.getScopeForNode(
          gVars._list.querySelectorAll(".variables-view-scope")[0]).target,
          gVars._list.querySelectorAll(".variables-view-scope")[0],
@@ -112,55 +112,55 @@ function testFrameParameters()
         if (!thisNode._retrieved ||
             !argumentsNode._retrieved ||
             !cNode._retrieved) {
           return;
         }
         window.clearInterval(intervalID);
 
         is(thisNode.target.querySelector(".value")
-           .getAttribute("value"), "[object Window]",
+           .getAttribute("value"), "Window",
           "Should have the right property value for 'this'.");
 
         is(thisNode.get("window").target.querySelector(".name")
            .getAttribute("value"), "window",
           "Should have the right property name for 'window'.");
-        ok(thisNode.get("window").target.querySelector(".value")
-           .getAttribute("value").search(/object/) != -1,
+        is(thisNode.get("window").target.querySelector(".value")
+           .getAttribute("value"), "Window",
           "'window' should be an object.");
 
         is(thisNode.get("document").target.querySelector(".name")
            .getAttribute("value"), "document",
           "Should have the right property name for 'document'.");
-        ok(thisNode.get("document").target.querySelector(".value")
-           .getAttribute("value").search(/object/) != -1,
+        is(thisNode.get("document").target.querySelector(".value")
+           .getAttribute("value"), "HTMLDocument",
           "'document' should be an object.");
 
 
         is(argumentsNode.target.querySelector(".value")
-           .getAttribute("value"), "[object Arguments]",
+           .getAttribute("value"), "Arguments",
           "Should have the right property value for 'arguments'.");
 
         is(argumentsNode.target.querySelectorAll(".variables-view-property > .title > .name")[0]
            .getAttribute("value"), "0",
           "Should have the right property name for 'arguments[0]'.");
-        ok(argumentsNode.target.querySelectorAll(".variables-view-property > .title > .value")[0]
-           .getAttribute("value").search(/object/) != -1,
+        is(argumentsNode.target.querySelectorAll(".variables-view-property > .title > .value")[0]
+           .getAttribute("value"), "Object",
           "'arguments[0]' should be an object.");
 
         is(argumentsNode.target.querySelectorAll(".variables-view-property > .title > .name")[7]
            .getAttribute("value"), "__proto__",
           "Should have the right property name for '__proto__'.");
-        ok(argumentsNode.target.querySelectorAll(".variables-view-property > .title > .value")[7]
-           .getAttribute("value").search(/object/) != -1,
+        is(argumentsNode.target.querySelectorAll(".variables-view-property > .title > .value")[7]
+           .getAttribute("value"), "Object",
           "'__proto__' should be an object.");
 
 
         is(cNode.target.querySelector(".value")
-           .getAttribute("value"), "[object Object]",
+           .getAttribute("value"), "Object",
           "Should have the right property value for 'c'.");
 
         is(cNode.target.querySelectorAll(".variables-view-property > .title > .name")[0]
            .getAttribute("value"), "a",
           "Should have the right property name for 'c.a'.");
         is(cNode.target.querySelectorAll(".variables-view-property > .title > .value")[0]
            .getAttribute("value"), "1",
           "Should have the right value for 'c.a'.");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
@@ -48,33 +48,33 @@ function testFrameParameters()
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(globalNodes[1].querySelector(".name").getAttribute("value"), "SpecialPowers",
         "Should have the right property name for |SpecialPowers|.");
 
-      is(globalNodes[1].querySelector(".value").getAttribute("value"), "[object Object]",
+      is(globalNodes[1].querySelector(".value").getAttribute("value"), "Object",
         "Should have the right property value for |SpecialPowers|.");
 
       let globalScopeObject = gDebugger.DebuggerView.Variables.getScopeForNode(globalScope);
       let documentNode = globalScopeObject.get("document");
 
       is(documentNode.target.querySelector(".name").getAttribute("value"), "document",
         "Should have the right property name for |document|.");
 
-      is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
+      is(documentNode.target.querySelector(".value").getAttribute("value"), "HTMLDocument",
         "Should have the right property value for |document|.");
 
       let len = globalNodes.length - 1;
       is(globalNodes[len].querySelector(".name").getAttribute("value"), "window",
         "Should have the right property name for |window|.");
 
-      is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Window]",
+      is(globalNodes[len].querySelector(".value").getAttribute("value"), "Window",
         "Should have the right property value for |window|.");
 
       resumeAndFinish();
     }}, 0);
   }, false);
 
   EventUtils.sendMouseEvent({ type: "click" },
     content.document.querySelector("button"),
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
@@ -62,24 +62,24 @@ function testWithFrame()
         "Should have the right property value for |one|.");
 
       let globalScopeObject = gDebugger.DebuggerView.Variables.getScopeForNode(globalScope);
       let documentNode = globalScopeObject.get("document");
 
       is(documentNode.target.querySelector(".name").getAttribute("value"), "document",
         "Should have the right property name for |document|.");
 
-      is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
+      is(documentNode.target.querySelector(".value").getAttribute("value"), "HTMLDocument",
         "Should have the right property value for |document|.");
 
       let len = globalNodes.length - 1;
       is(globalNodes[len].querySelector(".name").getAttribute("value"), "window",
         "Should have the right property name for |window|.");
 
-      is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Window]",
+      is(globalNodes[len].querySelector(".value").getAttribute("value"), "Window",
         "Should have the right property value for |window|.");
 
       resumeAndFinish();
     }}, 0);
   }, false);
 
   EventUtils.sendMouseEvent({ type: "click" },
     content.document.querySelector("button"),
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-11.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-11.js
@@ -47,32 +47,32 @@ function testFrameParameters()
 
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(anonymousNodes[1].querySelector(".name").getAttribute("value"), "button",
         "Should have the right property name for |button|.");
 
-      is(anonymousNodes[1].querySelector(".value").getAttribute("value"), "[object HTMLButtonElement]",
+      is(anonymousNodes[1].querySelector(".value").getAttribute("value"), "HTMLButtonElement",
         "Should have the right property value for |button|.");
 
       is(anonymousNodes[2].querySelector(".name").getAttribute("value"), "buttonAsProto",
         "Should have the right property name for |buttonAsProto|.");
 
-      is(anonymousNodes[2].querySelector(".value").getAttribute("value"), "[object Object]",
+      is(anonymousNodes[2].querySelector(".value").getAttribute("value"), "Object",
         "Should have the right property value for |buttonAsProto|.");
 
       let globalScopeObject = gVars.getScopeForNode(globalScope);
       let documentNode = globalScopeObject.get("document");
 
       is(documentNode.target.querySelector(".name").getAttribute("value"), "document",
         "Should have the right property name for |document|.");
 
-      is(documentNode.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
+      is(documentNode.target.querySelector(".value").getAttribute("value"), "HTMLDocument",
         "Should have the right property value for |document|.");
 
       let buttonNode = gVars.getItemForNode(anonymousNodes[1]);
       let buttonAsProtoNode = gVars.getItemForNode(anonymousNodes[2]);
 
       is(buttonNode.expanded, false,
         "The buttonNode should not be expanded at this point.");
       is(buttonAsProtoNode.expanded, false,
@@ -111,31 +111,31 @@ function testFrameParameters()
         }
         window.clearInterval(intervalID);
 
         // Test the prototypes of these objects.
         is(buttonNode.get("__proto__").target.querySelector(".name")
            .getAttribute("value"), "__proto__",
           "Should have the right property name for '__proto__' in buttonNode.");
         ok(buttonNode.get("__proto__").target.querySelector(".value")
-           .getAttribute("value").search(/object/) != -1,
+           .getAttribute("value"), "HTMLButtonElement",
           "'__proto__' in buttonNode should be an object.");
 
         is(buttonAsProtoNode.get("__proto__").target.querySelector(".name")
            .getAttribute("value"), "__proto__",
           "Should have the right property name for '__proto__' in buttonAsProtoNode.");
         ok(buttonAsProtoNode.get("__proto__").target.querySelector(".value")
-           .getAttribute("value").search(/object/) != -1,
+           .getAttribute("value"), "HTMLButtonElement",
           "'__proto__' in buttonAsProtoNode should be an object.");
 
         is(documentNode.get("__proto__").target.querySelector(".name")
            .getAttribute("value"), "__proto__",
           "Should have the right property name for '__proto__' in documentNode.");
         ok(documentNode.get("__proto__").target.querySelector(".value")
-           .getAttribute("value").search(/object/) != -1,
+           .getAttribute("value"), "HTMLDocument",
           "'__proto__' in documentNode should be an object.");
 
         // Now the main course: make sure that the native getters for WebIDL
         // attributes have been called and a value has been returned.
         is(buttonNode.get("type").target.querySelector(".name")
            .getAttribute("value"), "type",
           "Should have the right property name for 'type' in buttonProtoNode.");
         is(buttonNode.get("type").target.querySelector(".value")
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-data-big.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-data-big.js
@@ -47,23 +47,23 @@ function testWithFrame()
           loadScope = scopes.querySelectorAll(".variables-view-scope")[1],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[2],
           innerNodes = innerScope.querySelector(".variables-view-element-details").childNodes,
           arrayNodes = innerNodes[4].querySelector(".variables-view-element-details").childNodes;
 
       is(innerNodes[3].querySelector(".name").getAttribute("value"), "buffer",
         "Should have the right property name for |buffer|.");
 
-      is(innerNodes[3].querySelector(".value").getAttribute("value"), "[object ArrayBuffer]",
+      is(innerNodes[3].querySelector(".value").getAttribute("value"), "ArrayBuffer",
         "Should have the right property value for |buffer|.");
 
       is(innerNodes[4].querySelector(".name").getAttribute("value"), "z",
         "Should have the right property name for |z|.");
 
-      is(innerNodes[4].querySelector(".value").getAttribute("value"), "[object Int8Array]",
+      is(innerNodes[4].querySelector(".value").getAttribute("value"), "Int8Array",
         "Should have the right property value for |z|.");
 
 
       EventUtils.sendMouseEvent({ type: "mousedown" }, innerNodes[3].querySelector(".arrow"), gDebugger);
       EventUtils.sendMouseEvent({ type: "mousedown" }, innerNodes[4].querySelector(".arrow"), gDebugger);
 
       gDebugger.addEventListener("Debugger:FetchedProperties", function test2() {
         gDebugger.removeEventListener("Debugger:FetchedProperties", test2, false);
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-edit-value.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-edit-value.js
@@ -48,18 +48,18 @@ function testFrameEval() {
       is(varA.querySelector(".value").getAttribute("value"), 1,
         "Should have the right initial value for 'a'.");
 
       testModification(varA, function(aVar) {
         testModification(aVar, function(aVar) {
           testModification(aVar, function(aVar) {
             resumeAndFinish();
           }, "document.title", '"Debugger Function Call Parameter Test"');
-        }, "b", "[object Object]");
-      }, "{ a: 1 }", "[object Object]");
+        }, "b", "Object");
+      }, "{ a: 1 }", "Object");
     }}, 0);
   }, false);
 
   EventUtils.sendMouseEvent({ type: "click" },
     content.document.querySelector("button"),
     content.window);
 }
 
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -260,17 +260,18 @@
                    class="tabpanel-summary-input"
                    multiline="true"
                    rows="6"
                    wrap="off"
                    oninput="NetMonitorView.CustomRequest.onUpdate('body');"/>
         </vbox>
       </vbox>
       <tabbox id="event-details-pane"
-              class="devtools-sidebar-tabs">
+              class="devtools-sidebar-tabs"
+              handleCtrlTab="false">
         <tabs>
           <tab label="&netmonitorUI.tab.headers;"/>
           <tab label="&netmonitorUI.tab.cookies;"/>
           <tab label="&netmonitorUI.tab.params;"/>
           <tab label="&netmonitorUI.tab.response;"/>
           <tab label="&netmonitorUI.tab.timings;"/>
         </tabs>
         <tabpanels flex="1">
--- a/browser/devtools/netmonitor/test/browser_net_content-type.js
+++ b/browser/devtools/netmonitor/test/browser_net_content-type.js
@@ -173,17 +173,17 @@ function test() {
             is(jsonScope.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
               "greeting", "The first json property name was incorrect.");
             is(jsonScope.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
               "\"Hello JSON!\"", "The first json property value was incorrect.");
 
             is(jsonScope.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
               "__proto__", "The second json property name was incorrect.");
             is(jsonScope.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
-              "[object Object]", "The second json property value was incorrect.");
+              "Object", "The second json property value was incorrect.");
 
             return Promise.resolve();
           }
           case "html": {
             checkVisibility("textarea");
 
             return NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
               is(aEditor.getText(), "<blink>Not Found</blink>",
--- a/browser/devtools/netmonitor/test/browser_net_json-long.js
+++ b/browser/devtools/netmonitor/test/browser_net_json-long.js
@@ -72,25 +72,25 @@ function test() {
 
         is(jsonScope.querySelector(".name").getAttribute("value"),
           L10N.getStr("jsonScopeName"),
           "The json scope doesn't have the correct title.");
 
         is(jsonScope.querySelectorAll(names)[0].getAttribute("value"),
           "0", "The first json property name was incorrect.");
         is(jsonScope.querySelectorAll(values)[0].getAttribute("value"),
-          "[object Object]", "The first json property value was incorrect.");
+          "Object", "The first json property value was incorrect.");
 
         is(jsonScope.querySelectorAll(names)[1].getAttribute("value"),
           "greeting", "The second json property name was incorrect.");
         is(jsonScope.querySelectorAll(values)[1].getAttribute("value"),
           "\"Hello long string JSON!\"", "The second json property value was incorrect.");
 
         is(Array.slice(jsonScope.querySelectorAll(names), -1).shift().getAttribute("value"),
           "__proto__", "The last json property name was incorrect.");
         is(Array.slice(jsonScope.querySelectorAll(values), -1).shift().getAttribute("value"),
-          "[object Object]", "The last json property value was incorrect.");
+          "Object", "The last json property value was incorrect.");
       }
     });
 
     aDebuggee.performRequests();
   });
 }
--- a/browser/devtools/netmonitor/test/browser_net_jsonp.js
+++ b/browser/devtools/netmonitor/test/browser_net_jsonp.js
@@ -69,15 +69,15 @@ function test() {
         is(jsonScope.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
           "greeting", "The first json property name was incorrect.");
         is(jsonScope.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
           "\"Hello JSONP!\"", "The first json property value was incorrect.");
 
         is(jsonScope.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
           "__proto__", "The second json property name was incorrect.");
         is(jsonScope.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
-          "[object Object]", "The second json property value was incorrect.");
+          "Object", "The second json property value was incorrect.");
       }
     });
 
     aDebuggee.performRequests();
   });
 }
--- a/browser/devtools/netmonitor/test/sjs_status-codes-test-server.sjs
+++ b/browser/devtools/netmonitor/test/sjs_status-codes-test-server.sjs
@@ -1,27 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+const { classes: Cc, interfaces: Ci } = Components;
+
 function handleRequest(request, response) {
+  response.processAsync();
+
   let params = request.queryString.split("&");
   let status = params.filter((s) => s.contains("sts="))[0].split("=")[1];
 
-  switch (status) {
-    case "100":
-      response.setStatusLine(request.httpVersion, 101, "Switching Protocols");
-      break;
-    case "200":
-      response.setStatusLine(request.httpVersion, 202, "Created");
-      break;
-    case "300":
-      response.setStatusLine(request.httpVersion, 303, "See Other");
-      break;
-    case "400":
-      response.setStatusLine(request.httpVersion, 404, "Not Found");
-      break;
-    case "500":
-      response.setStatusLine(request.httpVersion, 501, "Not Implemented");
-      break;
-  }
-  response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
-  response.write("Hello status code " + status + "!");
+  Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer).initWithCallback(() => {
+    switch (status) {
+      case "100":
+        response.setStatusLine(request.httpVersion, 101, "Switching Protocols");
+        break;
+      case "200":
+        response.setStatusLine(request.httpVersion, 202, "Created");
+        break;
+      case "300":
+        response.setStatusLine(request.httpVersion, 303, "See Other");
+        break;
+      case "400":
+        response.setStatusLine(request.httpVersion, 404, "Not Found");
+        break;
+      case "500":
+        response.setStatusLine(request.httpVersion, 501, "Not Implemented");
+        break;
+    }
+    response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+    response.write("Hello status code " + status + "!");
+    response.finish();
+  }, 10, Ci.nsITimer.TYPE_ONE_SHOT); // Make sure this request takes a few ms.
 }
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -2242,17 +2242,17 @@ Variable.prototype = Heritage.extend(Sco
       aGrip = NetworkHelper.convertToUnicode(unescape(aGrip));
     }
 
     let prevGrip = this._valueGrip;
     if (prevGrip) {
       this._valueLabel.classList.remove(VariablesView.getClass(prevGrip));
     }
     this._valueGrip = aGrip;
-    this._valueString = VariablesView.getString(aGrip);
+    this._valueString = VariablesView.getString(aGrip, true);
     this._valueClassName = VariablesView.getClass(aGrip);
 
     this._valueLabel.classList.add(this._valueClassName);
     this._valueLabel.setAttribute("value", this._valueString);
   },
 
   /**
    * Initializes this variable's id, view and binds event listeners.
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -118,21 +118,23 @@ const DEFAULT_KEYBINDINGS = [
     action: "Comment/Uncomment",
     code: Ci.nsIDOMKeyEvent.DOM_VK_SLASH,
     accel: true,
   },
   {
     action: "Move to Bracket Opening",
     code: Ci.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET,
     accel: true,
+    alt: true,
   },
   {
     action: "Move to Bracket Closing",
     code: Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET,
     accel: true,
+    alt: true,
   },
 ];
 
 if (Services.appinfo.OS == "WINNT" ||
     Services.appinfo.OS == "Linux") {
   DEFAULT_KEYBINDINGS.push({
     action: "redo",
     code: Ci.nsIDOMKeyEvent.DOM_VK_Y,
--- a/browser/devtools/sourceeditor/test/browser_bug729960_block_bracket_jump.js
+++ b/browser/devtools/sourceeditor/test/browser_bug729960_block_bracket_jump.js
@@ -48,114 +48,114 @@ function test() {
                  "  function bar() { /* Block Level 2 */ }\n" +
                  "}";
 
     editor.setMode(SourceEditor.MODES.JAVASCRIPT);
     editor.setText(JSText);
 
     // Setting caret at Line 1 bracket start.
     editor.setCaretOffset(19);
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 220,
        "JS : Jump to closing bracket of the code block when caret at block start");
 
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 20,
        "JS : Jump to opening bracket of the code block when caret at block end");
 
     // Setting caret at Line 10 start.
     editor.setCaretOffset(161);
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 20,
        "JS : Jump to opening bracket of code block when inside the function");
 
     editor.setCaretOffset(161);
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 220,
        "JS : Jump to closing bracket of code block when inside the function");
 
     // Setting caret at Line 6 start.
     editor.setCaretOffset(67);
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 159,
        "JS : Jump to closing bracket in a nested function with caret inside");
 
     editor.setCaretOffset(67);
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 62,
        "JS : Jump to opening bracket in a nested function with caret inside");
 
     let CSSText = "#object {\n" +
                   "  property: value;\n" +
                   "  /* comment */\n" +
                   "}";
 
     editor.setMode(SourceEditor.MODES.CSS);
     editor.setText(CSSText);
 
     // Setting caret at Line 1 bracket start.
     editor.setCaretOffset(8);
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 45,
        "CSS : Jump to closing bracket of the code block when caret at block start");
 
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 9,
        "CSS : Jump to opening bracket of the code block when caret at block end");
 
     // Setting caret at Line 3 start.
     editor.setCaretOffset(28);
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 9,
        "CSS : Jump to opening bracket of code block when inside the function");
 
     editor.setCaretOffset(28);
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 45,
        "CSS : Jump to closing bracket of code block when inside the function");
 
     let HTMLText = "<html>\n" +
                    "  <head>\n" +
                    "    <title>Testing Block Jump</title>\n" +
                    "  </head>\n" +
                    "  <body></body>\n" +
                    "</html>";
 
     editor.setMode(SourceEditor.MODES.HTML);
     editor.setText(HTMLText);
 
     // Setting caret at Line 1 end.
     editor.setCaretOffset(6);
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 6,
        "HTML : Jump to block end : Nothing happens in html mode");
 
     // Setting caret at Line 4 end.
     editor.setCaretOffset(64);
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 64,
        "HTML : Jump to block start : Nothing happens in html mode");
 
     let text = "line 1\n" +
                "line 2\n" +
                "line 3\n" +
                "line 4\n";
 
     editor.setMode(SourceEditor.MODES.TEXT);
     editor.setText(text);
 
     // Setting caret at Line 1 start.
     editor.setCaretOffset(0);
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 0,
        "Text : Jump to block end : Nothing happens in text mode");
 
     // Setting caret at Line 4 end.
     editor.setCaretOffset(28);
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 28,
        "Text : Jump to block start : Nothing happens in text mode");
 
     editor.destroy();
 
     testWin.close();
     testWin = editor = null;
 
--- a/browser/devtools/sourceeditor/test/browser_bug744021_next_prev_bracket_jump.js
+++ b/browser/devtools/sourceeditor/test/browser_bug744021_next_prev_bracket_jump.js
@@ -52,50 +52,50 @@ function test() {
                  "  \n" +
                  "}";
 
     editor.setMode(SourceEditor.MODES.JAVASCRIPT);
     editor.setText(JSText);
 
     // Setting caret at end of line 11 (function baz() {).
     editor.setCaretOffset(147);
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 16,
        "JS : Jump to opening bracket of previous sibling block when no parent");
 
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 129,
        "JS : Jump to closing bracket of same code block");
 
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 151,
        "JS : Jump to closing bracket of next sibling code block");
 
     let CSSText = "#object1 {\n" +
                   "  property: value;\n" +
                   "  /* comment */\n" +
                   "}\n" +
                   ".class1 {\n" +
                   "  property: value;\n" +
                   "}";
 
     editor.setMode(SourceEditor.MODES.CSS);
     editor.setText(CSSText);
 
     // Setting caret at Line 5 end (.class1 {).
     editor.setCaretOffset(57);
-    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("[", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 10,
        "CSS : Jump to opening bracket of previous sibling code block");
 
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 46,
        "CSS : Jump to closing bracket of same code block");
 
-    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    EventUtils.synthesizeKey("]", {accelKey: true, altKey: true}, testWin);
     is(editor.getCaretOffset(), 77,
        "CSS : Jump to closing bracket of next sibling code block");
 
     editor.destroy();
 
     testWin.close();
     testWin = editor = null;
 
--- a/browser/devtools/webconsole/test/browser_jsterm_inspect.js
+++ b/browser/devtools/webconsole/test/browser_jsterm_inspect.js
@@ -24,12 +24,12 @@ function test()
   }
 
   function onObjFetch(aEvent, aVar)
   {
     ok(aVar._variablesView, "variables view object");
 
     findVariableViewProperties(aVar, [
       { name: "testProp", value: "testValue" },
-      { name: "document", value: "[object HTMLDocument]" },
+      { name: "document", value: "HTMLDocument" },
     ], { webconsole: hud }).then(finishTest);
   }
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -95,12 +95,12 @@ function testPropertyPanel()
     },
     failureFn: finishTest,
   });
 }
 
 function onVariablesViewReady(aEvent, aView)
 {
   findVariableViewProperties(aView, [
-    { name: "body", value: "[object HTMLBodyElement]" },
+    { name: "body", value: "HTMLBodyElement" },
   ], { webconsole: gHUD }).then(finishTest);
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js
@@ -17,13 +17,13 @@ function test() {
 
 function consoleOpened(hud) {
   hud.jsterm.execute("console.dir(document)");
   hud.jsterm.once("variablesview-fetched", testConsoleDir.bind(null, hud));
 }
 
 function testConsoleDir(hud, ev, view) {
   findVariableViewProperties(view, [
-    { name: "__proto__.__proto__.querySelectorAll", value: "[object Function]" },
-    { name: "location", value: "[object Location]" },
-    { name: "__proto__.write", value: "[object Function]" },
+    { name: "__proto__.__proto__.querySelectorAll", value: "Function" },
+    { name: "location", value: "Location" },
+    { name: "__proto__.write", value: "Function" },
   ], { webconsole: hud }).then(finishTest);
 }
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -566,17 +566,17 @@ function matchVariablesViewProperty(aPro
     let isGetter = !!(aProp.getter && aProp.get("get"));
     if (aRule.isGetter != isGetter) {
       info("rule " + aRule.name + " getter test failed");
       return resolve(false);
     }
   }
 
   if ("isGenerator" in aRule) {
-    let isGenerator = aProp.displayValue == "[object Generator]";
+    let isGenerator = aProp.displayValue == "Generator";
     if (aRule.isGenerator != isGenerator) {
       info("rule " + aRule.name + " generator test failed");
       return resolve(false);
     }
   }
 
   let outstanding = [];
 
@@ -607,17 +607,17 @@ function matchVariablesViewProperty(aPro
  *        The WebConsole instance to work with.
  * @return object
  *         A Promise that is resolved when the check completes. The resolved
  *         callback is given a boolean: true if the property is an iterator, or
  *         false otherwise.
  */
 function isVariableViewPropertyIterator(aProp, aWebConsole)
 {
-  if (aProp.displayValue == "[object Iterator]") {
+  if (aProp.displayValue == "Iterator") {
     return Promise.resolve(true);
   }
 
   let deferred = Promise.defer();
 
   variablesViewExpandTo({
     rootVariable: aProp,
     expandTo: "__proto__.__iterator__",
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.8.229
+Current extension version is: 0.8.291
 
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -198,37 +198,43 @@ let PdfJs = {
     // the 'application/pdf' handler is selected as internal?
     var handlerInfo = Svc.mime
                          .getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf');
     if (handlerInfo.alwaysAskBeforeHandling ||
         handlerInfo.preferredAction !== Ci.nsIHandlerInfo.handleInternally) {
       return false;
     }
 
-    // we also need to check if pdf plugin is not present or disabled...
+    // Check if we have disabled plugin handling of 'application/pdf' in prefs
+    if (Services.prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
+      let disabledPluginTypes =
+        Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES).split(',');
+      if (disabledPluginTypes.indexOf(PDF_CONTENT_TYPE) >= 0) {
+        return true;
+      }
+    }
+
+    // Check if there is an enabled pdf plugin.
+    // Note: this check is performed last because getPluginTags() triggers costly
+    // plugin list initialization (bug 881575)
     let tags = Cc["@mozilla.org/plugin/host;1"].
                   getService(Ci.nsIPluginHost).
                   getPluginTags();
     let enabledPluginFound = tags.some(function(tag) {
       if (tag.disabled) {
         return false;
       }
       let mimeTypes = tag.getMimeTypes();
       return mimeTypes.some(function(mimeType) {
         return mimeType.type === PDF_CONTENT_TYPE;
       });
     });
-    if (!enabledPluginFound) {
-      return true; // no plugins for this type, it's good
-    }
-    // ... and full page plugins list must have 'application/pdf' type,
-    // in case when enabled pdf plugin exists.
-    return Services.prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES) ?
-      (Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES).split(',').
-      indexOf(PDF_CONTENT_TYPE) >= 0) : false;
+
+    // Use pdf.js if pdf plugin is not present or disabled
+    return !enabledPluginFound;
   },
 
   _ensureRegistered: function _ensureRegistered() {
     if (this._registered)
       return;
 
     this._pdfStreamConverterFactory = new Factory();
     this._pdfStreamConverterFactory.register(PdfStreamConverter);
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -10,19 +10,23 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-var PDFJS = {};
-PDFJS.version = '0.8.229';
-PDFJS.build = 'b996e1b';
+// Initializing PDFJS global object (if still undefined)
+if (typeof PDFJS === 'undefined') {
+  (typeof window !== 'undefined' ? window : this).PDFJS = {};
+}
+
+PDFJS.version = '0.8.291';
+PDFJS.build = '4e83123';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -161,16 +165,22 @@ var ChunkedStream = (function ChunkedStr
       if (end > strEnd)
         end = strEnd;
       this.ensureRange(pos, end);
 
       this.pos = end;
       return bytes.subarray(pos, end);
     },
 
+    peekBytes: function ChunkedStream_peekBytes(length) {
+      var bytes = this.getBytes(length);
+      this.pos -= bytes.length;
+      return bytes;
+    },
+
     getByteRange: function ChunkedStream_getBytes(begin, end) {
       this.ensureRange(begin, end);
       return this.bytes.subarray(begin, end);
     },
 
     lookChar: function ChunkedStream_lookChar() {
       var pos = this.pos;
       if (pos >= this.end)
@@ -702,17 +712,16 @@ var Page = (function PageClosure() {
 
   function Page(pdfManager, xref, pageIndex, pageDict, ref) {
     this.pdfManager = pdfManager;
     this.pageIndex = pageIndex;
     this.pageDict = pageDict;
     this.xref = xref;
     this.ref = ref;
     this.idCounters = {
-      font: 0,
       obj: 0
     };
     this.resourcesPromise = null;
   }
 
   Page.prototype = {
     getPageProp: function Page_getPageProp(key) {
       return this.pageDict.get(key);
@@ -1798,185 +1807,226 @@ function isPDFFunction(v) {
   else if (isStream(v))
     fnDict = v.dict;
   else
     return false;
   return fnDict.has('FunctionType');
 }
 
 /**
- * 'Promise' object.
- * Each object that is stored in PDFObjects is based on a Promise object that
- * contains the status of the object and the data. There might be situations
- * where a function wants to use the value of an object, but it isn't ready at
- * that time. To get a notification, once the object is ready to be used, s.o.
- * can add a callback using the `then` method on the promise that then calls
- * the callback once the object gets resolved.
- * A promise can get resolved only once and only once the data of the promise
- * can be set. If any of these happens twice or the data is required before
- * it was set, an exception is throw.
+ * The following promise implementation tries to generally implment the
+ * Promise/A+ spec. Some notable differences from other promise libaries are:
+ * - There currently isn't a seperate deferred and promise object.
+ * - Unhandled rejections eventually show an error if they aren't handled.
+ *
+ * Based off of the work in:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
  */
 var Promise = PDFJS.Promise = (function PromiseClosure() {
-  var EMPTY_PROMISE = {};
-
-  /**
-   * If `data` is passed in this constructor, the promise is created resolved.
-   * If there isn't data, it isn't resolved at the beginning.
-   */
-  function Promise(name, data) {
-    this.name = name;
-    this.isRejected = false;
-    this.error = null;
-    this.exception = null;
-    // If you build a promise and pass in some data it's already resolved.
-    if (data !== null && data !== undefined) {
-      this.isResolved = true;
-      this._data = data;
-      this.hasData = true;
-    } else {
-      this.isResolved = false;
-      this._data = EMPTY_PROMISE;
-    }
-    this.callbacks = [];
-    this.errbacks = [];
-    this.progressbacks = [];
+  var STATUS_PENDING = 0;
+  var STATUS_RESOLVED = 1;
+  var STATUS_REJECTED = 2;
+
+  // In an attempt to avoid silent exceptions, unhandled rejections are
+  // tracked and if they aren't handled in a certain amount of time an
+  // error is logged.
+  var REJECTION_TIMEOUT = 500;
+
+  var HandlerManager = {
+    handlers: [],
+    running: false,
+    unhandledRejections: [],
+    pendingRejectionCheck: false,
+
+    scheduleHandlers: function scheduleHandlers(promise) {
+      if (promise._status == STATUS_PENDING) {
+        return;
+      }
+
+      this.handlers = this.handlers.concat(promise._handlers);
+      promise._handlers = [];
+
+      if (this.running) {
+        return;
+      }
+      this.running = true;
+
+      setTimeout(this.runHandlers.bind(this), 0);
+    },
+
+    runHandlers: function runHandlers() {
+      while (this.handlers.length > 0) {
+        var handler = this.handlers.shift();
+
+        var nextStatus = handler.thisPromise._status;
+        var nextValue = handler.thisPromise._value;
+
+        try {
+          if (nextStatus === STATUS_RESOLVED) {
+            if (typeof(handler.onResolve) == 'function') {
+              nextValue = handler.onResolve(nextValue);
+            }
+          } else if (typeof(handler.onReject) === 'function') {
+              nextValue = handler.onReject(nextValue);
+              nextStatus = STATUS_RESOLVED;
+
+              if (handler.thisPromise._unhandledRejection) {
+                this.removeUnhandeledRejection(handler.thisPromise);
+              }
+          }
+        } catch (ex) {
+          nextStatus = STATUS_REJECTED;
+          nextValue = ex;
+        }
+
+        handler.nextPromise._updateStatus(nextStatus, nextValue);
+      }
+
+      this.running = false;
+    },
+
+    addUnhandledRejection: function addUnhandledRejection(promise) {
+      this.unhandledRejections.push({
+        promise: promise,
+        time: Date.now()
+      });
+      this.scheduleRejectionCheck();
+    },
+
+    removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
+      promise._unhandledRejection = false;
+      for (var i = 0; i < this.unhandledRejections.length; i++) {
+        if (this.unhandledRejections[i].promise === promise) {
+          this.unhandledRejections.splice(i);
+          i--;
+        }
+      }
+    },
+
+    scheduleRejectionCheck: function scheduleRejectionCheck() {
+      if (this.pendingRejectionCheck) {
+        return;
+      }
+      this.pendingRejectionCheck = true;
+      setTimeout(function rejectionCheck() {
+        this.pendingRejectionCheck = false;
+        var now = Date.now();
+        for (var i = 0; i < this.unhandledRejections.length; i++) {
+          if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
+            warn('Unhandled rejection: ' +
+                 this.unhandledRejections[i].promise._value);
+            this.unhandledRejections.splice(i);
+            i--;
+          }
+        }
+        if (this.unhandledRejections.length) {
+          this.scheduleRejectionCheck();
+        }
+      }.bind(this), REJECTION_TIMEOUT);
+    }
+  };
+
+  function Promise() {
+    this._status = STATUS_PENDING;
+    this._handlers = [];
   }
   /**
    * Builds a promise that is resolved when all the passed in promises are
    * resolved.
    * @param {Promise[]} promises Array of promises to wait for.
    * @return {Promise} New dependant promise.
    */
   Promise.all = function Promise_all(promises) {
     var deferred = new Promise();
     var unresolved = promises.length;
     var results = [];
     if (unresolved === 0) {
       deferred.resolve(results);
       return deferred;
     }
     function reject(reason) {
-      if (deferred.isRejected) {
+      if (deferred._status === STATUS_REJECTED) {
         return;
       }
       results = [];
       deferred.reject(reason);
     }
     for (var i = 0, ii = promises.length; i < ii; ++i) {
       var promise = promises[i];
       promise.then((function(i) {
         return function(value) {
-          if (deferred.isRejected) {
+          if (deferred._status === STATUS_REJECTED) {
             return;
           }
           results[i] = value;
           unresolved--;
           if (unresolved === 0)
             deferred.resolve(results);
         };
       })(i), reject);
     }
     return deferred;
   };
+
   Promise.prototype = {
-    hasData: false,
-
-    set data(value) {
-      if (value === undefined) {
+    _status: null,
+    _value: null,
+    _handlers: null,
+    _unhandledRejection: null,
+
+    _updateStatus: function Promise__updateStatus(status, value) {
+      if (this._status === STATUS_RESOLVED ||
+          this._status === STATUS_REJECTED) {
+        return;
+      }
+
+      if (status == STATUS_RESOLVED &&
+          value && typeof(value.then) === 'function') {
+        value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
+                   this._updateStatus.bind(this, STATUS_REJECTED));
         return;
       }
-      if (this._data !== EMPTY_PROMISE) {
-        error('Promise ' + this.name +
-              ': Cannot set the data of a promise twice');
-      }
-      this._data = value;
-      this.hasData = true;
-
-      if (this.onDataCallback) {
-        this.onDataCallback(value);
-      }
-    },
-
-    get data() {
-      if (this._data === EMPTY_PROMISE) {
-        error('Promise ' + this.name + ': Cannot get data that isn\'t set');
-      }
-      return this._data;
-    },
-
-    onData: function Promise_onData(callback) {
-      if (this._data !== EMPTY_PROMISE) {
-        callback(this._data);
-      } else {
-        this.onDataCallback = callback;
-      }
-    },
-
-    resolve: function Promise_resolve(data) {
-      if (this.isResolved) {
-        error('A Promise can be resolved only once ' + this.name);
-      }
-      if (this.isRejected) {
-        error('The Promise was already rejected ' + this.name);
-      }
-
-      this.isResolved = true;
-      this.data = (typeof data !== 'undefined') ? data : null;
-      var callbacks = this.callbacks;
-
-      for (var i = 0, ii = callbacks.length; i < ii; i++) {
-        callbacks[i].call(null, data);
-      }
-    },
-
-    progress: function Promise_progress(data) {
-      var callbacks = this.progressbacks;
-      for (var i = 0, ii = callbacks.length; i < ii; i++) {
-        callbacks[i].call(null, data);
-      }
-    },
-
-    reject: function Promise_reject(reason, exception) {
-      if (this.isRejected) {
-        error('A Promise can be rejected only once ' + this.name);
-      }
-      if (this.isResolved) {
-        error('The Promise was already resolved ' + this.name);
-      }
-
-      this.isRejected = true;
-      this.error = reason || null;
-      this.exception = exception || null;
-      var errbacks = this.errbacks;
-
-      for (var i = 0, ii = errbacks.length; i < ii; i++) {
-        errbacks[i].call(null, reason, exception);
-      }
-    },
-
-    then: function Promise_then(callback, errback, progressback) {
-      // If the promise is already resolved, call the callback directly.
-      if (this.isResolved && callback) {
-        var data = this.data;
-        callback.call(null, data);
-      } else if (this.isRejected && errback) {
-        var error = this.error;
-        var exception = this.exception;
-        errback.call(null, error, exception);
-      } else {
-        if (callback) {
-          this.callbacks.push(callback);
-        }
-        if (errback) {
-          this.errbacks.push(errback);
-        }
-      }
-
-      if (progressback)
-        this.progressbacks.push(progressback);
+
+      this._status = status;
+      this._value = value;
+
+      if (status === STATUS_REJECTED && this._handlers.length === 0) {
+        this._unhandledRejection = true;
+        HandlerManager.addUnhandledRejection(this);
+      }
+
+      HandlerManager.scheduleHandlers(this);
+    },
+
+    get isResolved() {
+      return this._status === STATUS_RESOLVED;
+    },
+
+    get isRejected() {
+      return this._status === STATUS_REJECTED;
+    },
+
+    resolve: function Promise_resolve(value) {
+      this._updateStatus(STATUS_RESOLVED, value);
+    },
+
+    reject: function Promise_reject(reason) {
+      this._updateStatus(STATUS_REJECTED, reason);
+    },
+
+    then: function Promise_then(onResolve, onReject) {
+      var nextPromise = new Promise();
+      this._handlers.push({
+        thisPromise: this,
+        onResolve: onResolve,
+        onReject: onReject,
+        nextPromise: nextPromise
+      });
+      HandlerManager.scheduleHandlers(this);
+      return nextPromise;
     }
   };
 
   return Promise;
 })();
 
 var StatTimer = (function StatTimerClosure() {
   function rpad(str, pad, length) {
@@ -2063,17 +2113,18 @@ PDFJS.createBlob = function createBlob(d
  * password if wrong or no password was provided. The callback receives two
  * parameters: function that needs to be called with new password and reason
  * (see {PasswordResponses}).
  *
  * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
  */
 PDFJS.getDocument = function getDocument(source,
                                          pdfDataRangeTransport,
-                                         passwordCallback) {
+                                         passwordCallback,
+                                         progressCallback) {
   var workerInitializedPromise, workerReadyPromise, transport;
 
   if (typeof source === 'string') {
     source = { url: source };
   } else if (isArrayBuffer(source)) {
     source = { data: source };
   } else if (typeof source !== 'object') {
     error('Invalid parameter in getDocument, need either Uint8Array, ' +
@@ -2091,17 +2142,17 @@ PDFJS.getDocument = function getDocument
       continue;
     }
     params[key] = source[key];
   }
 
   workerInitializedPromise = new PDFJS.Promise();
   workerReadyPromise = new PDFJS.Promise();
   transport = new WorkerTransport(workerInitializedPromise,
-      workerReadyPromise, pdfDataRangeTransport);
+      workerReadyPromise, pdfDataRangeTransport, progressCallback);
   workerInitializedPromise.then(function transportInitialized() {
     transport.passwordCallback = passwordCallback;
     transport.fetchDocument(params);
   });
   return workerReadyPromise;
 };
 
 /**
@@ -2365,27 +2416,19 @@ var PDFPageProxy = (function PDFPageProx
      * For internal use only.
      */
     startRenderingFromOperatorList:
       function PDFPageProxy_startRenderingFromOperatorList(operatorList,
                                                            fonts) {
       var self = this;
       this.operatorList = operatorList;
 
-      var displayContinuation = function pageDisplayContinuation() {
-        // Always defer call to display() to work around bug in
-        // Firefox error reporting from XHR callbacks.
-        setTimeout(function pageSetTimeout() {
-          self.displayReadyPromise.resolve();
-        });
-      };
-
       this.ensureFonts(fonts,
         function pageStartRenderingFromOperatorListEnsureFonts() {
-          displayContinuation();
+          self.displayReadyPromise.resolve();
         }
       );
     },
     /**
      * For internal use only.
      */
     ensureFonts: function PDFPageProxy_ensureFonts(fonts, callback) {
       this.stats.time('Font Loading');
@@ -2495,20 +2538,21 @@ var PDFPageProxy = (function PDFPageProx
   };
   return PDFPageProxy;
 })();
 /**
  * For internal use only.
  */
 var WorkerTransport = (function WorkerTransportClosure() {
   function WorkerTransport(workerInitializedPromise, workerReadyPromise,
-      pdfDataRangeTransport) {
+      pdfDataRangeTransport, progressCallback) {
     this.pdfDataRangeTransport = pdfDataRangeTransport;
 
     this.workerReadyPromise = workerReadyPromise;
+    this.progressCallback = progressCallback;
     this.commonObjs = new PDFObjects();
 
     this.pageCache = [];
     this.pagePromises = [];
     this.embeddedFontsUsed = false;
 
     this.passwordCallback = null;
 
@@ -2720,24 +2764,22 @@ var WorkerTransport = (function WorkerTr
             }
             break;
           default:
             error('Got unknown object type ' + type);
         }
       }, this);
 
       messageHandler.on('DocProgress', function transportDocProgress(data) {
-        // TODO(mack): The progress event should be resolved on a different
-        // promise that tracks progress of whole file, since workerReadyPromise
-        // is for file being ready to render, not for when file is fully
-        // downloaded
-        this.workerReadyPromise.progress({
-          loaded: data.loaded,
-          total: data.total
-        });
+        if (this.progressCallback) {
+          this.progressCallback({
+            loaded: data.loaded,
+            total: data.total
+          });
+        }
       }, this);
 
       messageHandler.on('DocError', function transportDocError(data) {
         this.workerReadyPromise.reject(data);
       }, this);
 
       messageHandler.on('PageError', function transportError(data) {
         var page = this.pageCache[data.pageNum - 1];
@@ -3005,161 +3047,156 @@ var CachedCanvases = (function CachedCan
     }
   };
 })();
 
 function compileType3Glyph(imgData) {
   var POINT_TO_PROCESS_LIMIT = 1000;
 
   var width = imgData.width, height = imgData.height;
-  var i, j;
-  // we need sparse arrays
-  var points = [];
-  for (i = 0; i <= height; i++) {
-    points.push([]);
-  }
-
+  var i, j, j0, width1 = width + 1;
+  var points = new Uint8Array(width1 * (height + 1));
+  var POINT_TYPES = 
+      new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
   // finding iteresting points: every point is located between mask pixels,
   // so there will be points of the (width + 1)x(height + 1) grid. Every point
   // will have flags assigned based on neighboring mask pixels:
   //   4 | 8
   //   --P--
   //   2 | 1
   // We are interested only in points with the flags:
   //   - outside corners: 1, 2, 4, 8;
   //   - inside corners: 7, 11, 13, 14;
   //   - and, intersections: 5, 10.
   var pos = 3, data = imgData.data, lineSize = width * 4, count = 0;
   if (data[3] !== 0) {
-    points[0][0] = 1;
+    points[0] = 1;
     ++count;
   }
   for (j = 1; j < width; j++) {
     if (data[pos] !== data[pos + 4]) {
-      points[0][j] = data[pos] ? 2 : 1;
+      points[j] = data[pos] ? 2 : 1;
       ++count;
     }
     pos += 4;
   }
   if (data[pos] !== 0) {
-    points[0][j] = 2;
+    points[j] = 2;
     ++count;
   }
   pos += 4;
   for (i = 1; i < height; i++) {
+    j0 = i * width1;
     if (data[pos - lineSize] !== data[pos]) {
-      points[i][0] = data[pos] ? 1 : 8;
+      points[j0] = data[pos] ? 1 : 8;
       ++count;
     }
+    // 'sum' is the position of the current pixel configuration in the 'TYPES'
+    // array (in order 8-1-2-4, so we can use '>>2' to shift the column).
+    var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
     for (j = 1; j < width; j++) {
-      var f1 = data[pos + 4] ? 1 : 0;
-      var f2 = data[pos] ? 1 : 0;
-      var f4 = data[pos - lineSize] ? 1 : 0;
-      var f8 = data[pos - lineSize + 4] ? 1 : 0;
-      var fSum = f1 + f2 + f4 + f8;
-      if (fSum === 1 || fSum === 3 || (fSum === 2 && f1 === f4)) {
-        points[i][j] = f1 | (f2 << 1) | (f4 << 2) | (f8 << 3);
+      sum = (sum >> 2) + (data[pos + 4] ? 4 : 0) +
+            (data[pos - lineSize + 4] ? 8 : 0);
+      if (POINT_TYPES[sum]) { 
+        points[j0 + j] = POINT_TYPES[sum];
         ++count;
       }
       pos += 4;
     }
     if (data[pos - lineSize] !== data[pos]) {
-      points[i][j] = data[pos] ? 2 : 4;
+      points[j0 + j] = data[pos] ? 2 : 4;
       ++count;
     }
     pos += 4;
 
     if (count > POINT_TO_PROCESS_LIMIT) {
       return null;
     }
   }
+
   pos -= lineSize;
+  j0 = i * width1;
   if (data[pos] !== 0) {
-    points[i][0] = 8;
+    points[j0] = 8;
     ++count;
   }
   for (j = 1; j < width; j++) {
     if (data[pos] !== data[pos + 4]) {
-      points[i][j] = data[pos] ? 4 : 8;
+      points[j0 + j] = data[pos] ? 4 : 8;
       ++count;
     }
     pos += 4;
   }
   if (data[pos] !== 0) {
-    points[i][j] = 4;
+    points[j0 + j] = 4;
     ++count;
   }
   if (count > POINT_TO_PROCESS_LIMIT) {
     return null;
   }
 
   // building outlines
-  var outline = [];
-  outline.push('c.save();');
-  // the path shall be painted in [0..1]x[0..1] space
-  outline.push('c.scale(' + (1 / width) + ',' +  (-1 / height) + ');');
-  outline.push('c.translate(0,-' + height + ');');
-  outline.push('c.beginPath();');
-  for (i = 0; i <= height; i++) {
-    if (points[i].length === 0) {
+  var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
+  var outlines = [];
+  for (i = 0; count && i <= height; i++) {
+    var p = i * width1;
+    var end = p + width;
+    while (p < end && !points[p]) {
+      p++;
+    }
+    if (p === end) {
       continue;
     }
-    var js = null;
-    for (js in points[i]) {
-      break;
-    }
-    if (js === null) {
-      continue;
-    }
-    var i0 = i, j0 = (j = +js);
-
-    outline.push('c.moveTo(' + j + ',' + i + ');');
-    var type = points[i][j], d = 0;
+    var coords = [p % width1, i];
+
+    var type = points[p], p0 = p, pp;
     do {
-      if (type === 5 || type === 10) {
-        // line crossed: following dirrection we followed
-        points[i0][j0] = type | (15 ^ d); // changing direction for "future hit"
-        type |= d;
-      }
-
-      switch (type) {
-      case 1:
-      case 13:
-        do { i0++; } while (!points[i0][j0]);
-        d = 9;
-        break;
-      case 4:
-      case 7:
-        do { i0--; } while (!points[i0][j0]);
-        d = 6;
-        break;
-      case 8:
-      case 14:
-        do { j0++; } while (!points[i0][j0]);
-        d = 12;
-        break;
-      case 2:
-      case 11:
-        do { j0--; } while (!points[i0][j0]);
-        d = 3;
-        break;
-      }
-      outline.push('c.lineTo(' + j0 + ',' + i0 + ');');
-
-      type = points[i0][j0];
-      delete points[i0][j0];
-    } while (j0 !== j || i0 !== i);
+      var step = steps[type];
+      do { p += step; } while (!points[p]);
+      
+      pp = points[p];
+      if (pp !== 5 && pp !== 10) {
+        // set new direction
+        type = pp; 
+        // delete mark
+        points[p] = 0; 
+      } else { // type is 5 or 10, ie, a crossing
+        // set new direction
+        type = pp & ((0x33 * type) >> 4); 
+        // set new type for "future hit"
+        points[p] &= (type >> 2 | type << 2);
+      }
+
+      coords.push(p % width1);
+      coords.push((p / width1) | 0);
+      --count;
+    } while (p0 !== p);
+    outlines.push(coords);
     --i;
   }
-  outline.push('c.fill();');
-  outline.push('c.beginPath();');
-  outline.push('c.restore();');
-
-  /*jshint -W054 */
-  return new Function('c', outline.join('\n'));
+
+  var drawOutline = function(c) {
+    c.save();
+    // the path shall be painted in [0..1]x[0..1] space
+    c.scale(1 / width, -1 / height);
+    c.translate(0, -height);
+    c.beginPath();
+    for (var i = 0, ii = outlines.length; i < ii; i++) {
+      var o = outlines[i];
+      c.moveTo(o[0], o[1]);
+      for (var j = 2, jj = o.length; j < jj; j += 2) {
+        c.lineTo(o[j], o[j+1]);
+      }
+    }
+    c.fill();
+    c.beginPath();
+    c.restore();
+  };
+  
+  return drawOutline;
 }
 
 var CanvasExtraState = (function CanvasExtraStateClosure() {
   function CanvasExtraState(old) {
     // Are soft masks and alpha values shapes or opacities?
     this.alphaIsShape = false;
     this.fontSize = 0;
     this.fontSizeScale = 1;
@@ -4851,16 +4888,38 @@ var RefSet = (function RefSetClosure() {
     remove: function RefSet_remove(ref) {
       delete this.dict['R' + ref.num + '.' + ref.gen];
     }
   };
 
   return RefSet;
 })();
 
+var RefSetCache = (function RefSetCacheClosure() {
+  function RefSetCache() {
+    this.dict = {};
+  }
+
+  RefSetCache.prototype = {
+    get: function RefSetCache_get(ref) {
+      return this.dict['R' + ref.num + '.' + ref.gen];
+    },
+
+    has: function RefSetCache_has(ref) {
+      return ('R' + ref.num + '.' + ref.gen) in this.dict;
+    },
+
+    put: function RefSetCache_put(ref, obj) {
+      this.dict['R' + ref.num + '.' + ref.gen] = obj;
+    }
+  };
+
+  return RefSetCache;
+})();
+
 var Catalog = (function CatalogClosure() {
   function Catalog(pdfManager, xref) {
     this.pdfManager = pdfManager;
     this.xref = xref;
     this.catDict = xref.getCatalogObj();
     assertWellFormed(isDict(this.catDict),
       'catalog object is not a dictionary');
 
@@ -4908,16 +4967,28 @@ var Catalog = (function CatalogClosure()
     },
     get toplevelPagesDict() {
       var pagesObj = this.catDict.get('Pages');
       assertWellFormed(isDict(pagesObj), 'invalid top-level pages dictionary');
       // shadow the prototype getter
       return shadow(this, 'toplevelPagesDict', pagesObj);
     },
     get documentOutline() {
+      var obj = null;
+      try {
+        obj = this.readDocumentOutline();
+      } catch (ex) {
+        if (ex instanceof MissingDataException) {
+          throw ex;
+        }
+        warn('Unable to read document outline');
+      }
+      return shadow(this, 'documentOutline', obj);
+    },
+    readDocumentOutline: function Catalog_readDocumentOutline() {
       var xref = this.xref;
       var obj = this.catDict.get('Outlines');
       var root = { items: [] };
       if (isDict(obj)) {
         obj = obj.getRaw('First');
         var processed = new RefSet();
         if (isRef(obj)) {
           var queue = [{obj: obj, parent: root}];
@@ -4958,18 +5029,17 @@ var Catalog = (function CatalogClosure()
             obj = outlineDict.getRaw('Next');
             if (isRef(obj) && !processed.has(obj)) {
               queue.push({obj: obj, parent: i.parent});
               processed.put(obj);
             }
           }
         }
       }
-      obj = root.items.length > 0 ? root.items : null;
-      return shadow(this, 'documentOutline', obj);
+      return root.items.length > 0 ? root.items : null;
     },
     get numPages() {
       var obj = this.toplevelPagesDict.get('Count');
       assertWellFormed(
         isInt(obj),
         'page count in top level pages object is not an integer'
       );
       // shadow the prototype getter
@@ -5240,16 +5310,22 @@ var XRef = (function XRefClosure() {
         tableState.entryNum = 0;
         tableState.streamPos = stream.pos;
         tableState.parserBuf1 = parser.buf1;
         tableState.parserBuf2 = parser.buf2;
         delete tableState.firstEntryNum;
         delete tableState.entryCount;
       }
 
+      // Per issue 3248: hp scanners generate bad XRef
+      if (first === 1 && this.entries[1] && this.entries[1].free) {
+        // shifting the entries
+        this.entries.shift();
+      }
+
       // Sanity check: as per spec, first object must be free
       if (this.entries[0] && !this.entries[0].free)
         error('Invalid XRef table: unexpected first object');
 
       return obj;
     },
 
     processXRefStream: function XRef_processXRefStream(stream) {
@@ -5633,17 +5709,17 @@ var XRef = (function XRefClosure() {
         entries.push(parser.getObj());
         num = nums[i];
         var entry = this.entries[num];
         if (entry && entry.offset === tableOffset && entry.gen === i) {
           this.cache[num] = entries[i];
         }
       }
       e = entries[e.gen];
-      if (!e) {
+      if (e === undefined) {
         error('bad XRef entry for compressed object');
       }
       return e;
     },
     fetchIfRefAsync: function XRef_fetchIfRefAsync(obj) {
       if (!isRef(obj)) {
         var promise = new Promise();
         promise.resolve(obj);
@@ -5736,111 +5812,98 @@ var NameTree = (function NameTreeClosure
 var PDFObjects = (function PDFObjectsClosure() {
   function PDFObjects() {
     this.objs = {};
   }
 
   PDFObjects.prototype = {
     /**
      * Internal function.
-     * Ensures there is an object defined for `objId`. Stores `data` on the
-     * object *if* it is created.
-     */
-    ensureObj: function PDFObjects_ensureObj(objId, data) {
+     * Ensures there is an object defined for `objId`.
+     */
+    ensureObj: function PDFObjects_ensureObj(objId) {
       if (this.objs[objId])
         return this.objs[objId];
-      return this.objs[objId] = new Promise(objId, data);
+
+      var obj = {
+        promise: new Promise(objId),
+        data: null,
+        resolved: false
+      };
+      this.objs[objId] = obj;
+
+      return obj;
     },
 
     /**
      * If called *without* callback, this returns the data of `objId` but the
      * object needs to be resolved. If it isn't, this function throws.
      *
      * If called *with* a callback, the callback is called with the data of the
      * object once the object is resolved. That means, if you call this
      * function and the object is already resolved, the callback gets called
      * right away.
      */
     get: function PDFObjects_get(objId, callback) {
       // If there is a callback, then the get can be async and the object is
       // not required to be resolved right now
       if (callback) {
-        this.ensureObj(objId).then(callback);
+        this.ensureObj(objId).promise.then(callback);
         return null;
       }
 
       // If there isn't a callback, the user expects to get the resolved data
       // directly.
       var obj = this.objs[objId];
 
       // If there isn't an object yet or the object isn't resolved, then the
       // data isn't ready yet!
-      if (!obj || !obj.isResolved)
+      if (!obj || !obj.resolved)
         error('Requesting object that isn\'t resolved yet ' + objId);
 
       return obj.data;
     },
 
     /**
      * Resolves the object `objId` with optional `data`.
      */
     resolve: function PDFObjects_resolve(objId, data) {
-      var objs = this.objs;
-
-      // In case there is a promise already on this object, just resolve it.
-      if (objs[objId]) {
-        objs[objId].resolve(data);
-      } else {
-        this.ensureObj(objId, data);
-      }
-    },
-
-    onData: function PDFObjects_onData(objId, callback) {
-      this.ensureObj(objId).onData(callback);
+      var obj = this.ensureObj(objId);
+
+      obj.resolved = true;
+      obj.data = data;
+      obj.promise.resolve(data);
     },
 
     isResolved: function PDFObjects_isResolved(objId) {
       var objs = this.objs;
+
       if (!objs[objId]) {
         return false;
       } else {
-        return objs[objId].isResolved;
+        return objs[objId].resolved;
       }
     },
 
     hasData: function PDFObjects_hasData(objId) {
-      var objs = this.objs;
-      if (!objs[objId]) {
-        return false;
-      } else {
-        return objs[objId].hasData;
-      }
+      return this.isResolved(objId);
     },
 
     /**
      * Returns the data of `objId` if object exists, null otherwise.
      */
     getData: function PDFObjects_getData(objId) {
       var objs = this.objs;
-      if (!objs[objId] || !objs[objId].hasData) {
+      if (!objs[objId] || !objs[objId].resolved) {
         return null;
       } else {
         return objs[objId].data;
       }
     },
 
-    /**
-     * Sets the data of an object but *doesn't* resolve it.
-     */
-    setData: function PDFObjects_setData(objId, data) {
-      // Watchout! If you call `this.ensureObj(objId, data)` you're going to
-      // create a *resolved* promise which shouldn't be the case!
-      this.ensureObj(objId).data = data;
-    },
-
     clear: function PDFObjects_clear() {
       this.objs = {};
     }
   };
   return PDFObjects;
 })();
 
 /**
@@ -6097,16 +6160,20 @@ var Annotation = (function AnnotationClo
          !(data.annotationFlags & 0x22)) && // Hidden or NoView
         data.rect                            // rectangle is nessessary
       );
     },
 
     loadResources: function(keys) {
       var promise = new Promise();
       this.appearance.dict.getAsync('Resources').then(function(resources) {
+        if (!resources) {
+          promise.resolve();
+          return;
+        }
         var objectLoader = new ObjectLoader(resources.map,
                                             keys,
                                             resources.xref);
         objectLoader.load().then(function() {
           promise.resolve(resources);
         });
       }.bind(this));
 
@@ -15675,17 +15742,17 @@ var AES128Cipher = (function AES128Ciphe
   }
 
   function AES128Cipher(key) {
     this.key = expandKey128(key);
     this.buffer = new Uint8Array(16);
     this.bufferPosition = 0;
   }
 
-  function decryptBlock2(data) {
+  function decryptBlock2(data, finalize) {
     var i, j, ii, sourceLength = data.length,
         buffer = this.buffer, bufferLength = this.bufferPosition,
         result = [], iv = this.iv;
     for (i = 0; i < sourceLength; ++i) {
       buffer[bufferLength] = data[i];
       ++bufferLength;
       if (bufferLength < 16)
         continue;
@@ -15698,69 +15765,75 @@ var AES128Cipher = (function AES128Ciphe
       result.push(plain);
       buffer = new Uint8Array(16);
       bufferLength = 0;
     }
     // saving incomplete buffer
     this.buffer = buffer;
     this.bufferLength = bufferLength;
     this.iv = iv;
-    if (result.length === 0)
+    if (result.length === 0) {
       return new Uint8Array([]);
-    if (result.length == 1)
-      return result[0];
+    }
     // combining plain text blocks into one
-    var output = new Uint8Array(16 * result.length);
+    var outputLength = 16 * result.length;
+    if (finalize) {
+      // undo a padding that is described in RFC 2898
+      var lastBlock = result[result.length - 1];
+      outputLength -= lastBlock[15];
+      result[result.length - 1] = lastBlock.subarray(0, 16 - lastBlock[15]);
+    }
+    var output = new Uint8Array(outputLength);
     for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16)
       output.set(result[i], j);
     return output;
   }
 
   AES128Cipher.prototype = {
-    decryptBlock: function AES128Cipher_decryptBlock(data) {
+    decryptBlock: function AES128Cipher_decryptBlock(data, finalize) {
       var i, sourceLength = data.length;
       var buffer = this.buffer, bufferLength = this.bufferPosition;
       // waiting for IV values -- they are at the start of the stream
       for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength)
         buffer[bufferLength] = data[i];
       if (bufferLength < 16) {
         // need more data
         this.bufferLength = bufferLength;
         return new Uint8Array([]);
       }
       this.iv = buffer;
       this.buffer = new Uint8Array(16);
       this.bufferLength = 0;
       // starting decryption
       this.decryptBlock = decryptBlock2;
-      return this.decryptBlock(data.subarray(16));
+      return this.decryptBlock(data.subarray(16), finalize);
     }
   };
 
   return AES128Cipher;
 })();
 
 var CipherTransform = (function CipherTransformClosure() {
   function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
     this.stringCipherConstructor = stringCipherConstructor;
     this.streamCipherConstructor = streamCipherConstructor;
   }
   CipherTransform.prototype = {
     createStream: function CipherTransform_createStream(stream) {
       var cipher = new this.streamCipherConstructor();
       return new DecryptStream(stream,
-        function cipherTransformDecryptStream(data) {
-          return cipher.decryptBlock(data);
+        function cipherTransformDecryptStream(data, finalize) {
+          return cipher.decryptBlock(data, finalize);
         }
       );
     },
     decryptString: function CipherTransform_decryptString(s) {
       var cipher = new this.stringCipherConstructor();
       var data = stringToBytes(s);
-      data = cipher.decryptBlock(data);
+      data = cipher.decryptBlock(data, true);
       return bytesToString(data);
     }
   };
   return CipherTransform;
 })();
 
 var CipherTransformFactory = (function CipherTransformFactoryClosure() {
   var defaultPasswordBytes = new Uint8Array([
@@ -16003,16 +16076,17 @@ var PartialEvaluator = (function Partial
     this.stateStack = [];
 
     this.pdfManager = pdfManager;
     this.xref = xref;
     this.handler = handler;
     this.pageIndex = pageIndex;
     this.uniquePrefix = uniquePrefix;
     this.idCounters = idCounters;
+    this.fontCache = new RefSetCache();
   }
 
   // Specifies properties for each command
   //
   // If variableArgs === true: [0, `numArgs`] expected
   // If variableArgs === false: exactly `numArgs` expected
   var OP_MAP = {
     // Graphic state
@@ -16090,17 +16164,17 @@ var PartialEvaluator = (function Partial
     k: { fnName: 'setFillCMYKColor', numArgs: 4, variableArgs: false },
 
     // Shading
     sh: { fnName: 'shadingFill', numArgs: 1, variableArgs: false },
 
     // Images
     BI: { fnName: 'beginInlineImage', numArgs: 0, variableArgs: false },
     ID: { fnName: 'beginImageData', numArgs: 0, variableArgs: false },
-    EI: { fnName: 'endInlineImage', numArgs: 0, variableArgs: false },
+    EI: { fnName: 'endInlineImage', numArgs: 1, variableArgs: false },
 
     // XObjects
     Do: { fnName: 'paintXObject', numArgs: 1, variableArgs: false },
     MP: { fnName: 'markPoint', numArgs: 1, variableArgs: false },
     DP: { fnName: 'markPointProps', numArgs: 2, variableArgs: false },
     BMC: { fnName: 'beginMarkedContent', numArgs: 1, variableArgs: false },
     BDC: { fnName: 'beginMarkedContentProps', numArgs: 2, variableArgs: false },
     EMC: { fnName: 'endMarkedContent', numArgs: 0, variableArgs: false },
@@ -16470,48 +16544,55 @@ var PartialEvaluator = (function Partial
         promise.resolve(opListData);
       });
 
       return promise;
     },
 
     loadFont: function PartialEvaluator_loadFont(fontName, font, xref,
                                                  resources) {
-      var promise = new Promise();
-
-      var fontRes = resources.get('Font');
-      if (!fontRes) {
-        warn('fontRes not available');
-      }
-
-      font = xref.fetchIfRef(font) || (fontRes && fontRes.get(fontName));
-      if (!isDict(font)) {
-        ++this.idCounters.font;
+      function errorFont(promise) {
         promise.resolve({
           font: {
             translated: new ErrorFont('Font ' + fontName + ' is not available'),
-            loadedName: 'g_font_' + this.uniquePrefix + this.idCounters.obj
+            loadedName: 'g_font_error'
           },
           dependencies: {}
         });
         return promise;
       }
 
-      if (font.loaded) {
-        promise.resolve({
-          font: font,
-          dependencies: {}
-        });
-        return promise;
+      var fontRef;
+      if (font) { // Loading by ref.
+        assert(isRef(font));
+        fontRef = font;
+      } else { // Loading by name.
+        var fontRes = resources.get('Font');
+        if (fontRes) {
+          fontRef = fontRes.getRaw(fontName);
+        } else {
+          warn('fontRes not available');
+          return errorFont(new Promise());
+        }
+      }
+      if (this.fontCache.has(fontRef)) {
+        return this.fontCache.get(fontRef);
+      }
+
+      var promise = new Promise();
+      this.fontCache.put(fontRef, promise);
+
+      font = xref.fetchIfRef(fontRef);
+      if (!isDict(font)) {
+        return errorFont(promise);
       }
 
       // keep track of each font we translated so the caller can
       // load them asynchronously before calling display on a page
-      font.loadedName = 'g_font_' + this.uniquePrefix +
-                        (this.idCounters.font + 1);
+      font.loadedName = 'g_font_' + fontRef.num + '_' + fontRef.gen;
 
       if (!font.translated) {
         var translated;
         try {
           translated = this.translateFont(font, xref);
         } catch (e) {
           translated = new ErrorFont(e instanceof Error ? e.message : e);
         }
@@ -16535,24 +16616,22 @@ var PartialEvaluator = (function Partial
           for (var i = 0, n = charProcKeys.length; i < n; ++i) {
             var key = charProcKeys[i];
             var data = datas[i];
             charProcOperatorList[key] = data.queue;
             Util.extendObj(dependencies, data.dependencies);
           }
           font.translated.charProcOperatorList = charProcOperatorList;
           font.loaded = true;
-          ++this.idCounters.font;
           promise.resolve({
             font: font,
             dependencies: dependencies
           });
         }.bind(this));
       } else {
-        ++this.idCounters.font;
         font.loaded = true;
         promise.resolve({
           font: font,
           dependencies: {}
         });
       }
       return promise;
     },
@@ -17570,17 +17649,17 @@ var PartialEvaluator = (function Partial
         var count = Math.min((i - j) >> 2,
                              MAX_IMAGES_IN_MASKS_BLOCK);
         if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
           continue;
         }
         var images = [];
         for (var q = 0; q < count; q++) {
           var transform = argsArray[j + (q << 2) + 1];
-          var maskParams = argsArray[j + (q << 2) + 2];
+          var maskParams = argsArray[j + (q << 2) + 2][0];
           images.push({data: maskParams.data, width: maskParams.width,
             height: maskParams.height, transform: transform});
         }
         // replacing queue items
         fnArray.splice(j, count * 4, ['paintImageMaskXObjectGroup']);
         argsArray.splice(j, count * 4, [images]);
         i = j;
         ii = fnArray.length;
@@ -20743,17 +20822,18 @@ var Font = (function FontClosure() {
           for (var i = 0, ii = metrics.length; i < ii; i++)
             entries += String.fromCharCode(font.getByte());
           for (var i = 0; i < numMissing; i++)
             entries += '\x00\x00';
           metrics.data = stringToArray(entries);
         }
       }
 
-      function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart) {
+      function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart,
+                             hintsValid) {
         if (sourceEnd - sourceStart <= 12) {
           // glyph with data less than 12 is invalid one
           return 0;
         }
         var glyf = source.subarray(sourceStart, sourceEnd);
         var contoursCount = (glyf[0] << 8) | glyf[1];
         if (contoursCount & 0x8000) {
           // complex glyph, writing as is
@@ -20763,18 +20843,20 @@ var Font = (function FontClosure() {
 
         var j = 10, flagsCount = 0;
         for (var i = 0; i < contoursCount; i++) {
           var endPoint = (glyf[j] << 8) | glyf[j + 1];
           flagsCount = endPoint + 1;
           j += 2;
         }
         // skipping instructions
+        var instructionsStart = j;
         var instructionsLength = (glyf[j] << 8) | glyf[j + 1];
         j += 2 + instructionsLength;
+        var instructionsEnd = j;
         // validating flags
         var coordinatesLength = 0;
         for (var i = 0; i < flagsCount; i++) {
           var flag = glyf[j++];
           if (flag & 0xC0) {
             // reserved flags must be zero, rejecting
             return 0;
           }
@@ -20787,16 +20869,27 @@ var Font = (function FontClosure() {
             coordinatesLength += repeat * xyLength;
           }
         }
         var glyphDataLength = j + coordinatesLength;
         if (glyphDataLength > glyf.length) {
           // not enough data for coordinates
           return 0;
         }
+        if (!hintsValid && instructionsLength > 0) {
+          dest.set(glyf.subarray(0, instructionsStart), destStart);
+          dest.set([0, 0], destStart + instructionsStart);
+          dest.set(glyf.subarray(instructionsEnd, glyphDataLength),
+                   destStart + instructionsStart + 2);
+          glyphDataLength -= instructionsLength;
+          if (glyf.length - glyphDataLength > 3) {
+            glyphDataLength = (glyphDataLength + 3) & ~3;
+          }
+          return glyphDataLength;
+        }
         if (glyf.length - glyphDataLength > 3) {
           // truncating and aligning to 4 bytes the long glyph data
           glyphDataLength = (glyphDataLength + 3) & ~3;
           dest.set(glyf.subarray(0, glyphDataLength), destStart);
           return glyphDataLength;
         }
         // glyph data is fine
         dest.set(glyf, destStart);
@@ -20843,17 +20936,17 @@ var Font = (function FontClosure() {
             data[51] = 1;
           } else {
             warn('Could not fix indexToLocFormat: ' + indexToLocFormat);
           }
         }
       }
 
       function sanitizeGlyphLocations(loca, glyf, numGlyphs,
-                                      isGlyphLocationsLong) {
+                                      isGlyphLocationsLong, hintsValid) {
         var itemSize, itemDecode, itemEncode;
         if (isGlyphLocationsLong) {
           itemSize = 4;
           itemDecode = function fontItemDecodeLong(data, offset) {
             return (data[offset] << 24) | (data[offset + 1] << 16) |
                    (data[offset + 2] << 8) | data[offset + 3];
           };
           itemEncode = function fontItemEncodeLong(data, offset, value) {
@@ -20885,17 +20978,17 @@ var Font = (function FontClosure() {
           if (endOffset > oldGlyfDataLength) {
             // glyph end offset points outside glyf data, rejecting the glyph
             itemEncode(locaData, j, writeOffset);
             startOffset = endOffset;
             continue;
           }
 
           var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
-                                        newGlyfData, writeOffset);
+                                        newGlyfData, writeOffset, hintsValid);
           writeOffset += newLength;
           itemEncode(locaData, j, writeOffset);
           startOffset = endOffset;
         }
 
         if (writeOffset === 0) {
           // glyf table cannot be empty -- redoing the glyf and loca tables
           // to have single glyph with one point
@@ -21050,17 +21143,17 @@ var Font = (function FontClosure() {
         }
         return names;
       }
 
       var TTOpsStackDeltas = [
         0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5,
         -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1,
         1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1,
-        0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -2, 0, -2, -2,
+        0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2,
         0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1,
         -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1,
         -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1,
         -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
         // 0xC0-DF == -1 and 0xE0-FF == -2
 
       function sanitizeTTProgram(table, ttContext) {
@@ -21203,43 +21296,32 @@ var Font = (function FontClosure() {
           warn('TT: complementing a missing function tail');
           // new function definition started, but not finished
           // complete function by [CLEAR, ENDF]
           content.push(new Uint8Array([0x22, 0x2D]));
         }
         foldTTTable(table, content);
       }
 
-      function addTTDummyFunctions(table, ttContext, maxFunctionDefs) {
-        var content = [table.data];
-        if (!ttContext.tooComplexToFollowFunctions) {
-          var undefinedFunctions = [];
-          for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
-            if (!ttContext.functionsUsed[j] || ttContext.functionsDefined[j]) {
-              continue;
-            }
-            undefinedFunctions.push(j);
-            if (j >= maxFunctionDefs) {
-              continue;
-            }
-            // function is used, but not defined
-            if (j < 256) {
-              // creating empty one [PUSHB, function-id, FDEF, ENDF]
-              content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D]));
-            } else {
-              // creating empty one [PUSHW, function-id, FDEF, ENDF]
-              content.push(
-                new Uint8Array([0xB8, j >> 8, j & 255, 0x2C, 0x2D]));
-            }
-          }
-          if (undefinedFunctions.length > 0) {
-            warn('TT: undefined functions: ' + undefinedFunctions);
-          }
-        }
-        foldTTTable(table, content);
+      function checkInvalidFunctions(ttContext, maxFunctionDefs) {
+        if (ttContext.tooComplexToFollowFunctions) {
+          return;
+        }
+        for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
+          if (j > maxFunctionDefs) {
+            warn('TT: invalid function id: ' + j);
+            ttContext.hintsValid = false;
+            return;
+          }
+          if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
+            warn('TT: undefined function: ' + j);
+            ttContext.hintsValid = false;
+            return;
+          }
+        }
       }
 
       function foldTTTable(table, content) {
         if (content.length > 1) {
           // concatenating the content items
           var newLength = 0;
           for (var j = 0, jj = content.length; j < jj; j++) {
             newLength += content[j].length;
@@ -21256,27 +21338,29 @@ var Font = (function FontClosure() {
         }
       }
 
       function sanitizeTTPrograms(fpgm, prep) {
         var ttContext = {
           functionsDefined: [],
           functionsUsed: [],
           functionsStackDeltas: [],
-          tooComplexToFollowFunctions: false
+          tooComplexToFollowFunctions: false,
+          hintsValid: true
         };
         if (fpgm) {
           sanitizeTTProgram(fpgm, ttContext);
         }
         if (prep) {
           sanitizeTTProgram(prep, ttContext);
         }
         if (fpgm) {
-          addTTDummyFunctions(fpgm, ttContext, maxFunctionDefs);
-        }
+          checkInvalidFunctions(ttContext, maxFunctionDefs);
+        }
+        return ttContext.hintsValid;
       }
 
       // The following steps modify the original font data, making copy
       font = new Stream(new Uint8Array(font.getBytes()));
 
       // Check that required tables are present
       var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
                              'hmtx', 'maxp', 'name', 'post'];
@@ -21321,52 +21405,59 @@ var Font = (function FontClosure() {
           else if (table.tag == 'CFF ')
             return null; // XXX: OpenType font is found, stopping
           else // skipping table if it's not a required or optional table
             continue;
         }
         tables.push(table);
       }
 
+      // Ensure the hmtx table contains the advance width and
+      // sidebearings information for numGlyphs in the maxp table
+      font.pos = (font.start || 0) + maxp.offset;
+      var version = int32(font.getBytes(4));
+      var numGlyphs = int16(font.getBytes(2));
+      var maxFunctionDefs = 0;
+      if (version >= 0x00010000 && maxp.length >= 22) {
+        font.pos += 14;
+        var maxFunctionDefs = int16(font.getBytes(2));
+      }
+
+      var hintsValid = sanitizeTTPrograms(fpgm, prep, maxFunctionDefs);
+      if (!hintsValid) {
+        tables.splice(tables.indexOf(fpgm), 1);
+        fpgm = null;
+        tables.splice(tables.indexOf(prep), 1);
+        prep = null;
+      }
+
       var numTables = tables.length + requiredTables.length;
 
       // header and new offsets. Table entry information is appended to the
       // end of file. The virtualOffset represents where to put the actual
       // data of a particular table;
       var ttf = {
         file: '',
         virtualOffset: numTables * (4 * 4)
       };
 
       // The new numbers of tables will be the last one plus the num
       // of missing tables
       createOpenTypeHeader(header.version, ttf, numTables);
 
-      // Ensure the hmtx table contains the advance width and
-      // sidebearings information for numGlyphs in the maxp table
-      font.pos = (font.start || 0) + maxp.offset;
-      var version = int32(font.getBytes(4));
-      var numGlyphs = int16(font.getBytes(2));
-      var maxFunctionDefs = 0;
-      if (version >= 0x00010000 && maxp.length >= 22) {
-        font.pos += 14;
-        var maxFunctionDefs = int16(font.getBytes(2));
-      }
-
       sanitizeMetrics(font, hhea, hmtx, numGlyphs);
 
-      sanitizeTTPrograms(fpgm, prep, maxFunctionDefs);
-
       if (head) {
         sanitizeHead(head, numGlyphs, loca.length);
       }
 
       var isGlyphLocationsLong = int16([head.data[50], head.data[51]]);
       if (head && loca && glyf) {
-        sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong);
+        sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong,
+                               hintsValid);
       }
 
       var emptyGlyphIds = [];
       if (glyf)
         findEmptyGlyphs(loca, isGlyphLocationsLong, emptyGlyphIds);
 
       // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
       // Sometimes it's 0. That needs to be fixed
@@ -23173,17 +23264,17 @@ var CFFFont = (function CFFFontClosure()
         } else {
           for (var i = 0, ii = charsets.length; i < charsets.length; i++) {
             inverseEncoding.push(i);
           }
         }
       } else {
         for (var charcode in encoding)
           inverseEncoding[encoding[charcode]] = charcode | 0;
-        if (charsets[0] === '.notedef') {
+        if (charsets[0] === '.notdef') {
           gidStart = 1;
         }
       }
 
       for (var i = gidStart, ii = charsets.length; i < ii; i++) {
         var glyph = charsets[i];
 
         var code = inverseEncoding[i];
@@ -33042,23 +33133,32 @@ var Parser = (function ParserClosure() {
           break;
         dict.set(key, this.getObj(cipherTransform));
       }
 
       // parse image stream
       var startPos = stream.pos;
 
       // searching for the /EI\s/
-      var state = 0, ch;
+      var state = 0, ch, i, ii;
       while (state != 4 &&
              (ch = stream.getByte()) !== null && ch !== undefined) {
         switch (ch) {
           case 0x20:
           case 0x0D:
           case 0x0A:
+            // let's check next five bytes to be ASCII... just be sure
+            var followingBytes = stream.peekBytes(5);
+            for (i = 0, ii = followingBytes.length; i < ii; i++) {
+              ch = followingBytes[i];
+              if (ch !== 0x0A && ch != 0x0D && (ch < 0x20 || ch > 0x7F)) {
+                state = 0;
+                break; // some binary stuff found, resetting the state
+              }
+            }
             state = state === 3 ? 4 : 0;
             break;
           case 0x45:
             state = 2;
             break;
           case 0x49:
             state = state === 2 ? 3 : 0;
             break;
@@ -33096,19 +33196,57 @@ var Parser = (function ParserClosure() {
       var length = this.fetchIfRef(dict.get('Length'));
       if (!isInt(length))
         error('Bad ' + length + ' attribute in stream');
 
       // skip over the stream data
       stream.pos = pos + length;
       this.shift(); // '>>'
       this.shift(); // 'stream'
-      if (!isCmd(this.buf1, 'endstream'))
-        error('Missing endstream');
-      this.shift();
+      if (!isCmd(this.buf1, 'endstream')) {
+        // bad stream length, scanning for endstream
+        stream.pos = pos;
+        var SCAN_BLOCK_SIZE = 2048;
+        var ENDSTREAM_SIGNATURE_LENGTH = 9;
+        var ENDSTREAM_SIGNATURE = [0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65,
+                                   0x61, 0x6D];
+        var skipped = 0, found = false;
+        while (stream.pos < stream.end) {
+          var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE);
+          var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH;
+          var found = false, i, ii, j;
+          for (i = 0, j = 0; i < scanLength; i++) {
+            var b = scanBytes[i];
+            if (b !== ENDSTREAM_SIGNATURE[j]) {
+              i -= j;
+              j = 0;
+            } else {
+              j++;
+              if (j >= ENDSTREAM_SIGNATURE_LENGTH) {
+                found = true;
+                break;
+              }
+            }
+          }
+          if (found) {
+            skipped += i - ENDSTREAM_SIGNATURE_LENGTH;
+            stream.pos += i - ENDSTREAM_SIGNATURE_LENGTH;
+            break;
+          }
+          skipped += scanLength;
+          stream.pos += scanLength;
+        }
+        if (!found) {
+          error('Missing endstream');
+        }
+        length = skipped;
+        this.shift();
+        this.shift();
+      }
+      this.shift(); // 'endstream'
 
       stream = stream.makeSubStream(pos, length, dict);
       if (cipherTransform)
         stream = cipherTransform.createStream(stream);
       stream = this.filter(stream, dict, length);
       stream.dict = dict;
       return stream;
     },
@@ -34028,16 +34166,21 @@ var Stream = (function StreamClosure() {
 
       var end = pos + length;
       if (end > strEnd)
         end = strEnd;
 
       this.pos = end;
       return bytes.subarray(pos, end);
     },
+    peekBytes: function Stream_peekBytes(length) {
+      var bytes = this.getBytes(length);
+      this.pos -= bytes.length;
+      return bytes;
+    },
     lookChar: function Stream_lookChar() {
       if (this.pos >= this.end)
         return null;
       return String.fromCharCode(this.bytes[this.pos]);
     },
     getChar: function Stream_getChar() {
       if (this.pos >= this.end)
         return null;
@@ -34132,16 +34275,21 @@ var DecodeStream = (function DecodeStrea
         // the buffer has to be initialized
         if (!end)
           this.buffer = new Uint8Array(0);
       }
 
       this.pos = end;
       return this.buffer.subarray(pos, end);
     },
+    peekBytes: function DecodeStream_peekBytes(length) {
+      var bytes = this.getBytes(length);
+      this.pos -= bytes.length;
+      return bytes;
+    },
     lookChar: function DecodeStream_lookChar() {
       var pos = this.pos;
       while (this.bufferLength <= pos) {
         if (this.eof)
           return null;
         this.readBlock();
       }
       return String.fromCharCode(this.buffer[this.pos]);
@@ -34465,18 +34613,21 @@ var FlateStream = (function FlateStreamC
         error('Bad block header in flate stream');
       blockLen |= (b << 8);
       if (typeof (b = bytes[bytesPos++]) == 'undefined')
         error('Bad block header in flate stream');
       var check = b;
       if (typeof (b = bytes[bytesPos++]) == 'undefined')
         error('Bad block header in flate stream');
       check |= (b << 8);
-      if (check != (~blockLen & 0xffff))
+      if (check != (~blockLen & 0xffff) &&
+          (blockLen !== 0 || check !== 0)) {
+        // Ignoring error for bad "empty" block (see issue 1277)
         error('Bad uncompressed block length in flate stream');
+      }
 
       this.codeBuf = 0;
       this.codeSize = 0;
 
       var bufferLength = this.bufferLength;
       var buffer = this.ensureBuffer(bufferLength + blockLen);
       var end = bufferLength + blockLen;
       this.bufferLength = end;
@@ -35027,32 +35178,43 @@ var Jbig2Stream = (function Jbig2StreamC
   return Jbig2Stream;
 })();
 
 var DecryptStream = (function DecryptStreamClosure() {
   function DecryptStream(str, decrypt) {
     this.str = str;
     this.dict = str.dict;
     this.decrypt = decrypt;
+    this.nextChunk = null;
+    this.initialized = false;
 
     DecodeStream.call(this);
   }
 
   var chunkSize = 512;
 
   DecryptStream.prototype = Object.create(DecodeStream.prototype);
 
   DecryptStream.prototype.readBlock = function DecryptStream_readBlock() {
-    var chunk = this.str.getBytes(chunkSize);
+    var chunk;
+    if (this.initialized) {
+      chunk = this.nextChunk;
+    } else {
+      chunk = this.str.getBytes(chunkSize);
+      this.initialized = true;
+    }
     if (!chunk || chunk.length === 0) {
       this.eof = true;
       return;
     }
+    this.nextChunk = this.str.getBytes(chunkSize);
+    var hasMoreData = this.nextChunk && this.nextChunk.length > 0;
+
     var decrypt = this.decrypt;
-    chunk = decrypt(chunk);
+    chunk = decrypt(chunk, !hasMoreData);
 
     var bufferLength = this.bufferLength;
     var i, n = chunk.length;
     var buffer = this.ensureBuffer(bufferLength + n);
     for (i = 0; i < n; i++)
       buffer[bufferLength++] = chunk[i];
     this.bufferLength = bufferLength;
   };
@@ -40999,19 +41161,21 @@ var JpegImage = (function jpegImage() {
                 }
               } else
                 throw "DQT: invalid table spec";
               quantizationTables[quantizationTableSpec & 15] = tableData;
             }
             break;
 
           case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
+          case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
           case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
             readUint16(); // skip data length
             frame = {};
+            frame.extended = (fileMarker === 0xFFC1);
             frame.progressive = (fileMarker === 0xFFC2);
             frame.precision = data[offset++];
             frame.scanLines = readUint16();
             frame.samplesPerLine = readUint16();
             frame.components = {};
             frame.componentsOrder = [];
             var componentsCount = data[offset++], componentId;
             var maxH = 0, maxV = 0;
@@ -41123,16 +41287,31 @@ var JpegImage = (function jpegImage() {
             component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
             for (x = 0; x < width; x++) {
               Y = component1Line[0 | (x * component1.scaleX * scaleX)];
 
               data[offset++] = Y;
             }
           }
           break;
+        case 2:
+          // PDF might compress two component data in custom colorspace
+          component1 = this.components[0];
+          component2 = this.components[1];
+          for (y = 0; y < height; y++) {
+            component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
+            component2Line = component1.lines[0 | (y * component2.scaleY * scaleY)];
+            for (x = 0; x < width; x++) {
+              Y = component1Line[0 | (x * component1.scaleX * scaleX)];
+              data[offset++] = Y;
+              Y = component2Line[0 | (x * component2.scaleX * scaleX)];
+              data[offset++] = Y;
+            }
+          }
+          break;
         case 3:
           // The default transform for three components is true
           colorTransform = true;
           // The adobe transform marker overrides any previous setting
           if (this.adobe && this.adobe.transformCode)
             colorTransform = true;
           else if (typeof this.colorTransform !== 'undefined')
             colorTransform = !!this.colorTransform;
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -27,18 +27,20 @@ limitations under the License.
 
 
     <link rel="stylesheet" href="viewer.css"/>
 
 
 
 
 
+
     <script type="text/javascript" src="debugger.js"></script>
     <script type="text/javascript" src="viewer.js"></script>
+
   </head>
 
   <body tabindex="1">
     <div id="outerContainer" class="loadingInProgress">
 
       <div id="sidebarContainer">
         <div id="toolbarSidebar">
           <div class="splitToolbarButton toggled">
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -9,32 +9,33 @@
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals PDFJS, PDFBug, FirefoxCom, Stats */
+/* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar */
+/* globals PDFFindController, ProgressBar, getFileName, CustomStyle */
+/* globals getOutputScale, TextLayerBuilder */
 
 'use strict';
 
 var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
 var DEFAULT_SCALE = 'auto';
 var DEFAULT_SCALE_DELTA = 1.1;
 var UNKNOWN_SCALE = 0;
 var CACHE_SIZE = 20;
 var CSS_UNITS = 96.0 / 72.0;
 var SCROLLBAR_PADDING = 40;
 var VERTICAL_PADDING = 5;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 4.0;
 var SETTINGS_MEMORY = 20;
-var HISTORY_DISABLED = false;
 var SCALE_SELECT_CONTAINER_PADDING = 8;
 var SCALE_SELECT_PADDING = 22;
 var RenderingStates = {
   INITIAL: 0,
   RUNNING: 1,
   PAUSED: 2,
   FINISHED: 3
 };
@@ -44,57 +45,92 @@ var FindStates = {
   FIND_WRAPPED: 2,
   FIND_PENDING: 3
 };
 
   PDFJS.workerSrc = '../build/pdf.js';
 
 var mozL10n = document.mozL10n || document.webL10n;
 
+
+// optimised CSS custom property getter/setter
+var CustomStyle = (function CustomStyleClosure() {
+
+  // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
+  //              animate-css-transforms-firefox-webkit.html
+  // in some versions of IE9 it is critical that ms appear in this list
+  // before Moz
+  var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
+  var _cache = { };
+
+  function CustomStyle() {
+  }
+
+  CustomStyle.getProp = function get(propName, element) {
+    // check cache only when no element is given
+    if (arguments.length == 1 && typeof _cache[propName] == 'string') {
+      return _cache[propName];
+    }
+
+    element = element || document.documentElement;
+    var style = element.style, prefixed, uPropName;
+
+    // test standard property first
+    if (typeof style[propName] == 'string') {
+      return (_cache[propName] = propName);
+    }
+
+    // capitalize
+    uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
+
+    // test vendor specific properties
+    for (var i = 0, l = prefixes.length; i < l; i++) {
+      prefixed = prefixes[i] + uPropName;
+      if (typeof style[prefixed] == 'string') {
+        return (_cache[propName] = prefixed);
+      }
+    }
+
+    //if all fails then set to undefined
+    return (_cache[propName] = 'undefined');
+  };
+
+  CustomStyle.setProp = function set(propName, element, str) {
+    var prop = this.getProp(propName);
+    if (prop != 'undefined')
+      element.style[prop] = str;
+  };
+
+  return CustomStyle;
+})();
+
 function getFileName(url) {
   var anchor = url.indexOf('#');
   var query = url.indexOf('?');
   var end = Math.min(
     anchor > 0 ? anchor : url.length,
     query > 0 ? query : url.length);
   return url.substring(url.lastIndexOf('/', end) + 1, end);
 }
 
-function scrollIntoView(element, spot) {
-  // Assuming offsetParent is available (it's not available when viewer is in
-  // hidden iframe or object). We have to scroll: if the offsetParent is not set
-  // producing the error. See also animationStartedClosure.
-  var parent = element.offsetParent;
-  var offsetY = element.offsetTop + element.clientTop;
-  if (!parent) {
-    console.error('offsetParent is not set -- cannot scroll');
-    return;
-  }
-  while (parent.clientHeight == parent.scrollHeight) {
-    offsetY += parent.offsetTop;
-    parent = parent.offsetParent;
-    if (!parent)
-      return; // no need to scroll
-  }
-  if (spot)
-    offsetY += spot.top;
-  parent.scrollTop = offsetY;
+/**
+ * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
+ * @return {Object} The object with horizontal (sx) and vertical (sy)
+                    scales. The scaled property is set to false if scaling is
+                    not required, true otherwise.
+ */
+function getOutputScale() {
+  var pixelRatio = 'devicePixelRatio' in window ? window.devicePixelRatio : 1;
+  return {
+    sx: pixelRatio,
+    sy: pixelRatio,
+    scaled: pixelRatio != 1
+  };
 }
 
-var Cache = function cacheCache(size) {
-  var data = [];
-  this.push = function cachePush(view) {
-    var i = data.indexOf(view);
-    if (i >= 0)
-      data.splice(i);
-    data.push(view);
-    if (data.length > size)
-      data.shift().destroy();
-  };
-};
 
 var ProgressBar = (function ProgressBarClosure() {
 
   function clamp(v, min, max) {
     return Math.min(Math.max(v, min), max);
   }
 
   function ProgressBar(id, opts) {
@@ -135,16 +171,52 @@ var ProgressBar = (function ProgressBarC
       this._percent = clamp(val, 0, 100);
       this.updateBar();
     }
   };
 
   return ProgressBar;
 })();
 
+var Cache = function cacheCache(size) {
+  var data = [];
+  this.push = function cachePush(view) {
+    var i = data.indexOf(view);
+    if (i >= 0)
+      data.splice(i);
+    data.push(view);
+    if (data.length > size)
+      data.shift().destroy();
+  };
+};
+
+
+
+function scrollIntoView(element, spot) {
+  // Assuming offsetParent is available (it's not available when viewer is in
+  // hidden iframe or object). We have to scroll: if the offsetParent is not set
+  // producing the error. See also animationStartedClosure.
+  var parent = element.offsetParent;
+  var offsetY = element.offsetTop + element.clientTop;
+  if (!parent) {
+    console.error('offsetParent is not set -- cannot scroll');
+    return;
+  }
+  while (parent.clientHeight == parent.scrollHeight) {
+    offsetY += parent.offsetTop;
+    parent = parent.offsetParent;
+    if (!parent)
+      return; // no need to scroll
+  }
+  if (spot)
+    offsetY += spot.top;
+  parent.scrollTop = offsetY;
+}
+
+
 /* Copyright 2012 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
@@ -276,21 +348,191 @@ var Settings = (function SettingsClosure
   };
 
   return Settings;
 })();
 
 var cache = new Cache(CACHE_SIZE);
 var currentPageNumber = 1;
 
+// TODO: Enable the FindBar *AFTER* the pagesPromise in the load function
+// got resolved
+
+/* globals PDFFindController, FindStates, mozL10n */
+
+/**
+ * Creates a "search bar" given set of DOM elements
+ * that act as controls for searching, or for setting
+ * search preferences in the UI. This object also sets
+ * up the appropriate events for the controls. Actual
+ * searching is done by PDFFindController
+ */
+var PDFFindBar = {
+
+  opened: false,
+  bar: null,
+  toggleButton: null,
+  findField: null,
+  highlightAll: null,
+  caseSensitive: null,
+  findMsg: null,
+  findStatusIcon: null,
+  findPreviousButton: null,
+  findNextButton: null,
+
+  initialize: function(options) {
+    if(typeof PDFFindController === 'undefined' || PDFFindController === null) {
+        throw 'PDFFindBar cannot be initialized ' +
+            'without a PDFFindController instance.';
+    }
+
+    this.bar = options.bar;
+    this.toggleButton = options.toggleButton;
+    this.findField = options.findField;
+    this.highlightAll = options.highlightAllCheckbox;
+    this.caseSensitive = options.caseSensitiveCheckbox;
+    this.findMsg = options.findMsg;
+    this.findStatusIcon = options.findStatusIcon;
+    this.findPreviousButton = options.findPreviousButton;
+    this.findNextButton = options.findNextButton;
+
+    var self = this;
+    this.toggleButton.addEventListener('click', function() {
+      self.toggle();
+    });
+
+    this.findField.addEventListener('input', function() {
+      self.dispatchEvent('');
+    });
+
+    this.bar.addEventListener('keydown', function(evt) {
+      switch (evt.keyCode) {
+        case 13: // Enter
+          if (evt.target === self.findField) {
+            self.dispatchEvent('again', evt.shiftKey);
+          }
+          break;
+        case 27: // Escape
+          self.close();
+          break;
+      }
+    });
+
+    this.findPreviousButton.addEventListener('click',
+      function() { self.dispatchEvent('again', true); }
+    );
+
+    this.findNextButton.addEventListener('click', function() {
+      self.dispatchEvent('again', false);
+    });
+
+    this.highlightAll.addEventListener('click', function() {
+      self.dispatchEvent('highlightallchange');
+    });
+
+    this.caseSensitive.addEventListener('click', function() {
+      self.dispatchEvent('casesensitivitychange');
+    });
+  },
+
+  dispatchEvent: function(aType, aFindPrevious) {
+    var event = document.createEvent('CustomEvent');
+    event.initCustomEvent('find' + aType, true, true, {
+      query: this.findField.value,
+      caseSensitive: this.caseSensitive.checked,
+      highlightAll: this.highlightAll.checked,
+      findPrevious: aFindPrevious
+    });
+    return window.dispatchEvent(event);
+  },
+
+  updateUIState: function(state, previous) {
+    var notFound = false;
+    var findMsg = '';
+    var status = '';
+
+    switch (state) {
+      case FindStates.FIND_FOUND:
+        break;
+
+      case FindStates.FIND_PENDING:
+        status = 'pending';
+        break;
+
+      case FindStates.FIND_NOTFOUND:
+        findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
+        notFound = true;
+        break;
+
+      case FindStates.FIND_WRAPPED:
+        if (previous) {
+          findMsg = mozL10n.get('find_reached_top', null,
+                      'Reached top of document, continued from bottom');
+        } else {
+          findMsg = mozL10n.get('find_reached_bottom', null,
+                                'Reached end of document, continued from top');
+        }
+        break;
+    }
+
+    if (notFound) {
+      this.findField.classList.add('notFound');
+    } else {
+      this.findField.classList.remove('notFound');
+    }
+
+    this.findField.setAttribute('data-status', status);
+    this.findMsg.textContent = findMsg;
+  },
+
+  open: function() {
+    if (this.opened) return;
+
+    this.opened = true;
+    this.toggleButton.classList.add('toggled');
+    this.bar.classList.remove('hidden');
+    this.findField.select();
+    this.findField.focus();
+  },
+
+  close: function() {
+    if (!this.opened) return;
+
+    this.opened = false;
+    this.toggleButton.classList.remove('toggled');
+    this.bar.classList.add('hidden');
+
+    PDFFindController.active = false;
+  },
+
+  toggle: function() {
+    if (this.opened) {
+      this.close();
+    } else {
+      this.open();
+    }
+  }
+};
+
+
+
+/* globals PDFFindBar, PDFJS, FindStates, FirefoxCom */
+
+/**
+ * Provides a "search" or "find" functionality for the PDF.
+ * This object actually performs the search for a given string.
+ */
+
 var PDFFindController = {
   startedTextExtraction: false,
 
   extractTextPromises: [],
 
+  pendingFindMatches: {},
+
   // If active, find results will be highlighted.
   active: false,
 
   // Stores the text for each page.
   pageContents: [],
 
   pageMatches: [],
 
@@ -311,17 +553,29 @@ var PDFFindController = {
   resumeCallback: null,
 
   state: null,
 
   dirtyMatch: false,
 
   findTimeout: null,
 
-  initialize: function() {
+  pdfPageSource: null,
+
+  integratedFind: false,
+
+  initialize: function(options) {
+    if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) {
+        throw 'PDFFindController cannot be initialized ' +
+            'without a PDFFindController instance';
+    }
+
+    this.pdfPageSource = options.pdfPageSource;
+    this.integratedFind = options.integratedFind;
+
     var events = [
       'find',
       'findagain',
       'findhighlightallchange',
       'findcasesensitivitychange'
     ];
 
     this.handleEvent = this.handleEvent.bind(this);
@@ -370,43 +624,42 @@ var PDFFindController = {
 
   extractText: function() {
     if (this.startedTextExtraction) {
       return;
     }
     this.startedTextExtraction = true;
 
     this.pageContents = [];
-    for (var i = 0, ii = PDFView.pdfDocument.numPages; i < ii; i++) {
+    for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) {
       this.extractTextPromises.push(new PDFJS.Promise());
     }
 
     var self = this;
     function extractPageText(pageIndex) {
-      PDFView.pages[pageIndex].getTextContent().then(
+      self.pdfPageSource.pages[pageIndex].getTextContent().then(
         function textContentResolved(data) {
           // Build the find string.
           var bidiTexts = data.bidiTexts;
           var str = '';
 
           for (var i = 0; i < bidiTexts.length; i++) {
             str += bidiTexts[i].str;
           }
 
           // Store the pageContent as a string.
           self.pageContents.push(str);
 
           self.extractTextPromises[pageIndex].resolve(pageIndex);
-          if ((pageIndex + 1) < PDFView.pages.length)
+          if ((pageIndex + 1) < self.pdfPageSource.pages.length)
             extractPageText(pageIndex + 1);
         }
       );
     }
     extractPageText(0);
-    return this.extractTextPromise;
   },
 
   handleEvent: function(e) {
     if (this.state === null || e.type !== 'findagain') {
       this.dirtyMatch = true;
     }
     this.state = e.detail;
     this.updateUIState(FindStates.FIND_PENDING);
@@ -418,34 +671,34 @@ var PDFFindController = {
       // Only trigger the find action after 250ms of silence.
       this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
     } else {
       this.nextMatch();
     }
   },
 
   updatePage: function(idx) {
-    var page = PDFView.pages[idx];
+    var page = this.pdfPageSource.pages[idx];
 
     if (this.selected.pageIdx === idx) {
       // If the page is selected, scroll the page into view, which triggers
       // rendering the page, which adds the textLayer. Once the textLayer is
       // build, it will scroll onto the selected match.
       page.scrollIntoView();
     }
 
     if (page.textLayer) {
       page.textLayer.updateMatches();
     }
   },
 
   nextMatch: function() {
-    var pages = PDFView.pages;
+    var pages = this.pdfPageSource.pages;
     var previous = this.state.findPrevious;
-    var numPages = PDFView.pages.length;
+    var numPages = this.pdfPageSource.pages.length;
 
     this.active = true;
 
     if (this.dirtyMatch) {
       // Need to recalculate the matches, reset everything.
       this.dirtyMatch = false;
       this.selected.pageIdx = this.selected.matchIdx = -1;
       this.offset.pageIdx = previous ? numPages - 1 : 0;
@@ -456,23 +709,23 @@ var PDFFindController = {
       this.pageMatches = [];
       var self = this;
 
       for (var i = 0; i < numPages; i++) {
         // Wipe out any previous highlighted matches.
         this.updatePage(i);
 
         // As soon as the text is extracted start finding the matches.
-        this.extractTextPromises[i].onData(function(pageIdx) {
-          // Use a timeout since all the pages may already be extracted and we
-          // want to start highlighting before finding all the matches.
-          setTimeout(function() {
+        if (!(i in this.pendingFindMatches)) {
+          this.pendingFindMatches[i] = true;
+          this.extractTextPromises[i].then(function(pageIdx) {
+            delete self.pendingFindMatches[pageIdx];
             self.calcFindMatch(pageIdx);
           });
-        });
+        }
       }
     }
 
     // If there's no query there's no point in searching.
     if (this.state.query === '') {
       this.updateUIState(FindStates.FIND_FOUND);
       return;
     }
@@ -576,178 +829,48 @@ var PDFFindController = {
     }
     this.updateUIState(state, this.state.findPrevious);
     if (this.selected.pageIdx !== -1) {
       this.updatePage(this.selected.pageIdx, true);
     }
   },
 
   updateUIState: function(state, previous) {
-    if (PDFView.supportsIntegratedFind) {
+    if (this.integratedFind) {
       FirefoxCom.request('updateFindControlState',
                          {result: state, findPrevious: previous});
       return;
     }
     PDFFindBar.updateUIState(state, previous);
   }
 };
 
-var PDFFindBar = {
-  // TODO: Enable the FindBar *AFTER* the pagesPromise in the load function
-  // got resolved
-
-  opened: false,
-
-  initialize: function() {
-    this.bar = document.getElementById('findbar');
-    this.toggleButton = document.getElementById('viewFind');
-    this.findField = document.getElementById('findInput');
-    this.highlightAll = document.getElementById('findHighlightAll');
-    this.caseSensitive = document.getElementById('findMatchCase');
-    this.findMsg = document.getElementById('findMsg');
-    this.findStatusIcon = document.getElementById('findStatusIcon');
-
-    var self = this;
-    this.toggleButton.addEventListener('click', function() {
-      self.toggle();
-    });
-
-    this.findField.addEventListener('input', function() {
-      self.dispatchEvent('');
-    });
-
-    this.bar.addEventListener('keydown', function(evt) {
-      switch (evt.keyCode) {
-        case 13: // Enter
-          if (evt.target === self.findField) {
-            self.dispatchEvent('again', evt.shiftKey);
-          }
-          break;
-        case 27: // Escape
-          self.close();
-          break;
-      }
-    });
-
-    document.getElementById('findPrevious').addEventListener('click',
-      function() { self.dispatchEvent('again', true); }
-    );
-
-    document.getElementById('findNext').addEventListener('click', function() {
-      self.dispatchEvent('again', false);
-    });
-
-    this.highlightAll.addEventListener('click', function() {
-      self.dispatchEvent('highlightallchange');
-    });
-
-    this.caseSensitive.addEventListener('click', function() {
-      self.dispatchEvent('casesensitivitychange');
-    });
-  },
-
-  dispatchEvent: function(aType, aFindPrevious) {
-    var event = document.createEvent('CustomEvent');
-    event.initCustomEvent('find' + aType, true, true, {
-      query: this.findField.value,
-      caseSensitive: this.caseSensitive.checked,
-      highlightAll: this.highlightAll.checked,
-      findPrevious: aFindPrevious
-    });
-    return window.dispatchEvent(event);
-  },
-
-  updateUIState: function(state, previous) {
-    var notFound = false;
-    var findMsg = '';
-    var status = '';
-
-    switch (state) {
-      case FindStates.FIND_FOUND:
-        break;
-
-      case FindStates.FIND_PENDING:
-        status = 'pending';
-        break;
-
-      case FindStates.FIND_NOTFOUND:
-        findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
-        notFound = true;
-        break;
-
-      case FindStates.FIND_WRAPPED:
-        if (previous) {
-          findMsg = mozL10n.get('find_reached_top', null,
-                      'Reached top of document, continued from bottom');
-        } else {
-          findMsg = mozL10n.get('find_reached_bottom', null,
-                                'Reached end of document, continued from top');
-        }
-        break;
-    }
-
-    if (notFound) {
-      this.findField.classList.add('notFound');
-    } else {
-      this.findField.classList.remove('notFound');
-    }
-
-    this.findField.setAttribute('data-status', status);
-    this.findMsg.textContent = findMsg;
-  },
-
-  open: function() {
-    if (this.opened) return;
-
-    this.opened = true;
-    this.toggleButton.classList.add('toggled');
-    this.bar.classList.remove('hidden');
-    this.findField.select();
-    this.findField.focus();
-  },
-
-  close: function() {
-    if (!this.opened) return;
-
-    this.opened = false;
-    this.toggleButton.classList.remove('toggled');
-    this.bar.classList.add('hidden');
-
-    PDFFindController.active = false;
-  },
-
-  toggle: function() {
-    if (this.opened) {
-      this.close();
-    } else {
-      this.open();
-    }
-  }
-};
+
 
 var PDFHistory = {
   initialized: false,
   initialDestination: null,
 
   initialize: function pdfHistoryInitialize(fingerprint) {
-    if (HISTORY_DISABLED || window.parent !== window) {
+    if (PDFJS.disableHistory || window.parent !== window) {
       // The browsing history is only enabled when the viewer is standalone,
       // i.e. not when it is embedded in a page.
       return;
     }
     this.initialized = true;
     this.reInitialized = false;
     this.allowHashChange = true;
     this.historyUnlocked = true;
 
     this.previousHash = window.location.hash.substring(1);
     this.currentBookmark = '';
     this.currentPage = 0;
     this.updatePreviousBookmark = false;
     this.previousBookmark = '';
+    this.previousPage = 0;
     this.nextHashParam = '';
 
     this.fingerprint = fingerprint;
     this.currentUid = this.uid = 0;
     this.current = {};
 
     var state = window.history.state;
     if (this._isStateObjectDefined(state)) {
@@ -845,16 +968,17 @@ var PDFHistory = {
 
   updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
                                                                   pageNum) {
     if (this.initialized) {
       this.currentBookmark = bookmark.substring(1);
       this.currentPage = pageNum | 0;
       if (this.updatePreviousBookmark) {
         this.previousBookmark = this.currentBookmark;
+        this.previousPage = this.currentPage;
         this.updatePreviousBookmark = false;
       }
     }
   },
 
   updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
     if (this.initialized) {
       this.nextHashParam = param;
@@ -906,18 +1030,18 @@ var PDFHistory = {
                                                             beforeUnload) {
     if (!(this.currentBookmark && this.currentPage)) {
       return null;
     }
     if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
       if (this.previousBookmark === this.currentBookmark) {
         return null;
       }
-    } else if (this.current.page) {
-      if (this.current.page === this.currentPage) {
+    } else if (this.current.page || onlyCheckPage) {
+      if (this.previousPage === this.currentPage) {
         return null;
       }
     } else {
       return null;
     }
     var params = { hash: this.currentBookmark, page: this.currentPage };
     if (PDFView.isPresentationMode) {
       params.hash = null;
@@ -1023,17 +1147,17 @@ var PDFView = {
   thumbnailContainer: null,
   initialized: false,
   fellback: false,
   pdfDocument: null,
   sidebarOpen: false,
   pageViewScroll: null,
   thumbnailViewScroll: null,
   isPresentationMode: false,
-  previousScale: null,
+  presentationModeArgs: null,
   pageRotation: 0,
   mouseScrollTimeStamp: 0,
   mouseScrollDelta: 0,
   lastScroll: 0,
   previousPageNumber: 1,
 
   // called once when the document is loaded
   initialize: function pdfViewInitialize() {
@@ -1043,18 +1167,32 @@ var PDFView = {
     this.watchScroll(container, this.pageViewScroll, updateViewarea);
 
     var thumbnailContainer = this.thumbnailContainer =
                              document.getElementById('thumbnailView');
     this.thumbnailViewScroll = {};
     this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
                      this.renderHighestPriority.bind(this));
 
-    PDFFindBar.initialize();
-    PDFFindController.initialize();
+    PDFFindBar.initialize({
+        bar: document.getElementById('findbar'),
+        toggleButton: document.getElementById('viewFind'),
+        findField: document.getElementById('findInput'),
+        highlightAllCheckbox: document.getElementById('findHighlightAll'),
+        caseSensitiveCheckbox: document.getElementById('findMatchCase'),
+        findMsg: document.getElementById('findMsg'),
+        findStatusIcon: document.getElementById('findStatusIcon'),
+        findPreviousButton: document.getElementById('findPrevious'),
+        findNextButton: document.getElementById('findNext')
+    });
+
+    PDFFindController.initialize({
+        pdfPageSource: this,
+        integratedFind: this.supportsIntegratedFind
+    });
 
     this.initialized = true;
     container.addEventListener('scroll', function() {
       self.lastScroll = Date.now();
     }, false);
   },
 
   getPage: function pdfViewGetPage(n) {
@@ -1374,17 +1512,22 @@ var PDFView = {
       }
 
       password = prompt(promptString);
       if (password && password.length > 0) {
         return updatePassword(password);
       }
     };
 
-    PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded).then(
+    function getDocumentProgress(progressData) {
+      self.progress(progressData.loaded / progressData.total);
+    }
+
+    PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
+                      getDocumentProgress).then(
       function getDocumentCallback(pdfDocument) {
         self.load(pdfDocument, scale);
         self.loading = false;
       },
       function getDocumentError(message, exception) {
         var loadingErrorMessage = mozL10n.get('loading_error', null,
           'An error occurred while loading the PDF.');
 
@@ -1401,19 +1544,16 @@ var PDFView = {
 
         }
 
         var moreInfo = {
           message: message
         };
         self.error(loadingErrorMessage, moreInfo);
         self.loading = false;
-      },
-      function getDocumentProgress(progressData) {
-        self.progress(progressData.loaded / progressData.total);
       }
     );
   },
 
   download: function pdfViewDownload() {
     function noData() {
       FirefoxCom.request('download', { originalUrl: url });
     }
@@ -1520,30 +1660,16 @@ var PDFView = {
    * For the firefox extension we prefix the full url on anchor links so they
    * don't come up as resource:// urls and so open in new tab/window works.
    * @param {String} anchor The anchor hash include the #.
    */
   getAnchorUrl: function getAnchorUrl(anchor) {
     return this.url.split('#')[0] + anchor;
   },
 
-  /**
-   * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
-   * @return {Object} The object with horizontal (sx) and vertical (sy)
-                      scales. The scaled property is set to false if scaling is
-                      not required, true otherwise.
-   */
-  getOutputScale: function pdfViewGetOutputDPI() {
-    var pixelRatio = 'devicePixelRatio' in window ? window.devicePixelRatio : 1;
-    return {
-      sx: pixelRatio,
-      sy: pixelRatio,
-      scaled: pixelRatio != 1
-    };
-  },
 
   /**
    * Show the error box.
    * @param {String} message A message that is human readable.
    * @param {Object} moreInfo (optional) Further information about the error
    *                            that is more technical.  Should have a 'message'
    *                            and optionally a 'stack' property.
    */
@@ -1617,16 +1743,17 @@ var PDFView = {
     if ('_loadingInterval' in thumbsView)
       clearInterval(thumbsView._loadingInterval);
 
     var container = document.getElementById('viewer');
     while (container.hasChildNodes())
       container.removeChild(container.lastChild);
 
     var pagesCount = pdfDocument.numPages;
+
     var id = pdfDocument.fingerprint;
     document.getElementById('numPages').textContent =
       mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
     document.getElementById('pageNumber').max = pagesCount;
 
     PDFView.documentFingerprint = id;
     var store = PDFView.store = new Settings(id);
 
@@ -2115,39 +2242,39 @@ var PDFView = {
       wrapper.requestFullscreen();
     } else if (document.documentElement.mozRequestFullScreen) {
       wrapper.mozRequestFullScreen();
     } else if (document.documentElement.webkitRequestFullScreen) {
       wrapper.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
     } else {
       return false;
     }
+
+    this.presentationModeArgs = {
+      page: this.page,
+      previousScale: this.currentScaleValue
+    };
+
     return true;
   },
 
   enterPresentationMode: function pdfViewEnterPresentationMode() {
     this.isPresentationMode = true;
-    var currentPage = this.pages[this.page - 1];
-    this.previousScale = this.currentScaleValue;
+    this.page = this.presentationModeArgs.page;
     this.parseScale('page-fit', true);
-
-    // Wait for presentation mode to take effect
-    setTimeout(function() {
-      currentPage.scrollIntoView();
-    }, 0);
-
     this.showPresentationControls();
   },
 
   exitPresentationMode: function pdfViewExitPresentationMode() {
     this.isPresentationMode = false;
-    this.parseScale(this.previousScale);
+    this.parseScale(this.presentationModeArgs.previousScale);
     this.page = this.page;
     this.clearMouseScrollState();
     this.hidePresentationControls();
+    this.presentationModeArgs = null;
 
     // Ensure that the thumbnail of the current page is visible
     // when exiting presentation mode.
     scrollIntoView(document.getElementById('thumbnailContainer' + this.page));
   },
 
   showPresentationControls: function pdfViewShowPresentationControls() {
     var DELAY_BEFORE_HIDING_CONTROLS = 3000;
@@ -2450,19 +2577,19 @@ var PageView = function pageView(contain
           x = dest[2];
           scale = 'page-height';
           break;
         case 'FitR':
           x = dest[2];
           y = dest[3];
           width = dest[4] - x;
           height = dest[5] - y;
-          widthScale = (this.container.clientWidth - SCROLLBAR_PADDING) /
+          widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) /
             width / CSS_UNITS;
-          heightScale = (this.container.clientHeight - SCROLLBAR_PADDING) /
+          heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) /
             height / CSS_UNITS;
           scale = Math.min(widthScale, heightScale);
           break;
         default:
           return;
       }
 
       if (scale && scale !== PDFView.currentScale) {
@@ -2527,30 +2654,34 @@ var PageView = function pageView(contain
 
     var canvas = document.createElement('canvas');
     canvas.id = 'page' + this.id;
     canvasWrapper.appendChild(canvas);
     div.appendChild(canvasWrapper);
     this.canvas = canvas;
 
     var scale = this.scale;
-    var outputScale = PDFView.getOutputScale();
+    var outputScale = getOutputScale();
     canvas.width = Math.floor(viewport.width) * outputScale.sx;
     canvas.height = Math.floor(viewport.height) * outputScale.sy;
 
     var textLayerDiv = null;
     if (!PDFJS.disableTextLayer) {
       textLayerDiv = document.createElement('div');
       textLayerDiv.className = 'textLayer';
       textLayerDiv.style.width = canvas.width + 'px';
       textLayerDiv.style.height = canvas.height + 'px';
       div.appendChild(textLayerDiv);
     }
     var textLayer = this.textLayer =
-          textLayerDiv ? new TextLayerBuilder(textLayerDiv, this.id - 1) : null;
+          textLayerDiv ? new TextLayerBuilder({
+              textLayerDiv: textLayerDiv,
+              pageIndex: this.id - 1,
+              lastScrollSource: PDFView
+          }) : null;
 
     if (outputScale.scaled) {
       var cssScale = 'scale(' + (1 / outputScale.sx) + ', ' +
                                 (1 / outputScale.sy) + ')';
       CustomStyle.setProp('transform' , canvas, cssScale);
       CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
       if (textLayerDiv) {
         CustomStyle.setProp('transform' , textLayerDiv, cssScale);
@@ -2895,122 +3026,45 @@ var ThumbnailView = function thumbnailVi
     var ctx = this.getPageDrawContext();
     ctx.drawImage(img, 0, 0, img.width, img.height,
                   0, 0, ctx.canvas.width, ctx.canvas.height);
 
     this.hasImage = true;
   };
 };
 
-var DocumentOutlineView = function documentOutlineView(outline) {
-  var outlineView = document.getElementById('outlineView');
-  var outlineButton = document.getElementById('viewOutline');
-  while (outlineView.firstChild)
-    outlineView.removeChild(outlineView.firstChild);
-
-  if (!outline) {
-    if (!outlineView.classList.contains('hidden'))
-      PDFView.switchSidebarView('thumbs');
-
-    return;
-  }
-
-  function bindItemLink(domObj, item) {
-    domObj.href = PDFView.getDestinationHash(item.dest);
-    domObj.onclick = function documentOutlineViewOnclick(e) {
-      PDFView.navigateTo(item.dest);
-      return false;
-    };
-  }
-
-
-  var queue = [{parent: outlineView, items: outline}];
-  while (queue.length > 0) {
-    var levelData = queue.shift();
-    var i, n = levelData.items.length;
-    for (i = 0; i < n; i++) {
-      var item = levelData.items[i];
-      var div = document.createElement('div');
-      div.className = 'outlineItem';
-      var a = document.createElement('a');
-      bindItemLink(a, item);
-      a.textContent = item.title;
-      div.appendChild(a);
-
-      if (item.items.length > 0) {
-        var itemsDiv = document.createElement('div');
-        itemsDiv.className = 'outlineItems';
-        div.appendChild(itemsDiv);
-        queue.push({parent: itemsDiv, items: item.items});
-      }
-
-      levelData.parent.appendChild(div);
-    }
-  }
-};
-
-// optimised CSS custom property getter/setter
-var CustomStyle = (function CustomStyleClosure() {
-
-  // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
-  //              animate-css-transforms-firefox-webkit.html
-  // in some versions of IE9 it is critical that ms appear in this list
-  // before Moz
-  var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
-  var _cache = { };
-
-  function CustomStyle() {
-  }
-
-  CustomStyle.getProp = function get(propName, element) {
-    // check cache only when no element is given
-    if (arguments.length == 1 && typeof _cache[propName] == 'string') {
-      return _cache[propName];
-    }
-
-    element = element || document.documentElement;
-    var style = element.style, prefixed, uPropName;
-
-    // test standard property first
-    if (typeof style[propName] == 'string') {
-      return (_cache[propName] = propName);
-    }
-
-    // capitalize
-    uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
-
-    // test vendor specific properties
-    for (var i = 0, l = prefixes.length; i < l; i++) {
-      prefixed = prefixes[i] + uPropName;
-      if (typeof style[prefixed] == 'string') {
-        return (_cache[propName] = prefixed);
-      }
-    }
-
-    //if all fails then set to undefined
-    return (_cache[propName] = 'undefined');
-  };
-
-  CustomStyle.setProp = function set(propName, element, str) {
-    var prop = this.getProp(propName);
-    if (prop != 'undefined')
-      element.style[prop] = str;
-  };
-
-  return CustomStyle;
-})();
-
-var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) {
+
+/* globals CustomStyle, PDFFindController, scrollIntoView */
+
+/**
+ * TextLayerBuilder provides text-selection
+ * functionality for the PDF. It does this
+ * by creating overlay divs over the PDF
+ * text. This divs contain text that matches
+ * the PDF text they are overlaying. This
+ * object also provides for a way to highlight
+ * text that is being searched for.
+ */
+var TextLayerBuilder = function textLayerBuilder(options) {
   var textLayerFrag = document.createDocumentFragment();
 
-  this.textLayerDiv = textLayerDiv;
+  this.textLayerDiv = options.textLayerDiv;
   this.layoutDone = false;
   this.divContentDone = false;
-  this.pageIdx = pageIdx;
+  this.pageIdx = options.pageIndex;
   this.matches = [];
+  this.lastScrollSource = options.lastScrollSource;
+
+  if(typeof PDFFindController === 'undefined') {
+      window.PDFFindController = null;
+  }
+
+  if(typeof this.lastScrollSource === 'undefined') {
+      this.lastScrollSource = null;
+  }
 
   this.beginLayout = function textLayerBuilderBeginLayout() {
     this.textDivs = [];
     this.renderingDone = false;
   };
 
   this.endLayout = function textLayerBuilderEndLayout() {
     this.layoutDone = true;
@@ -3061,17 +3115,20 @@ var TextLayerBuilder = function textLaye
     textLayerDiv.appendChild(textLayerFrag);
   };
 
   this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
     // Schedule renderLayout() if user has been scrolling, otherwise
     // run it right away
     var RENDER_DELAY = 200; // in ms
     var self = this;
-    if (Date.now() - PDFView.lastScroll > RENDER_DELAY) {
+    var lastScroll = this.lastScrollSource === null ?
+        0 : this.lastScrollSource.lastScroll;
+
+    if (Date.now() - lastScroll > RENDER_DELAY) {
       // Render right away
       this.renderLayer();
     } else {
       // Schedule
       if (this.renderTimer)
         clearTimeout(this.renderTimer);
       this.renderTimer = setTimeout(function() {
         self.setupRenderLayoutTimer();
@@ -3129,17 +3186,18 @@ var TextLayerBuilder = function textLaye
     this.insertDivContent();
   };
 
   this.convertMatches = function textLayerBuilderConvertMatches(matches) {
     var i = 0;
     var iIndex = 0;
     var bidiTexts = this.textContent.bidiTexts;
     var end = bidiTexts.length - 1;
-    var queryLen = PDFFindController.state.query.length;
+    var queryLen = PDFFindController === null ?
+        0 : PDFFindController.state.query.length;
 
     var lastDivIdx = -1;
     var pos;
 
     var ret = [];
 
     // Loop over all the matches.
     for (var m = 0; m < matches.length; m++) {
@@ -3188,19 +3246,24 @@ var TextLayerBuilder = function textLaye
     // Early exit if there is nothing to render.
     if (matches.length === 0) {
       return;
     }
 
     var bidiTexts = this.textContent.bidiTexts;
     var textDivs = this.textDivs;
     var prevEnd = null;
-    var isSelectedPage = this.pageIdx === PDFFindController.selected.pageIdx;
-    var selectedMatchIdx = PDFFindController.selected.matchIdx;
-    var highlightAll = PDFFindController.state.highlightAll;
+    var isSelectedPage = PDFFindController === null ?
+        false : (this.pageIdx === PDFFindController.selected.pageIdx);
+
+    var selectedMatchIdx = PDFFindController === null ?
+        -1 : PDFFindController.selected.matchIdx;
+
+    var highlightAll = PDFFindController === null ?
+        false : PDFFindController.state.highlightAll;
 
     var infty = {
       divIdx: -1,
       offset: undefined
     };
 
     function beginText(begin, className) {
       var divIdx = begin.divIdx;
@@ -3308,28 +3371,78 @@ var TextLayerBuilder = function textLaye
       for (var n = begin; n <= match.end.divIdx; n++) {
         var div = textDivs[n];
         div.textContent = bidiTexts[n].str;
         div.className = '';
       }
       clearedUntilDivIdx = match.end.divIdx + 1;
     }
 
-    if (!PDFFindController.active)
+    if (PDFFindController === null || !PDFFindController.active)
       return;
 
     // Convert the matches on the page controller into the match format used
     // for the textLayer.
     this.matches = matches =
-      this.convertMatches(PDFFindController.pageMatches[this.pageIdx] || []);
+      this.convertMatches(PDFFindController === null ?
+          [] : (PDFFindController.pageMatches[this.pageIdx] || []));
 
     this.renderMatches(this.matches);
   };
 };
 
+
+
+var DocumentOutlineView = function documentOutlineView(outline) {
+  var outlineView = document.getElementById('outlineView');
+  var outlineButton = document.getElementById('viewOutline');
+  while (outlineView.firstChild)
+    outlineView.removeChild(outlineView.firstChild);
+
+  if (!outline) {
+    if (!outlineView.classList.contains('hidden'))
+      PDFView.switchSidebarView('thumbs');
+
+    return;
+  }
+
+  function bindItemLink(domObj, item) {
+    domObj.href = PDFView.getDestinationHash(item.dest);
+    domObj.onclick = function documentOutlineViewOnclick(e) {
+      PDFView.navigateTo(item.dest);
+      return false;
+    };
+  }
+
+
+  var queue = [{parent: outlineView, items: outline}];
+  while (queue.length > 0) {
+    var levelData = queue.shift();
+    var i, n = levelData.items.length;
+    for (i = 0; i < n; i++) {
+      var item = levelData.items[i];
+      var div = document.createElement('div');
+      div.className = 'outlineItem';
+      var a = document.createElement('a');
+      bindItemLink(a, item);
+      a.textContent = item.title;
+      div.appendChild(a);
+
+      if (item.items.length > 0) {
+        var itemsDiv = document.createElement('div');
+        itemsDiv.className = 'outlineItems';
+        div.appendChild(itemsDiv);
+        queue.push({parent: itemsDiv, items: item.items});
+      }
+
+      levelData.parent.appendChild(div);
+    }
+  }
+};
+
 document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) {
   PDFView.initialize();
   var params = PDFView.parseQueryString(document.location.search.substring(1));
 
   var file = window.location.toString()
 
   document.getElementById('openFile').setAttribute('hidden', 'true');
 
@@ -3348,16 +3461,20 @@ document.addEventListener('DOMContentLoa
   if ('disableAutoFetch' in hashParams) {
     PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true');
   }
 
   if ('disableFontFace' in hashParams) {
     PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true');
   }
 
+  if ('disableHistory' in hashParams) {
+    PDFJS.disableHistory = (hashParams['disableHistory'] === 'true');
+  }
+
   if (!PDFView.supportsDocumentFonts) {
     PDFJS.disableFontFace = true;
   }
 
   if ('textLayer' in hashParams) {
     switch (hashParams['textLayer']) {
       case 'off':
         PDFJS.disableTextLayer = true;
@@ -3882,16 +3999,25 @@ window.addEventListener('keydown', funct
       case 82: // 'r'
         PDFView.rotatePages(90);
         break;
     }
   }
 
   if (cmd === 4) { // shift-key
     switch (evt.keyCode) {
+      case 32: // spacebar
+        if (!PDFView.isPresentationMode &&
+            PDFView.currentScaleValue !== 'page-fit') {
+          break;
+        }
+        PDFView.page--;
+        handled = true;
+        break;
+
       case 82: // 'r'
         PDFView.rotatePages(-90);
         break;
     }
   }
 
   if (cmd === 2) { // alt-key
     switch (evt.keyCode) {
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -619,16 +619,18 @@ Example Enterprises, Inc.
 The layout of the identity dialog prevents combining this into a single string with
 substitution variables.  If it is difficult to translate the sense of the string
 with that structure, consider a translation which ignores the preceding domain and
 just addresses the organization to follow, e.g. "This site is run by " -->
 <!ENTITY identity.runBy "which is run by">
 
 <!ENTITY identity.moreInfoLinkText "More Information…">
 
+<!ENTITY identity.permissions "Permissions">
+
 <!-- Name for the tabs toolbar as spoken by screen readers.
      The word "toolbar" is appended automatically and should not be contained below! -->
 <!ENTITY tabsToolbar.label "Browser tabs">
 
 <!-- LOCALIZATION NOTE (syncTabsMenu2.label): This appears in the history menu -->
 <!ENTITY syncTabsMenu2.label     "Tabs From Other Devices">
 
 <!ENTITY syncBrand.shortName.label    "Sync">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.properties
@@ -0,0 +1,18 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+allow = Allow
+allowForSession = Allow for Session
+block = Block
+
+permission.cookie.label = Set Cookies
+permission.desktop-notification.label = Show Notifications
+permission.image.label = Load Images
+permission.install.label = Install Add-ons
+permission.popup.label = Open Pop-up Windows
+permission.geo.label = Access Your Location
+permission.indexedDB.label = Maintain Offline Storage
+permission.fullscreen.label = Enter Fullscreen
+permission.pointerLock.label = Hide the Mouse Pointer
+
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -58,16 +58,17 @@
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
     locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
     locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
     locale/browser/sanitize.dtd                    (%chrome/browser/sanitize.dtd)
     locale/browser/search.properties               (%chrome/browser/search.properties)
     locale/browser/searchbar.dtd                   (%chrome/browser/searchbar.dtd)
+    locale/browser/sitePermissions.properties      (%chrome/browser/sitePermissions.properties)
     locale/browser/engineManager.dtd               (%chrome/browser/engineManager.dtd)
     locale/browser/engineManager.properties        (%chrome/browser/engineManager.properties)
     locale/browser/setDesktopBackground.dtd        (%chrome/browser/setDesktopBackground.dtd)
     locale/browser/shellservice.properties         (%chrome/browser/shellservice.properties)
     locale/browser/tabbrowser.dtd                  (%chrome/browser/tabbrowser.dtd)
     locale/browser/tabbrowser.properties           (%chrome/browser/tabbrowser.properties)
     locale/browser/tabview.properties              (%chrome/browser/tabview.properties)
     locale/browser/taskbar.properties              (%chrome/browser/taskbar.properties)
--- a/browser/metro/base/content/NavButtonSlider.js
+++ b/browser/metro/base/content/NavButtonSlider.js
@@ -4,16 +4,18 @@
 
 /*
  * Handles nav overlay button positioning.
  */
 
 // minimum amount of movement using the mouse after which we cancel the button click handlers
 const kOnClickMargin = 3;
 
+const kNavButtonPref = "browser.display.overlaynavbuttons";
+
 var NavButtonSlider = {
   _back: document.getElementById("overlay-back"),
   _plus: document.getElementById("overlay-plus"),
   _mouseMoveStarted: false,
   _mouseDown: false,
   _yPos: -1,
 
   /*
@@ -61,16 +63,34 @@ var NavButtonSlider = {
     Elements.browsers.addEventListener("ContentSizeChanged", this, true);
     let events = ["mousedown", "mouseup", "mousemove", "click"];
     events.forEach(function (value) {
       this._back.addEventListener(value, this, true);
       this._plus.addEventListener(value, this, true);
     }, this);
 
     this._updateStops();
+    this._updateVisibility();
+    Services.prefs.addObserver(kNavButtonPref, this, false);
+  },
+
+  observe: function BrowserUI_observe(aSubject, aTopic, aData) {
+    if (aTopic == "nsPref:changed" && aData == kNavButtonPref) {
+      this._updateVisibility();
+    }
+  },
+
+  _updateVisibility: function () {
+    if (Services.prefs.getBoolPref(kNavButtonPref)) {
+      this._back.removeAttribute("hidden");
+      this._plus.removeAttribute("hidden");
+    } else {
+      this._back.setAttribute("hidden", true);
+      this._plus.setAttribute("hidden", true);
+    }
   },
 
   _updateStops: function () {
     this._contentHeight = ContentAreaObserver.contentHeight;
     this._imageHeight = 118;
     this._topStop = this._imageHeight * .7;
     this._bottomStop = this._contentHeight - (this._imageHeight * .7);
 
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -151,19 +151,18 @@ var BrowserUI = {
 #endif
       } catch(ex) {
         Util.dumpLn("Exception in delay load module:", ex.message);
       }
 
       BrowserUI._pullDesktopControlledPrefs();
 
       // check for left over crash reports and submit them if found.
-      if (BrowserUI.startupCrashCheck()) {
-        Browser.selectedTab = BrowserUI.newOrSelectTab("about:crash");
-      }
+      BrowserUI.startupCrashCheck();
+
       Util.dumpLn("* delay load complete.");
     }, false);
 
 #ifndef MOZ_OFFICIAL_BRANDING
     setTimeout(function() {
       let startup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup).getStartupInfo();
       for (let name in startup) {
         if (name != "process")
@@ -207,34 +206,53 @@ var BrowserUI = {
    */
 
   get CrashSubmit() {
     delete this.CrashSubmit;
     Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
     return this.CrashSubmit;
   },
 
+  get lastCrashID() {
+    return Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime).lastRunCrashID;
+  },
+
   startupCrashCheck: function startupCrashCheck() {
 #ifdef MOZ_CRASHREPORTER
-    if (!Services.prefs.getBoolPref("app.reportCrashes"))
-      return false;
-    if (CrashReporter.enabled) {
-      var lastCrashID = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).lastRunCrashID;
-      if (lastCrashID && lastCrashID.length) {
-        Util.dumpLn("Submitting last crash id:", lastCrashID);
-        try {
-          this.CrashSubmit.submit(lastCrashID);
-        } catch (ex) {
-          Util.dumpLn(ex);
-        }
-        return true;
-      }
+    if (!CrashReporter.enabled) {
+      return;
+    }
+    let lastCrashID = this.lastCrashID;
+    if (!lastCrashID || !lastCrashID.length) {
+      return;
+    }
+    let shouldReport = Services.prefs.getBoolPref("app.crashreporter.autosubmit");
+    let didPrompt = Services.prefs.getBoolPref("app.crashreporter.prompted");
+
+    if (!shouldReport && !didPrompt) {
+      // We have a crash to submit, we haven't prompted for approval yet,
+      // and the auto-submit pref is false, prompt. The dialog will call
+      // startupCrashCheck again if the user approves.
+      Services.prefs.setBoolPref("app.crashreporter.prompted", true);
+      DialogUI.importModal(document, "chrome://browser/content/prompt/crash.xul");
+      return;
+    }
+
+    // We've already prompted, return if the user doesn't want to report.
+    if (!shouldReport && didPrompt) {
+      return;
+    }
+
+    Util.dumpLn("Submitting last crash id:", lastCrashID);
+    try {
+      this.CrashSubmit.submit(lastCrashID);
+    } catch (ex) {
+      Util.dumpLn(ex);
     }
 #endif
-    return false;
   },
 
 
   /*********************************
    * Navigation
    */
 
   update: function(aState) {
@@ -1634,19 +1652,28 @@ var DialogUI = {
     let parentNode = contentMenuContainer.parentNode;
 
     // emit DOMWillOpenModalDialog event
     let event = document.createEvent("Events");
     event.initEvent("DOMWillOpenModalDialog", true, false);
     let dispatcher = aParent || getBrowser();
     dispatcher.dispatchEvent(event);
 
-    // create a full-screen semi-opaque box as a background
-    let back = document.createElement("box");
+    // create a full-screen semi-opaque box as a background or reuse
+    // the existing one.
+    let back = document.getElementById("dialog-modal-block");
+    if (!back) {
+      back = document.createElement("box");
+    } else {
+      while (back.hasChildNodes()) {
+        back.removeChild(back.firstChild);
+      }
+    }
     back.setAttribute("class", "modal-block");
+    back.setAttribute("id", "dialog-modal-block");
     dialog = back.appendChild(document.importNode(doc, true));
     parentNode.insertBefore(back, contentMenuContainer);
 
     dialog.arguments = aArguments;
     dialog.parent = aParent;
     return dialog;
   },
 
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -496,17 +496,17 @@
               <image src="chrome://browser/skin/images/throbber.png" />
             </hbox>
             <description id="clear-notification-done">&clearPrivateData.done;</description>
           </deck>
         </hbox>
       </settings>
       <setting pref="signon.rememberSignons" title="&optionsHeader.privacy.passwords.label;" type="bool"/>
       <settings id="prefs-reporting" label="&optionsHeader.reporting.title;">
-        <setting pref="app.reportCrashes" type="bool" title="&optionsHeader.reporting.crashes.label;" oncommand="BrowserUI.crashReportingPrefChanged(this.value);"/>
+        <setting pref="app.crashreporter.autosubmit" type="bool" title="&optionsHeader.reporting.crashes.label;" oncommand="BrowserUI.crashReportingPrefChanged(this.value);"/>
       </settings>
       <settings id="prefs-dnt" label="&doNotTrack.title;">
         <description>&doNotTrack.desc;</description>
         <setting id="prefs-dnt-value" pref="privacy.donottrackheader.value" onpreferencechanged="PreferencesPanelView.onDNTPreferenceChanged()" type="radio" >
           <radiogroup id="prefs-dnt-options">
             <radio id="prefs-dnt-notrack" label="&doNotTrack.options.trackingNotOkay;" value="1"/>
             <radio id="prefs-dnt-nopref" label="&doNotTrack.options.noPreference;" value="-1"/>
             <radio id="prefs-dnt-oktrack" label="&doNotTrack.options.trackingOkay;" value="0"/>
@@ -520,25 +520,25 @@
       <dialog id="syncsetup-dialog" class="content-dialog" flex="1">
         <vbox class="prompt-inner">
           <hbox class="prompt-title">
             <description>&sync.setup2.title;</description>
           </hbox>
           <vbox id="syncsetup-simple" class="syncsetup-page" flex="1">
             <scrollbox id="sync-message" class="prompt-message" orient="vertical" flex="1">
               <description class="syncsetup-desc" flex="1">&sync.setup.pair2;</description>
-              <description class="link" flex="1" onclick="Sync.openTutorial();">&sync.setup.tutorial;</description>
+              <description class="text-link" flex="1" onclick="Sync.openTutorial();">&sync.setup.tutorial;</description>
               <separator/>
               <vbox flex="1" pack="center" align="start">
                 <description id="syncsetup-code1" class="syncsetup-code">....</description>
                 <description id="syncsetup-code2" class="syncsetup-code">....</description>
                 <description id="syncsetup-code3" class="syncsetup-code">....</description>
               </vbox>
               <separator/>
-              <description class="link" flex="1" onclick="Sync.openManual();">&sync.fallback;</description>
+              <description class="text-link" flex="1" onclick="Sync.openManual();">&sync.fallback;</description>
               <separator flex="1"/>
             </scrollbox>
             <hbox class="prompt-buttons">
               <button oncommand="Sync.close();">&sync.setup.cancel;</button>
             </hbox>
           </vbox>
           <vbox id="syncsetup-waiting" class="syncsetup-page" flex="1" hidden="true">
             <progressmeter id="syncsetup-progressbar" mode="undetermined"/>
@@ -577,17 +577,17 @@
       <dialog id="syncpair-dialog" class="content-dialog" flex="1">
         <vbox class="prompt-inner">
           <hbox class="prompt-title">
             <description>&sync.pair.title;</description>
           </hbox>
           <vbox id="syncpair-simple" class="syncsetup-page" flex="1">
             <vbox id="sync-message" class="prompt-message" orient="vertical" flex="1">
               <description class="syncsetup-desc" flex="1">&sync.pair.description;</description>
-              <description class="link" flex="1" onclick="SyncPairDevice.close(); Sync.openTutorial();">&sync.setup.tutorial;</description>
+              <description class="text-link" flex="1" onclick="SyncPairDevice.close(); Sync.openTutorial();">&sync.setup.tutorial;</description>
               <separator/>
               <vbox align="center" flex="1">
                 <textbox id="syncpair-code1" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
                 <textbox id="syncpair-code2" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
                 <textbox id="syncpair-code3" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
               </vbox>
             </vbox>
             <hbox class="prompt-buttons" pack="center">
--- a/browser/metro/base/content/downloads.js
+++ b/browser/metro/base/content/downloads.js
@@ -37,18 +37,20 @@ var Downloads = {
 
     this._inited = true;
 
     Services.obs.addObserver(this, "dl-start", true);
     Services.obs.addObserver(this, "dl-done", true);
   },
 
   uninit: function dh_uninit() {
-    Services.obs.removeObserver(this, "dl-start");
-    Services.obs.removeObserver(this, "dl-done");
+    if (this._inited) {
+      Services.obs.removeObserver(this, "dl-start");
+      Services.obs.removeObserver(this, "dl-done");
+    }
   },
 
   openDownload: function dh_openDownload(aDownload) {
     // expects xul item
     let id = aDownload.getAttribute("downloadId");
     let download = this.manager.getDownload(id);
     let fileURI = download.target;
 
deleted file mode 100644
--- a/browser/metro/base/content/pages/aboutCrash.xhtml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html [
-  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
-  %htmlDTD;
-  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-  %brandDTD;
-  <!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd">
-  %aboutRightsDTD;
-]>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-<head>
-  <title>Oh noooz! You crashed!</title>
-  <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
-</head>
-
-<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer">
-
-<h1>Oh noooz! You crashed!</h1>
-
-<p>A crash report is being submitted as you read this, we hope! Check <a href="about:crashes">about:crashes</a> for crash reports.</p>
-<p>(I'm know I'm boring to look at, hopefully someday I'll be pretty and useful and stuff!)</p>
-
-<script type="application/javascript"><![CDATA[
-]]></script>
-
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/prompt/crash.xul
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE prompt [
+  <!ENTITY % promptDTD SYSTEM "chrome://browser/locale/prompt.dtd">
+  <!ENTITY % commonDialogDTD SYSTEM "chrome://global/locale/commonDialog.dtd">
+  <!ENTITY % crashpromptDTD SYSTEM "chrome://browser/locale/crashprompt.dtd">
+  %crashpromptDTD;
+  %promptDTD;
+  %commonDialogDTD;
+]>
+
+<dialog id="crash-prompt-dialog"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="this.CrashPrompt.load();"
+        script="chrome://browser/content/prompt/prompt.js">
+
+  <keyset>
+    <key keycode="VK_RETURN" command="cmd_ok"/>
+    <key keycode="VK_ESCAPE" command="cmd_cancel"/>
+  </keyset>
+
+  <commandset>
+    <command id="cmd_ok" oncommand="CrashPrompt.accept();"/>
+    <command id="cmd_cancel" oncommand="CrashPrompt.refuse();"/>
+  </commandset>
+
+  <!-- user query and options -->
+  <vbox id="crash-privacy-options" class="prompt-inner">
+    <vbox class="prompt-header" flex="1">
+      <hbox class="prompt-title">
+        <description>&crashprompt.dialog.title;</description>
+      </hbox>
+      <description class="prompt-message" id="privacy-crash-blurb"/>
+      <hbox flex="1">
+        <description class="text-link crash-link" flex="1"
+          onclick="document.getElementById('crash-prompt-dialog').CrashPrompt.privacy();">&crashprompt.dialog.privacyLink;</description>
+      </hbox>
+    </vbox>
+    <hbox class="prompt-buttons">
+      <button id="crash-button-accept" class="button-default" default="true" command="cmd_ok">&crashprompt.dialog.acceptbutton;</button>
+      <separator/>
+      <button id="crash-button-refuse" command="cmd_cancel">&crashprompt.dialog.refusebutton;</button>
+    </hbox>
+  </vbox>
+
+  <!-- long winded privacy statement, hidden by default -->
+  <vbox id="crash-privacy-statement" class="prompt-inner-statement" hidden="true">
+    <hbox flex="1">
+      <vbox class="crash-privacy-back-button" onclick="document.getElementById('crash-prompt-dialog').CrashPrompt.privacyBack();"/>
+      <scrollbox orient="vertical" class="crash-privacy-statement-scroller" flex="1">
+        <hbox class="prompt-title">
+          <description>&crashprompt.dialog.title2;</description>
+        </hbox>
+        <description class="crash-message">&crashprompt.dialog.statement1;</description>
+        <separator/>
+        <description class="crash-message">&crashprompt.dialog.statement2;</description>
+      </scrollbox>
+    </hbox>
+  </vbox>
+</dialog>
--- a/browser/metro/base/content/prompt/prompt.js
+++ b/browser/metro/base/content/prompt/prompt.js
@@ -65,8 +65,57 @@ const PromptHelper = {
     this.closeDialog(confirm, "prompt-select-dialog");
   },
 
   onCloseSelect: function(dialog) {
     if (dialog.arguments)
       dialog.arguments.selection.value = document.getElementById("prompt-select-list").selectedIndex;
   }
 };
+
+var CrashPrompt = {
+  load: function () {
+    if (this._preventRecurse) {
+      throw new Exception("CrashPrompt recursion error!!");
+      return;
+    }
+    // populate the text blurb with localized text
+    let brandName = Services.strings.createBundle("chrome://branding/locale/brand.properties")
+                                    .GetStringFromName("brandShortName");
+    let vendorName = Services.strings.createBundle("chrome://branding/locale/brand.properties")
+                                     .GetStringFromName("vendorShortName");
+    let crashBundle = Services.strings.createBundle("chrome://browser/locale/crashprompt.properties");
+    let message = crashBundle.formatStringFromName("crashprompt.messagebody", [brandName, vendorName, brandName], 3);
+    let descElement = document.getElementById("privacy-crash-blurb");
+    descElement.textContent = message;
+
+    // focus the send button
+    document.getElementById('crash-button-accept').focus();
+  },
+
+  accept: function() {
+    document.getElementById("crash-prompt-dialog").close();
+    Services.prefs.setBoolPref('app.crashreporter.autosubmit', true);
+    Services.prefs.setBoolPref('app.crashreporter.prompted', true);
+    BrowserUI.crashReportingPrefChanged(true);
+
+    this._preventRecurse = true;
+    BrowserUI.startupCrashCheck();
+    this._preventRecurse = false;
+  },
+
+  refuse: function() {
+    document.getElementById("crash-prompt-dialog").close();
+    Services.prefs.setBoolPref('app.crashreporter.autosubmit', false);
+    Services.prefs.setBoolPref('app.crashreporter.prompted', true);
+    BrowserUI.crashReportingPrefChanged(false);
+  },
+
+  privacy: function() {
+    document.getElementById("crash-privacy-options").setAttribute("hidden", true);
+    document.getElementById("crash-privacy-statement").setAttribute("hidden", false);
+  },
+
+  privacyBack: function() {
+    document.getElementById("crash-privacy-options").setAttribute("hidden", false);
+    document.getElementById("crash-privacy-statement").setAttribute("hidden", true);
+  },
+};
--- a/browser/metro/base/jar.mn
+++ b/browser/metro/base/jar.mn
@@ -3,17 +3,16 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 chrome.jar:
 % content browser %content/
 
   content/aboutCertError.xhtml                 (content/pages/aboutCertError.xhtml)
   content/aboutRights.xhtml                    (content/pages/aboutRights.xhtml)
-  content/aboutCrash.xhtml                     (content/pages/aboutCrash.xhtml)
   content/blockedSite.xhtml                    (content/pages/blockedSite.xhtml)
   content/netError.xhtml                       (content/pages/netError.xhtml)
 
   content/bindings/bindings.xml                (content/bindings/bindings.xml)
   content/bindings/tabs.xml                    (content/bindings/tabs.xml)
   content/bindings/toggleswitch.xml            (content/bindings/toggleswitch.xml)
   content/bindings/browser.xml                 (content/bindings/browser.xml)
   content/bindings/browser.js                  (content/bindings/browser.js)
@@ -30,16 +29,17 @@ chrome.jar:
   content/bindings/popup.xml                   (content/bindings/popup.xml)
 
   content/prompt/alert.xul                     (content/prompt/alert.xul)
   content/prompt/confirm.xul                   (content/prompt/confirm.xul)
   content/prompt/prompt.xul                    (content/prompt/prompt.xul)
   content/prompt/promptPassword.xul            (content/prompt/promptPassword.xul)
   content/prompt/select.xul                    (content/prompt/select.xul)
   content/prompt/prompt.js                     (content/prompt/prompt.js)
+  content/prompt/crash.xul                     (content/prompt/crash.xul)
 
   content/helperui/AlertsHelper.js             (content/helperui/AlertsHelper.js)
   content/helperui/IndexedDB.js                (content/helperui/IndexedDB.js)
   content/helperui/MenuUI.js                   (content/helperui/MenuUI.js)
   content/helperui/OfflineApps.js              (content/helperui/OfflineApps.js)
   content/helperui/SelectHelperUI.js           (content/helperui/SelectHelperUI.js)
   content/helperui/SelectionHelperUI.js        (content/helperui/SelectionHelperUI.js)
   content/helperui/ChromeSelectionHandler.js   (content/helperui/ChromeSelectionHandler.js)
--- a/browser/metro/base/tests/mochitest/Makefile.in
+++ b/browser/metro/base/tests/mochitest/Makefile.in
@@ -27,16 +27,17 @@ BROWSER_TESTS = \
   browser_remotetabs.js \
   browser_tabs.js \
   browser_test.js \
   browser_tiles.js \
   browser_tilegrid.xul \
   browser_topsites.js \
   browser_form_auto_complete.js \
   browser_form_auto_complete.html \
+  browser_crashprompt.js \
   $(NULL)
 
 ifndef MOZ_DEBUG
 BROWSER_TESTS += \
   browser_selection_basic.js \
   browser_selection_basic.html \
   browser_selection_textarea.js \
   browser_selection_textarea.html \
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_crashprompt.js
@@ -0,0 +1,160 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cc = Components.classes;
+
+const CONTRACT_ID = "@mozilla.org/xre/runtime;1";
+
+var gAppInfoClassID, gIncOldFactory;
+
+var gMockAppInfoQueried = false;
+
+function MockAppInfo() {
+}
+
+var newFactory = {
+  createInstance: function(aOuter, aIID) {
+    if (aOuter)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return new MockAppInfo().QueryInterface(aIID);
+  },
+  lockFactory: function(aLock) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+function initMockAppInfo() {
+  var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+  gAppInfoClassID = registrar.contractIDToCID(CONTRACT_ID);
+  gIncOldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+  registrar.unregisterFactory(gAppInfoClassID, gIncOldFactory);
+  var components = [MockAppInfo];
+  registrar.registerFactory(gAppInfoClassID, "", CONTRACT_ID, newFactory);
+  gIncOldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+}
+
+function cleanupMockAppInfo() {
+  if (gIncOldFactory) {
+    var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+    registrar.unregisterFactory(gAppInfoClassID, newFactory);
+    registrar.registerFactory(gAppInfoClassID, "", CONTRACT_ID, gIncOldFactory);
+  }
+  gIncOldFactory = null;
+}
+
+MockAppInfo.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULRuntime]),
+  get lastRunCrashID() {
+    gMockAppInfoQueried = true;
+    return this.crashid;
+  },
+}
+
+gTests.push({
+  desc: "Test Crash Prompt",
+  setUp: initMockAppInfo,
+  tearDown: cleanupMockAppInfo,
+
+  get autosubmitPref() {
+    return Services.prefs.getBoolPref("app.crashreporter.autosubmit");
+  },
+  set autosubmitPref(aValue) {
+    Services.prefs.setBoolPref('app.crashreporter.autosubmit', aValue);
+  },
+  get promptedPref() {
+    return Services.prefs.getBoolPref("app.crashreporter.prompted");
+  },
+  set promptedPref(aValue) {
+    Services.prefs.setBoolPref('app.crashreporter.prompted', aValue);
+  },
+  get dialog() {
+    return document.getElementById("crash-prompt-dialog");
+  },
+
+  run: function() {
+    MockAppInfo.crashid = "testid";
+
+    // never prompted, autosubmit off. We should prompt.
+    this.autosubmitPref = false;
+    this.promptedPref = false;
+
+    BrowserUI.startupCrashCheck();
+
+    yield waitForMs(100);
+
+    ok(this.dialog, "prompt dialog exists");
+    ok(!this.dialog.hidden, "prompt dialog is visible");
+
+    // user refuses crash reporting opt-in
+    let refuseButton = document.getElementById("crash-button-refuse");
+    sendElementTap(window, refuseButton, 20, 20);
+
+    yield waitForCondition(() => this.dialog == null);
+
+    ok(!this.autosubmitPref, "auto submit disabled?");
+    ok(this.promptedPref, "prompted should be true");
+
+    // never prompted, autosubmit off. We should prompt.
+    this.autosubmitPref = false;
+    this.promptedPref = false;
+
+    // should query on the first call to startupCrashCheck
+    gMockAppInfoQueried = false;
+
+    BrowserUI.startupCrashCheck();
+
+    yield waitForMs(100);
+
+    ok(this.dialog, "prompt dialog exists");
+    ok(!this.dialog.hidden, "prompt dialog is visible");
+    ok(gMockAppInfoQueried, "id queried");
+
+    // should query again when the user submits
+    gMockAppInfoQueried = false;
+
+    // user accepts crash reporting opt-in
+    let submitButton = document.getElementById("crash-button-accept");
+    sendElementTap(window, submitButton, 20, 20);
+
+    yield waitForCondition(() => this.dialog == null);
+
+    ok(this.autosubmitPref, "auto submit enabled?");
+    ok(this.promptedPref, "prompted should be true");
+    ok(gMockAppInfoQueried, "id queried");
+
+    // prompted, auto-submit off. We shouldn't prompt.
+    this.autosubmitPref = false;
+    this.promptedPref = true;
+
+    BrowserUI.startupCrashCheck();
+
+    yield waitForMs(100);
+
+    ok(!this.dialog, "prompt dialog does not exists");
+
+    // never prompted, autosubmit *on*. We shouldn't prompt.
+    this.autosubmitPref = true;
+    this.promptedPref = false;
+
+    BrowserUI.startupCrashCheck();
+
+    yield waitForMs(100);
+
+    ok(!this.dialog, "prompt dialog does not exists");
+  }
+});
+
+function test() {
+  if (!CrashReporter.enabled) {
+    info("crash reporter prompt tests didn't run, CrashReporter.enabled is false.");
+    return;
+  }
+  runTests();
+}
--- a/browser/metro/components/AboutRedirector.js
+++ b/browser/metro/components/AboutRedirector.js
@@ -35,20 +35,16 @@ let modules = {
   certerror: {
     uri: "chrome://browser/content/aboutCertError.xhtml",
     privileged: true
   },
   // an alias for about:start
   home: {
     uri: "about:blank",
     privileged: true
-  },
-  crash: {
-    uri: "chrome://browser/content/aboutCrash.xhtml",
-    privileged: true
   }
 }
 
 function AboutGeneric() {}
 
 AboutGeneric.prototype = {
   classID: Components.ID("{433d2d75-5923-49b0-854d-f37267b03dc7}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
--- a/browser/metro/components/components.manifest
+++ b/browser/metro/components/components.manifest
@@ -1,17 +1,16 @@
 # AboutRedirector.js
 component {433d2d75-5923-49b0-854d-f37267b03dc7} AboutRedirector.js
 contract @mozilla.org/network/protocol/about;1?what=start {433d2d75-5923-49b0-854d-f37267b03dc7}
 contract @mozilla.org/network/protocol/about;1?what=empty {433d2d75-5923-49b0-854d-f37267b03dc7}
 contract @mozilla.org/network/protocol/about;1?what=firefox {433d2d75-5923-49b0-854d-f37267b03dc7}
 contract @mozilla.org/network/protocol/about;1?what=rights {433d2d75-5923-49b0-854d-f37267b03dc7}
 contract @mozilla.org/network/protocol/about;1?what=certerror {433d2d75-5923-49b0-854d-f37267b03dc7}
 contract @mozilla.org/network/protocol/about;1?what=home {433d2d75-5923-49b0-854d-f37267b03dc7}
-contract @mozilla.org/network/protocol/about;1?what=crash {433d2d75-5923-49b0-854d-f37267b03dc7}
 #ifdef MOZ_SAFE_BROWSING
 contract @mozilla.org/network/protocol/about;1?what=blocked {433d2d75-5923-49b0-854d-f37267b03dc7}
 #endif
 
 # DirectoryProvider.js
 component {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b} DirectoryProvider.js
 contract @mozilla.org/browser/directory-provider;1 {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b}
 category xpcom-directory-providers browser-directory-provider @mozilla.org/browser/directory-provider;1
new file mode 100644
--- /dev/null
+++ b/browser/metro/locales/en-US/chrome/crashprompt.dtd
@@ -0,0 +1,12 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!ENTITY crashprompt.dialog.title           "Would you like to send Mozilla crash reports?">
+<!ENTITY crashprompt.dialog.privacyLink     "Privacy statement of crash-reporting feature">
+<!ENTITY crashprompt.dialog.acceptbutton    "Send reports">
+<!ENTITY crashprompt.dialog.refusebutton    "Don't send">
+<!ENTITY crashprompt.dialog.title2          "Privacy statement of crash-reporting feature">
+<!ENTITY crashprompt.dialog.statement1      "Firefox has a crash-reporting feature that sends a report to Mozilla when Firefox crashes. Mozilla uses information in crash reports to diagnose and correct problems in Firefox that cause crashes. Though this feature starts automatically after Firefox crashes, it does not send information to Mozilla until you explicitly authorize it to do so. By default, this feature sends a variety of Non-Personal Information to Mozilla, including the stack trace (a detailed description of which parts of the Firefox code were active at the time of the crash) and the type of computer you are using. Additional information is collected by the crash reporting feature. Which crash reporting feature is used and what additional information collected by Firefox depends on which version of Firefox you are using.">
+<!ENTITY crashprompt.dialog.statement2      "For current versions of Firefox, “Firefox Crash Reporter” is Firefox’s crash reporting feature. With this feature, you may have the option to include Personal Information (including your email address), Potentially Personal Information (including your IP address and the URL of the site you were visiting when Firefox crashed), and a comment. Firefox Crash Reporter also sends a list of all add-ons that you were using at the time of the crash, the time since (i) the last crash, (ii) the last install, and (iii) the start-up of the program. Mozilla only makes Non-Personal Information (i.e., generic information about your computer, the stack trace, and any comment given by the user) available in the public reports available online at http://crash-stats.mozilla.com/.">
new file mode 100644
--- /dev/null
+++ b/browser/metro/locales/en-US/chrome/crashprompt.properties
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE: '%S' is short brand name
+crashprompt.messagebody=We are sorry, %S just recovered from a crash. Sending crash reports will help %S make %S more stable and secure. You can always change your preference in Settings/Options.
--- a/browser/metro/locales/jar.mn
+++ b/browser/metro/locales/jar.mn
@@ -16,16 +16,18 @@
   locale/browser/preferences.dtd          (%chrome/preferences.dtd)
   locale/browser/aboutPanel.dtd           (%chrome/aboutPanel.dtd)
   locale/browser/checkbox.dtd             (%chrome/checkbox.dtd)
   locale/browser/sync.dtd                 (%chrome/sync.dtd)
   locale/browser/sync.properties          (%chrome/sync.properties)
   locale/browser/passwordmgr.properties   (%chrome/passwordmgr.properties)
   locale/browser/prompt.dtd               (%chrome/prompt.dtd)
   locale/browser/phishing.dtd             (%chrome/phishing.dtd)
+  locale/browser/crashprompt.dtd          (%chrome/crashprompt.dtd)
+  locale/browser/crashprompt.properties   (%chrome/crashprompt.properties)
 
 @AB_CD@.jar:
 % locale browser @AB_CD@ %locale/browser/
   locale/browser/bookmarks.json           (bookmarks.json)
 
 #
 # Browser jar resources
 #
--- a/browser/metro/modules/CrossSlide.jsm
+++ b/browser/metro/modules/CrossSlide.jsm
@@ -156,34 +156,31 @@ CrossSlideHandler.prototype = {
     let touch = aEvent.touches[0];
      // cross-slide is a single touch gesture
      // the top target is the one we need here, touch.target not relevant
     let target = aEvent.target;
 
     if (!isSelectable(target))
         return;
 
-    // we'll handle this event, dont let it bubble further
-    aEvent.stopPropagation();
-
     let scrollAxis = getScrollAxisFromElement(target);
 
     this.drag = {
       scrollAxis: scrollAxis,
       crossAxis: (scrollAxis=='x') ? 'y' : 'x',
       origin: pointFromTouchEvent(aEvent),
       state: -1
     };
   },
 
   _onTouchMove: function(aEvent){
     if (!this.drag) {
       return;
     }
-    // event is handled here, dont let it bubble further
+
     aEvent.stopPropagation();
 
     if (aEvent.touches.length!==1) {
       // cancel if another touch point gets involved
       return this.cancel(aEvent);
     }
 
     let startPt = this.drag.origin;
@@ -201,16 +198,17 @@ CrossSlideHandler.prototype = {
     let newState = this.getCrossSlideState(crossAxisDistance, scrollAxisDistance);
 
     if (-1 == newState) {
       // out of bounds, cancel the event always
       return this.cancel(aEvent);
     }
 
     let isWithinCone = withinCone(crossAxisDistance, scrollAxisDistance);
+
     if (currState < CrossSlidingState.SELECTING && !isWithinCone) {
       // ignore, no progress to report
       return;
     }
     if (currState >= CrossSlidingState.SELECTING && !isWithinCone) {
       // we're committed to a cross-slide gesture,
       // so going out of bounds at this point means aborting
       return this.cancel(aEvent);
@@ -223,19 +221,16 @@ CrossSlideHandler.prototype = {
 
     this.drag.state = newState;
     this._fireProgressEvent( CrossSlidingStateNames[newState], aEvent );
   },
   _onTouchEnd: function(aEvent){
     if (!this.drag)
       return;
 
-    // event is handled, dont let it bubble further
-    aEvent.stopPropagation();
-
     if (this.drag.state < CrossSlidingState.SELECTING) {
       return this.cancel(aEvent);
     }
 
     this._fireProgressEvent("completed", aEvent);
     this._fireSelectEvent(aEvent);
     this.drag = null;
   },
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -6,18 +6,20 @@
 
 #ifdef DEBUG
 // disable content and content script caching
 pref("nglayout.debug.disable_xul_cache", true);
 pref("nglayout.debug.disable_xul_fastload", true);
 pref("devtools.errorconsole.enabled", true);
 #endif
 
-// Enable headless crash reporting by default
-pref("app.reportCrashes", true);
+// Automatically submit crash reports
+pref("app.crashreporter.autosubmit", false);
+// Has the user been prompted about crash reporting?
+pref("app.crashreporter.prompted", false);
 
 // Debug prefs, see input.js
 pref("metro.debug.treatmouseastouch", false);
 pref("metro.debug.colorizeInputOverlay", false);
 pref("metro.debug.selection.displayRanges", false);
 pref("metro.debug.selection.dumpRanges", false);
 pref("metro.debug.selection.dumpEvents", false);
 
@@ -79,16 +81,19 @@ pref("browser.offline-apps.notify", true
 
 /* protocol warning prefs */
 pref("network.protocol-handler.warn-external.tel", false);
 pref("network.protocol-handler.warn-external.mailto", false);
 pref("network.protocol-handler.warn-external.vnd.youtube", false);
 pref("network.protocol-handler.warn-external.ms-windows-store", false);
 pref("network.protocol-handler.external.ms-windows-store", true);
 
+// display the overlay nav buttons
+pref("browser.display.overlaynavbuttons", true);
+
 /* history max results display */
 pref("browser.display.history.maxresults", 100);
 
 /* max items per section of the startui */
 pref("browser.display.startUI.maxresults", 16);
 
 // Backspace and Shift+Backspace behavior
 // 0 goes Back/Forward
--- a/browser/metro/theme/platform.css
+++ b/browser/metro/theme/platform.css
@@ -12,18 +12,18 @@
 /* Typography & General Styling -------------------------------------------- */
 
 :root {
   font-family: "Segoe UI", sans-serif !important;
   font-size: @font_normal@;
 }
 
 .text-link {
+  color: @metro_orange@;
   text-decoration: none;
-  color: #1167bd;
 }
 
 textbox,
 menulist {
   -moz-appearance: none;
   min-width: @field_sizing@; /* button size */
   min-height: @field_sizing@; /* button size */
   margin: @metro_spacing_small@;
@@ -113,21 +113,16 @@ button[disabled] {
 }
 
 /* Textbox ----------------------------------------------------------------- */
 
 textbox[isempty="true"] {
   color: @field_mid_foreground_color@;
 }
 
-.link {
-  color: blue;
-  text-decoration: underline;
-}
-
 spinbuttons {
   border: none;
 }
 
 .spinbuttons-box {
   border: none;
   -moz-box-orient: horizontal;
   -moz-box-direction: reverse;
@@ -492,32 +487,69 @@ dialog > .prompt-inner {
 }
 
 /* Authentication dialogs do not have a title */
 .prompt-title:empty {
   display: none;
 }
 
 .prompt-message {
+  padding-top: @metro_spacing_normal@;
   text-align: start;
   font-size: @metro_font_normal@;
 }
 
 .prompt-buttons {
   -moz-box-pack: end;
   text-align: end;
   padding: @metro_spacing_normal@ 0;
 }
 
 .prompt-edit {
   margin: @margin_xnormal@;
   font-size: @font_normal@;
   text-align: start;
 }
 
+/* additional styles for crash prompt dialog */
+
+/* specific height and wider for the long privacy statement */
+.prompt-inner-statement {
+  margin-top: @metro_spacing_xnormal@;
+  margin-bottom: @metro_spacing_xnormal@;
+  height: 450px;
+  width: 800px;
+}
+
+.crash-message {
+  text-align: justify;
+  margin-top: 5px;
+  margin-bottom: @metro_spacing_small@;
+  font-size: @metro_font_normal@;
+}
+
+.crash-link {
+  margin-top: @metro_spacing_small@;
+}
+
+/* temp - bug 887176 */
+.crash-privacy-back-button {
+  width: 26px;
+  margin-top: 22px; /* align with title text */
+  -moz-margin-end: 15px;
+  background-image: url(chrome://browser/skin/images/appbar-stop.png);
+  background-origin: border-box;
+  background-position: right 0 top 0;
+  background-repeat: no-repeat;
+}
+
+.crash-privacy-statement-scroller {
+  overflow: auto;
+}
+
 /* Arrowbox ---------------------------------------------------------------- */
 
 arrowbox {
   -moz-appearance: none;
   background: transparent;
   border: none;
 }
 
--- a/browser/modules/Makefile.in
+++ b/browser/modules/Makefile.in
@@ -12,16 +12,17 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/config.mk
 
 EXTRA_JS_MODULES = \
 	BrowserNewTabPreloader.jsm \
 	openLocationLastURL.jsm \
 	NetworkPrioritizer.jsm \
 	offlineAppCache.jsm \
 	SignInToWebsite.jsm \
+	SitePermissions.jsm \
 	webappsUI.jsm \
 	webrtcUI.jsm \
 	Social.jsm \
 	SharedFrame.jsm \
 	$(NULL)
 
 EXTRA_PP_JS_MODULES = \
 	AboutHomeUtils.jsm \
new file mode 100644
--- /dev/null
+++ b/browser/modules/SitePermissions.jsm
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [ "SitePermissions" ];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+let gStringBundle =
+  Services.strings.createBundle("chrome://browser/locale/sitePermissions.properties");
+
+this.SitePermissions = {
+
+  UNKNOWN: Services.perms.UNKNOWN_ACTION,
+  ALLOW: Services.perms.ALLOW_ACTION,
+  BLOCK: Services.perms.DENY_ACTION,
+  SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
+
+  /* Checks whether a UI for managing permissions should be exposed for a given
+   * URI. This excludes file URIs, for instance, as they don't have a host,
+   * even though nsIPermissionManager can still handle them.
+   */
+  isSupportedURI: function (aURI) {
+    return aURI.schemeIs("http") || aURI.schemeIs("https");
+  },
+
+  /* Returns an array of all permission IDs.
+   */
+  listPermissions: function () {
+    return Object.keys(gPermissionObject);
+  },
+
+  /* Returns an array of permission states to be exposed to the user for a
+   * permission with the given ID.
+   */
+  getAvailableStates: function (aPermissionID) {
+    return gPermissionObject[aPermissionID].states ||
+           [ SitePermissions.ALLOW, SitePermissions.BLOCK ];
+  },
+
+  /* Returns the state of a perticular permission for a given URI.
+   */
+  get: function (aURI, aPermissionID) {
+    if (!this.isSupportedURI(aURI))
+      return this.UNKNOWN;
+
+    let state;
+    if (gPermissionObject[aPermissionID].exactHostMatch)
+      state = Services.perms.testExactPermission(aURI, aPermissionID);
+    else
+      state = Services.perms.testPermission(aURI, aPermissionID);
+    return state;
+  },
+
+  /* Sets the state of a perticular permission for a given URI.
+   */
+  set: function (aURI, aPermissionID, aState) {
+    if (!this.isSupportedURI(aURI))
+      return;
+
+    Services.perms.add(aURI, aPermissionID, aState);
+
+    if (gPermissionObject[aPermissionID].onSet)
+      gPermissionObject[aPermissionID].onSet(aURI, aState);
+  },
+
+  /* Returns the localized label for the permission with the given ID, to be
+   * used in a UI for managing permissions.
+   */
+  getPermissionLabel: function (aPermissionID) {
+    return gStringBundle.GetStringFromName("permission." + aPermissionID + ".label");
+  },
+
+  /* Returns the localized label for the given permission state, to be used in
+   * a UI for managing permissions.
+   */
+  getStateLabel: function (aState) {
+    switch (aState) {
+      case this.ALLOW:
+        return gStringBundle.GetStringFromName("allow");
+      case this.SESSION:
+        return gStringBundle.GetStringFromName("allowForSession");
+      case this.BLOCK:
+        return gStringBundle.GetStringFromName("block");
+      default:
+        throw new Error("unknown permission state");
+    }
+  }
+};
+
+let gPermissionObject = {
+  /* Holds permission ID => options pairs.
+   *
+   * Supported options:
+   *
+   *  - exactHostMatch
+   *    Allows sub domains to have their own permissions.
+   *    Defaults to false.
+   *
+   *  - onSet
+   *    Called when a permission state changes.
+   *
+   *  - states
+   *    Array of permission states to be exposed to the user.
+   *    Defaults to ALLOW and BLOCK.
+   */
+
+  "image": {},
+
+  "cookie": {
+    states: [ SitePermissions.ALLOW, SitePermissions.SESSION, SitePermissions.BLOCK ]
+  },
+
+  "desktop-notification": {},
+
+  "popup": {},
+
+  "install": {},
+
+  "geo": {
+    exactHostMatch: true
+  },
+
+  "indexedDB": {
+    onSet: function (aURI, aState) {
+      if (aState == SitePermissions.ALLOW || aState == SitePermissions.BLOCK)
+        Services.perms.remove(aURI.host, "indexedDB-unlimited");
+    }
+  },
+
+  "fullscreen": {},
+
+  "pointerLock": {
+    exactHostMatch: true
+  }
+};
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1112,19 +1112,29 @@ toolbar[iconsize="small"] #webrtc-status
   -moz-margin-start: -18px;
 }
 
 #identity-popup-content-box.verifiedIdentity > #identity-popup-encryption > vbox > #identity-popup-encryption-icon ,
 #identity-popup-content-box.verifiedDomain > #identity-popup-encryption > vbox > #identity-popup-encryption-icon {
   list-style-image: url("chrome://browser/skin/Secure.png");
 }
 
-/* Identity popup bounding box */
+#identity-popup > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
 #identity-popup-container {
   min-width: 280px;
+  padding: 10px;
+}
+
+#identity-popup-button-container {
+  background: linear-gradient(to bottom, rgba(0,0,0,0.04) 60%, transparent);
+  padding: 10px;
+  margin-top: 5px;
 }
 
 /* Notification popup */
 #notification-popup {
   min-width: 280px;
 }
 
 .popup-notification-icon {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3007,16 +3007,30 @@ toolbarbutton.chevron > .toolbarbutton-m
 @media (min-resolution: 2dppx) {
   #identity-popup-content-box.verifiedIdentity > #identity-popup-encryption > vbox > #identity-popup-encryption-icon ,
   #identity-popup-content-box.verifiedDomain > #identity-popup-encryption > vbox > #identity-popup-encryption-icon {
     list-style-image: url("chrome://browser/skin/Secure-Glyph@2x.png");
     width: 24px;
   }
 }
 
+#identity-popup > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+#identity-popup-container {
+  padding: 16px;
+}
+
+#identity-popup-button-container {
+  background: linear-gradient(to bottom, rgba(0,0,0,0.04) 60%, transparent);
+  padding: 16px;
+  margin-top: 5px;
+}
+
 #notification-popup-box {
   position: relative;
   background-color: #fff;
   background-clip: padding-box;
   padding-left: 3px;
   border-radius: 2px 0 0 2px;
   border-width: 0 8px 0 0;
   border-style: solid;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2356,16 +2356,31 @@ toolbarbutton.bookmark-item[dragover="tr
 }
 
 #identity-popup-more-info-button {
   margin-top: 6px;
   margin-bottom: 0;
   -moz-margin-end: 0;
 }
 
+#identity-popup > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+#identity-popup-container {
+  min-width: 280px;
+  padding: 10px;
+}
+
+#identity-popup-button-container {
+  background: linear-gradient(to bottom, rgba(0,0,0,0.04) 60%, transparent);
+  padding: 10px;
+  margin-top: 5px;
+}
+
 .popup-notification-icon {
   width: 64px;
   height: 64px;
   -moz-margin-end: 10px;
 }
 
 .popup-notification-icon[popupid="geolocation"] {
   list-style-image: url(chrome://browser/skin/Geolocation-64.png);
@@ -2573,20 +2588,16 @@ toolbarbutton.bookmark-item[dragover="tr
 
 #pointerLock-notification-icon {
   list-style-image: url(chrome://browser/skin/pointerLock-16.png);
 }
 #pointerLock-cancel {
   margin: 0px;
 }
 
-#identity-popup-container {
-  min-width: 280px;
-}
-
 /* Bookmarks roots menu-items */
 #appmenu_subscribeToPage:not([disabled]),
 #appmenu_subscribeToPageMenu,
 #subscribeToPageMenuitem:not([disabled]),
 #subscribeToPageMenupopup,
 #BMB_subscribeToPageMenuitem:not([disabled]),
 #BMB_subscribeToPageMenupopup {
   list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
--- a/build/mobile/sutagent/android/WifiConfiguration.java
+++ b/build/mobile/sutagent/android/WifiConfiguration.java
@@ -300,17 +300,17 @@ public class WifiConfiguration implement
         for (int i = 0; i < wepKeys.length; i++)
             wepKeys[i] = null;
         for (EnterpriseField field : enterpriseFields) {
             field.setValue(null);
         }
     }
 
     public String toString() {
-        StringBuffer sbuf = new StringBuffer();
+        StringBuilder sbuf = new StringBuilder();
         if (this.status == WifiConfiguration.Status.CURRENT) {
             sbuf.append("* ");
         } else if (this.status == WifiConfiguration.Status.DISABLED) {
             sbuf.append("- ");
         }
         sbuf.append("ID: ").append(this.networkId).append(" SSID: ").append(this.SSID).
                 append(" BSSID: ").append(this.BSSID).append(" PRIO: ").append(this.priority).
                 append('\n');
--- a/configure.in
+++ b/configure.in
@@ -4579,17 +4579,17 @@ cairo-os2)
     TK_LIBS='$(MOZ_CAIRO_LIBS) $(MOZ_PIXMAN_LIBS)'
     MOZ_PDF_PRINTING=1
     ;;
 
 cairo-cocoa)
     MOZ_WIDGET_TOOLKIT=cocoa
     AC_DEFINE(MOZ_WIDGET_COCOA)
     LDFLAGS="$LDFLAGS -framework Cocoa -lobjc"
-    TK_LIBS='-framework QuartzCore -framework Carbon -framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework AddressBook -framework OpenGL'
+    TK_LIBS='-framework CoreLocation -framework QuartzCore -framework Carbon -framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework AddressBook -framework OpenGL'
     TK_CFLAGS="-DNO_X11"
     CFLAGS="$CFLAGS $TK_CFLAGS"
     CXXFLAGS="$CXXFLAGS $TK_CFLAGS"
     DYNAMIC_XPCOM_LIBS='$(LIBXUL_DIST)/bin/XUL -lxpcom_core -lmozalloc'
     XPCOM_FROZEN_LDOPTS='$(LIBXUL_DIST)/bin/XUL -lmozalloc'
     MOZ_USER_DIR="Mozilla"
     MOZ_FS_LAYOUT=bundle
     MOZ_WEBGL=1
@@ -9359,17 +9359,17 @@ fi
 # Run freetype configure script
 
 if test "$MOZ_TREE_FREETYPE"; then
    export CFLAGS="$CFLAGS $MOZ_DEBUG_FLAGS -std=c99"
    export CPPFLAGS="$CPPFLAGS $MOZ_DEBUG_FLAGS"
    export CXXFLAGS="$CXXFLAGS $MOZ_DEBUG_FLAGS"
    export LDFLAGS="$LDFLAGS $MOZ_DEBUG_LDFLAGS"
    export CONFIG_FILES="unix-cc.mk:unix-cc.in unix-def.mk:unix-def.in freetype-config freetype2.pc:freetype2.in"
-   ac_configure_args="$ac_configure_args --host=$target --disable-shared --with-pic=yes"
+   ac_configure_args="$ac_configure_args --host=$target --disable-shared --with-pic=yes --without-png"
 
    if ! test -e modules; then
      mkdir modules
    fi
 
    AC_OUTPUT_SUBDIRS(modules/freetype2)
 fi
 
@@ -9477,16 +9477,21 @@ if test -n "$ENABLE_CLANG_PLUGIN"; then
     AC_OUTPUT_SUBDIRS(build/clang-plugin)
 fi
 
 
 # Run the SpiderMonkey 'configure' script.
 dist=$MOZ_BUILD_ROOT/dist
 ac_configure_args="$_SUBDIR_CONFIG_ARGS"
 ac_configure_args="$ac_configure_args --enable-threadsafe"
+if test "A" = "A"; then # "$MOZ_BUILDAPP" != "browser", when desktop builds this
+    # The Internationalization API is currently disabled in all non-standalone
+    # SpiderMonkey builds, but it should be turned on for desktop builds soon.
+    ac_configure_args="$ac_configure_args --disable-intl-api"
+fi
 if test "$BUILD_CTYPES"; then
     # Build js-ctypes on the platforms we can.
     ac_configure_args="$ac_configure_args --enable-ctypes"
 fi
 if test -z "$JS_SHARED_LIBRARY" ; then
     ac_configure_args="$ac_configure_args --disable-shared-js"
 fi
 if test -z "$MOZ_NATIVE_NSPR"; then
--- a/content/base/public/Element.h
+++ b/content/base/public/Element.h
@@ -917,16 +917,32 @@ public:
    * Sets value of boolean attribute by removing attribute or setting it to
    * the empty string. Only works for attributes in null namespace.
    *
    * @param aAttr    name of attribute.
    * @param aValue   Boolean value of attribute.
    */
   NS_HIDDEN_(nsresult) SetBoolAttr(nsIAtom* aAttr, bool aValue);
 
+  /**
+   * Retrieve the ratio of font-size-inflated text font size to computed font
+   * size for this element. This will query the element for its primary frame,
+   * and then use this to get font size inflation information about the frame.
+   *
+   * @returns The font size inflation ratio (inflated font size to uninflated
+   *          font size) for the primary frame of this element. Returns 1.0
+   *          by default if font size inflation is not enabled. Returns -1
+   *          if the element does not have a primary frame.
+   *
+   * @note The font size inflation ratio that is returned is actually the
+   *       font size inflation data for the element's _primary frame_, not the
+   *       element itself, but for most purposes, this should be sufficient.
+   */
+  float FontSizeInflation();
+
 protected:
   /*
    * Named-bools for use with SetAttrAndNotify to make call sites easier to
    * read.
    */
   static const bool kFireMutationEvent           = true;
   static const bool kDontFireMutationEvent       = false;
   static const bool kNotifyDocumentObservers     = true;
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -512,25 +512,53 @@ CSPRep.fromStringSpecCompliant = functio
     // clean userpass out of the URI (not used for CSP origin checking, but
     // shows up in prePath).
     try {
       // GetUserPass throws for some protocols without userPass
       selfUri.userPass = '';
     } catch (ex) {}
   }
 
-  var dirs = aStr.split(";");
-
-  directive:
-  for each(var dir in dirs) {
+  var dirs_list = aStr.split(";");
+  var dirs = {};
+  for each(var dir in dirs_list) {
     dir = dir.trim();
     if (dir.length < 1) continue;
 
     var dirname = dir.split(/\s+/)[0];
     var dirvalue = dir.substring(dirname.length).trim();
+    dirs[dirname] = dirvalue;
+  }
+
+  // Spec compliant policies have different default behavior for inline
+  // scripts, styles, and eval. Bug 885433
+  aCSPR._allowEval = true;
+  aCSPR._allowInlineScripts = true;
+  aCSPR._allowInlineStyles = true;
+
+  // In CSP 1.0, you need to opt-in to blocking inline scripts and eval by
+  // specifying either default-src or script-src, and to blocking inline
+  // styles by specifying either default-src or style-src.
+  if ("default-src" in dirs) {
+    aCSPR._allowInlineScripts = false;
+    aCSPR._allowInlineStyles = false;
+    aCSPR._allowEval = false;
+  } else {
+    if ("script-src" in dirs) {
+      aCSPR._allowInlineScripts = false;
+      aCSPR._allowEval = false;
+    }
+    if ("style-src" in dirs) {
+      aCSPR._allowInlineStyles = false;
+    }
+  }
+
+  directive:
+  for (var dirname in dirs) {
+    var dirvalue = dirs[dirname];
 
     if (aCSPR._directives.hasOwnProperty(dirname)) {
       // Check for (most) duplicate directives
       cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
                                                 [dirname]));
       CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
       continue directive;
     }
--- a/content/base/src/Element.cpp
+++ b/content/base/src/Element.cpp
@@ -3427,8 +3427,23 @@ nsresult
 Element::SetBoolAttr(nsIAtom* aAttr, bool aValue)
 {
   if (aValue) {
     return SetAttr(kNameSpaceID_None, aAttr, EmptyString(), true);
   }
 
   return UnsetAttr(kNameSpaceID_None, aAttr, true);
 }
+
+float
+Element::FontSizeInflation()
+{
+  nsIFrame* frame = mPrimaryFrame;
+  if (!frame) {
+    return -1.0;
+  }
+
+  if (nsLayoutUtils::FontSizeInflationEnabled(frame->PresContext())) {
+    return nsLayoutUtils::FontSizeInflationFor(frame);
+  }
+
+  return 1.0;
+}
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -701,22 +701,23 @@ class nsDOMMemoryFileDataOwnerMemoryRepo
         aClosure);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return NS_OK;
   }
 };
 
-NS_IMPL_ISUPPORTS1(nsDOMMemoryFileDataOwnerMemoryReporter,
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMMemoryFileDataOwnerMemoryReporter,
                    nsIMemoryMultiReporter)
 
 /* static */ void
 nsDOMMemoryFile::DataOwner::EnsureMemoryReporterRegistered()
 {
+  sDataOwnerMutex.AssertCurrentThreadOwns();
   if (sMemoryReporterRegistered) {
     return;
   }
 
   nsRefPtr<nsDOMMemoryFileDataOwnerMemoryReporter> reporter = new
     nsDOMMemoryFileDataOwnerMemoryReporter();
   NS_RegisterMemoryMultiReporter(reporter);
 
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -812,16 +812,23 @@ nsFrameMessageManager::RemoveFromParent(
   mParentManager = nullptr;
   mCallback = nullptr;
   mOwnedCallback = nullptr;
 }
 
 void
 nsFrameMessageManager::Disconnect(bool aRemoveFromParent)
 {
+  if (!mDisconnected) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+       obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this),
+                            "message-manager-disconnect", nullptr);
+    }
+  }
   if (mParentManager && aRemoveFromParent) {
     mParentManager->RemoveChildManager(this);
   }
   mDisconnected = true;
   mParentManager = nullptr;
   mCallback = nullptr;
   mOwnedCallback = nullptr;
   if (!mHandlingMessage) {
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -2010,32 +2010,40 @@ GK_ATOM(windows_theme_aero, "windows-the
 GK_ATOM(windows_theme_aero_lite, "windows-theme-aero-lite")
 GK_ATOM(windows_theme_luna_blue, "windows-theme-luna-blue")
 GK_ATOM(windows_theme_luna_olive, "windows-theme-luna-olive")
 GK_ATOM(windows_theme_luna_silver, "windows-theme-luna-silver")
 GK_ATOM(windows_theme_royale, "windows-theme-royale")
 GK_ATOM(windows_theme_zune, "windows-theme-zune")
 GK_ATOM(windows_theme_generic, "windows-theme-generic")
 
+// windows version info selector metrics, helpful in removing ambiguity
+// in theme selectors.
+GK_ATOM(windows_version_xp, "windows-version-xp")
+GK_ATOM(windows_version_vista, "windows-version-vista")
+GK_ATOM(windows_version_win7, "windows-version-win7")
+GK_ATOM(windows_version_win8, "windows-version-win8")
+
 // And the same again, as media query keywords.
 GK_ATOM(_moz_scrollbar_start_backward, "-moz-scrollbar-start-backward")
 GK_ATOM(_moz_scrollbar_start_forward, "-moz-scrollbar-start-forward")
 GK_ATOM(_moz_scrollbar_end_backward, "-moz-scrollbar-end-backward")
 GK_ATOM(_moz_scrollbar_end_forward, "-moz-scrollbar-end-forward")
 GK_ATOM(_moz_scrollbar_thumb_proportional, "-moz-scrollbar-thumb-proportional")
 GK_ATOM(_moz_images_in_menus, "-moz-images-in-menus")
 GK_ATOM(_moz_images_in_buttons, "-moz-images-in-buttons")
 GK_ATOM(_moz_overlay_scrollbars, "-moz-overlay-scrollbars")
 GK_ATOM(_moz_windows_default_theme, "-moz-windows-default-theme")
 GK_ATOM(_moz_mac_graphite_theme, "-moz-mac-graphite-theme")
 GK_ATOM(_moz_mac_lion_theme, "-moz-mac-lion-theme")
 GK_ATOM(_moz_windows_compositor, "-moz-windows-compositor")
 GK_ATOM(_moz_windows_classic, "-moz-windows-classic")
 GK_ATOM(_moz_windows_glass, "-moz-windows-glass")
 GK_ATOM(_moz_windows_theme, "-moz-windows-theme")
+GK_ATOM(_moz_os_version, "-moz-os-version")
 GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
 GK_ATOM(_moz_maemo_classic, "-moz-maemo-classic")
 GK_ATOM(_moz_menubar_drag, "-moz-menubar-drag")
 GK_ATOM(_moz_device_pixel_ratio, "-moz-device-pixel-ratio")
 GK_ATOM(_moz_device_orientation, "-moz-device-orientation")
 GK_ATOM(_moz_is_resource_document, "-moz-is-resource-document")
 GK_ATOM(_moz_swipe_animation_enabled, "-moz-swipe-animation-enabled")
 GK_ATOM(_moz_physical_home_button, "-moz-physical-home-button")
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -626,16 +626,21 @@ MOCHITEST_FILES_C= \
 		badMessageEvent2.eventsource \
 		badMessageEvent2.eventsource^headers^ \
 		test_object.html \
 		test_bug869006.html \
 		test_bug868999.html \
 		test_bug869000.html \
 		test_bug869002.html \
 		test_bug876282.html \
+		test_CSP_bug885433.html \
+		file_CSP_bug885433_allows.html \
+		file_CSP_bug885433_allows.html^headers^ \
+		file_CSP_bug885433_blocks.html \
+		file_CSP_bug885433_blocks.html^headers^ \
 		$(NULL)
 
 # OOP tests don't work on Windows (bug 763081) or native-fennec
 # (see Bug 774939)
 ifneq ($(OS_ARCH),WINNT)
 ifndef MOZ_ANDROID_OMTC
 MOCHITEST_FILES_B += \
 		test_messagemanager_assertpermission.html \
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_CSP_bug885433_allows.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<!--
+The Content-Security-Policy header for this file is:
+
+  Content-Security-Policy: img-src 'self';
+
+It does not include any of the default-src, script-src, or style-src
+directives. It should allow the use of unsafe-inline and unsafe-eval on
+scripts, and unsafe-inline on styles, because no directives related to scripts
+or styles are specified.
+-->
+<html>
+<body>
+  <ol>
+    <li id="unsafe-inline-script-allowed">Inline script allowed (this text should be green)</li>
+    <li id="unsafe-eval-script-allowed">Eval script allowed (this text should be green)</li>
+    <li id="unsafe-inline-style-allowed">Inline style allowed (this text should be green)</li>
+  </ol>
+
+  <script>
+    // Use inline script to set a style attribute
+    document.getElementById("unsafe-inline-script-allowed").style.color = "green";
+
+    // Use eval to set a style attribute
+    // try/catch is used because CSP causes eval to throw an exception when it
+    // is blocked, which would derail the rest of the tests  in this file.
+    try {
+      eval('document.getElementById("unsafe-eval-script-allowed").style.color = "green";');
+    } catch (e) {}
+  </script>
+
+  <style>
+    li#unsafe-inline-style-allowed {
+      color: green;
+    }
+  </style>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_CSP_bug885433_allows.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: img-src 'self';
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_CSP_bug885433_blocks.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<!--
+The Content-Security-Policy header for this file is:
+
+  Content-Security-Policy: default-src 'self';
+
+The Content-Security-Policy header for this file includes the default-src
+directive, which triggers the default behavior of blocking unsafe-inline and
+unsafe-eval on scripts, and unsafe-inline on styles.
+-->
+<html>
+<body>
+  <ol>
+    <li id="unsafe-inline-script-blocked">Inline script blocked (this text should be black)</li>
+    <li id="unsafe-eval-script-blocked">Eval script blocked (this text should be black)</li>
+    <li id="unsafe-inline-style-blocked">Inline style blocked (this text should be black)</li>
+  </ol>
+
+  <script>
+    // Use inline script to set a style attribute
+    document.getElementById("unsafe-inline-script-blocked").style.color = "green";
+
+    // Use eval to set a style attribute
+    // try/catch is used because CSP causes eval to throw an exception when it
+    // is blocked, which would derail the rest of the tests  in this file.
+    try {
+      eval('document.getElementById("unsafe-eval-script-blocked").style.color = "green";');
+    } catch (e) {}
+  </script>
+
+  <style>
+    li#unsafe-inline-style-blocked {
+      color: green;
+    }
+  </style>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_CSP_bug885433_blocks.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: default-src 'self';
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_CSP_bug885433.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Content Security Policy inline stylesheets stuff</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe style="width:100%;" id='cspframe'></iframe>
+<iframe style="width:100%;" id='cspframe2'></iframe>
+<script class="testbody" type="text/javascript">
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+SimpleTest.waitForExplicitFinish();
+
+// utilities for check functions
+// black means the style wasn't applied, applied styles are green
+var green = 'rgb(0, 128, 0)';
+var black = 'rgb(0, 0, 0)';
+
+// We test both script and style execution by observing changes in computed styles
+function checkAllowed () {
+  var cspframe = document.getElementById('cspframe');
+  var color;
+
+  color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-inline-script-allowed')).color;
+  ok(color === green, "Inline script should be allowed");
+  color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-eval-script-allowed')).color;
+  ok(color === green, "Eval should be allowed");
+  color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-inline-style-allowed')).color;
+  ok(color === green, "Inline style should be allowed");
+}
+
+function checkBlocked () {
+  var cspframe = document.getElementById('cspframe2');
+  var color;
+
+  color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-inline-script-blocked')).color;
+  ok(color === black, "Inline script should be blocked");
+  color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-eval-script-blocked')).color;
+  ok(color === black, "Eval should be blocked");
+  color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-inline-style-blocked')).color;
+  ok(color === black, "Inline style should be blocked");
+
+  SimpleTest.finish();
+}
+
+SpecialPowers.pushPrefEnv(
+  {'set':[["security.csp.speccompliant", true]]},
+  function () {
+    document.getElementById('cspframe').src = 'file_CSP_bug885433_allows.html';
+    document.getElementById('cspframe').addEventListener('load', checkAllowed, false);
+    document.getElementById('cspframe2').src = 'file_CSP_bug885433_blocks.html';
+    document.getElementById('cspframe2').addEventListener('load', checkBlocked, false);
+  }
+);
+</script>
+</pre>
+</body>
+</html>
--- a/content/base/test/test_child_process_shutdown_message.html
+++ b/content/base/test/test_child_process_shutdown_message.html
@@ -17,16 +17,18 @@
 <script class="testbody" type="application/javascript;version=1.8">
 
 const APP_URL = "http://example.org";
 const APP_MANIFEST = "http://example.org/manifest.webapp";
 const CHILD_PROCESS_SHUTDOWN_MESSAGE = "child-process-shutdown";
 
 let ppmm = SpecialPowers.Cc["@mozilla.org/parentprocessmessagemanager;1"]
                         .getService(SpecialPowers.Ci.nsIMessageBroadcaster);
+let obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+                       .getService(SpecialPowers.Ci.nsIObserverService);
 
 /**
  * Load the example.org site in an <iframe mozbrowser>
  *
  * @param isApp
  *        If true, the example.org site will be loaded as an app.
  */
 function loadBrowser(isApp, callback) {
@@ -66,27 +68,27 @@ function prepareProcess(frameMM, callbac
   ppmm.addMessageListener("TestChild:Ohai", function receiveMessage(msg) {
     ppmm.removeMessageListener("TestChild:Ohai", receiveMessage);
     msg = SpecialPowers.wrap(msg);
     callback(msg.target);
   });
 }
 
 /**
- * Expects an OOP frame's process to shut down and report three
+ * Expects an OOP frame's process to shut down and report four
  * events/messages: an error event on the browser element, and a
  * 'child-process-shutdown' message on both the frame and process
  * message managers.
  */
 function expectFrameProcessShutdown(iframe, frameMM, processMM, callback) {
   let msgCount = 0;
   function countMessage() {
     msgCount += 1;
-    if (msgCount == 3) {
-      ok(true, "Observed all three expected events.");
+    if (msgCount == 4) {
+      ok(true, "Observed all four expected events.");
       callback();
     }
   };
 
   iframe.addEventListener("mozbrowsererror", function onerror(event) {
     iframe.removeEventListener("mozbrowsererror", onerror);
     is(event.detail.type, "fatal", "Observed expected event.");
     countMessage();
@@ -98,16 +100,25 @@ function expectFrameProcessShutdown(ifra
     countMessage();
   });
 
   frameMM.addMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, function receiveMessage() {
     frameMM.removeMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, receiveMessage);
     ok(true, "Received 'child-process-shutdown' message from frame message manager.");
     countMessage();
   });
+
+  obs.addObserver(function observe(subject, type, data) {
+    if (subject == SpecialPowers.unwrap(processMM)) {
+      obs.removeObserver(observe, "message-manager-disconnect");
+      ok(true, "Received 'message-manager-disconnect' notification with " +
+               "frame message manager");
+      countMessage();
+    }
+  }, "message-manager-disconnect", false);
 }
 
 function setUp() {
   SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
   SpecialPowers.setBoolPref("dom.ipc.browser_frames.oop_by_default", true);
   SpecialPowers.addPermission("browser", true, window.document);
   SpecialPowers.addPermission("embed-apps", true, window.document);
 
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -409,20 +409,26 @@ WebGLContext::BufferData(WebGLenum targe
         return ErrorInvalidValue("bufferData: negative size");
 
     if (!ValidateBufferUsageEnum(usage, "bufferData: usage"))
         return;
 
     if (!boundBuffer)
         return ErrorInvalidOperation("bufferData: no buffer bound!");
 
+    void* zeroBuffer = calloc(size, 1);
+    if (!zeroBuffer)
+        return ErrorOutOfMemory("bufferData: out of memory");
+
     MakeContextCurrent();
     InvalidateCachedMinInUseAttribArrayLength();
 
-    GLenum error = CheckedBufferData(target, size, 0, usage);
+    GLenum error = CheckedBufferData(target, size, zeroBuffer, usage);
+    free(zeroBuffer);
+
     if (error) {
         GenerateWarning("bufferData generated error %s", ErrorName(error));
         return;
     }
 
     boundBuffer->SetByteLength(size);
     if (!boundBuffer->ElementArrayCacheBufferData(nullptr, size)) {
         return ErrorOutOfMemory("bufferData: out of memory");
--- a/content/events/src/moz.build
+++ b/content/events/src/moz.build
@@ -11,20 +11,22 @@ EXPORTS += [
     'nsDOMEventTargetHelper.h',
     'nsDOMTouchEvent.h',
     'nsDOMUIEvent.h',
     'nsEventListenerManager.h',
     'nsEventStateManager.h',
 ]
 
 EXPORTS.mozilla.dom += [
-    'SpeechRecognitionError.h',
     'Touch.h',
 ]
 
+if CONFIG['MOZ_WEBSPEECH']:
+    EXPORTS.mozilla.dom += ['SpeechRecognitionError.h']
+
 CPP_SOURCES += [
     'DOMWheelEvent.cpp',
     'EventTarget.cpp',
     'TextComposition.cpp',
     'Touch.cpp',
     'nsAsyncDOMEvent.cpp',
     'nsContentEventHandler.cpp',
     'nsDOMAnimationEvent.cpp',
@@ -55,10 +57,12 @@ CPP_SOURCES += [
     'nsDOMXULCommandEvent.cpp',
     'nsEventDispatcher.cpp',
     'nsEventListenerManager.cpp',
     'nsEventListenerService.cpp',
     'nsEventStateManager.cpp',
     'nsIMEStateManager.cpp',
     'nsPaintRequest.cpp',
     'nsPrivateTextRange.cpp',
-    'SpeechRecognitionError.cpp',
 ]
+
+if CONFIG['MOZ_WEBSPEECH']:
+    CPP_SOURCES += ['SpeechRecognitionError.cpp']
--- a/content/html/content/test/forms/test_input_file_picker.html
+++ b/content/html/content/test/forms/test_input_file_picker.html
@@ -18,36 +18,38 @@
   <input id='c' type='file' accept="video/*">
   <input id='d' type='file' accept="image/*, audio/* ">
   <input id='e' type='file' accept=" image/*,video/*">
   <input id='f' type='file' accept="audio/*,video/*">
   <input id='g' type='file' accept="image/*, audio/* ,video/*">
   <input id='h' type='file' accept="foo/baz,image/*,bogus/duh">
   <input id='i' type='file' accept="mime/type;parameter,video/*">
   <input id='j' type='file' accept="audio/*, audio/*, audio/*">
-  <input id='k' type="file" accept="image/gif,image/png" />
-  <input id='l' type="file" accept="image/*,image/gif,image/png" />
-  <input id='m' type="file" accept="image/gif,image/gif" />
-  <input id='n' type="file" accept="" />
+  <input id='k' type="file" accept="image/gif,image/png">
+  <input id='l' type="file" accept="image/*,image/gif,image/png">
+  <input id='m' type="file" accept="image/gif,image/gif">
+  <input id='n' type="file" accept="">
   <input id='z' type='file' accept="i/am,a,pathological,;,,,,test/case">
   <input id='hidden' hidden type='file'>
   <input id='untrusted-click' type='file'>
   <input id='prevent-default' type='file'>
   <input id='prevent-default-false' type='file'>
   <input id='right-click' type='file'>
   <input id='middle-click' type='file'>
   <input id='left-click' type='file'>
   <label id='label-1'>foo<input type='file'></label>
   <label id='label-2' for='labeled-2'>foo</label><input id='labeled-2' type='file'></label>
   <label id='label-3'>foo<input type='file'></label>
   <label id='label-4' for='labeled-4'>foo</label><input id='labeled-4' type='file'></label>
   <input id='by-button' type='file'>
   <button id='button-click' onclick="document.getElementById('by-button').click();">foo</button>
   <button id='button-down' onclick="document.getElementById('by-button').click();">foo</button>
   <button id='button-up' onclick="document.getElementById('by-button').click();">foo</button>
+  <div id='div-click' onclick="document.getElementById('by-button').click();" tabindex='1'>foo</div>
+  <div id='div-click-on-demand' onclick="var i=document.createElement('input'); i.type='file'; i.click();" tabindex='1'>foo</div>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /**
  * This test checks various scenarios and make sure that a file picker is being
  * shown in all of them (minus a few exceptions).
  * |testData| defines the tests to do and |launchNextTest| can be used to have
@@ -89,16 +91,18 @@ var testData = [["a", 1, MockFilePicker.
                 ["left-click", 0, undefined, 0],
                 ["label-1", 0, undefined, 0],
                 ["label-2", 0, undefined, 0],
                 ["label-3", 0, undefined, 0],
                 ["label-4", 0, undefined, 0],
                 ["button-click", 0, undefined, 0],
                 ["button-down", 0, undefined, 0],
                 ["button-up", 0, undefined, 0],
+                ["div-click", 0, undefined, 0],
+                ["div-click-on-demand", 0, undefined, 0],
                ];
 
 var currentTest = 0;
 var filterAllAdded;
 var filters;
 var filterIndex;
 
 // disable popups to make sure that the popup blocker does not interfere
@@ -149,17 +153,19 @@ function launchNextTest() {
       ++currentTest;
       launchNextTest();
     }, 500);
   } else if (testData[currentTest][0] == 'label-3' ||
              testData[currentTest][0] == 'label-4') {
     synthesizeMouse(document.getElementById(testData[currentTest][0]), 5, 5, {});
   } else if (testData[currentTest][0] == 'button-click' ||
              testData[currentTest][0] == 'button-down' ||
-             testData[currentTest][0] == 'button-up') {
+             testData[currentTest][0] == 'button-up' ||
+             testData[currentTest][0] == 'div-click' ||
+             testData[currentTest][0] == 'div-click-on-demand') {
     synthesizeMouseAtCenter(document.getElementById(testData[currentTest][0]), {});
   } else {
     document.getElementById(testData[currentTest][0]).click();
   }
 }
 
 function runTests() {
   MockFilePicker.appendFilterCallback = function(filepicker, title, val) {
@@ -188,36 +194,23 @@ function runTests() {
       is(filters.length, testData[currentTest][1],
          "appendFilters not called as often as expected (" + testName + ")");
       is(filters[0], testData[currentTest][2],
          "Correct filters should have been added (" + testName + ")");
       is(filterIndex, testData[currentTest][3],
          "File picker should show the correct filter index (" + testName + ")");
 
       if (++currentTest == testData.length) {
-        setTimeout(testDisconnectedElement, 0);
+        MockFilePicker.cleanup();
+        SimpleTest.finish();
       } else {
         launchNextTest();
       }
     });
   };
 
   launchNextTest();
 }
 
-function testDisconnectedElement() {
-  MockFilePicker.shown = false;
-  MockFilePicker.showCallback = function(filepicker) {
-    ok(MockFilePicker.shown, "FilePicker should be open!");
-    MockFilePicker.shown = false;
-    MockFilePicker.cleanup();
-    SimpleTest.finish();
-  }
-  var f = document.createElement("input");
-  f.setAttribute("type", "file");
-  f.click();
-  ok(!MockFilePicker.shown, "FilePicker should open asynchronously!");
-}
-
 </script>
 </pre>
 </body>
 </html>
--- a/content/media/DecoderTraits.cpp
+++ b/content/media/DecoderTraits.cpp
@@ -513,17 +513,19 @@ MediaDecoderReader* DecoderTraits::Creat
 /* static */
 bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
 {
   return
 #ifdef MOZ_OGG
     IsOggType(aType) ||
 #endif
 #ifdef MOZ_OMX_DECODER
-    IsOmxSupportedType(aType) ||
+    // We support amr inside WebApps on firefoxOS but not in general web content.
+    // Ensure we dont create a VideoDocument when accessing amr URLs directly.
+    (IsOmxSupportedType(aType) && !aType.EqualsASCII("audio/amr")) ||
 #endif
 #ifdef MOZ_WEBM
     IsWebMType(aType) ||
 #endif
 #ifdef MOZ_DASH
     IsDASHMPDType(aType) ||
 #endif
 #ifdef MOZ_GSTREAMER
--- a/content/media/omx/OmxDecoder.cpp
+++ b/content/media/omx/OmxDecoder.cpp
@@ -146,17 +146,18 @@ OmxDecoder::OmxDecoder(MediaResource *aR
   mVideoRotation(0),
   mAudioChannels(-1),
   mAudioSampleRate(-1),
   mDurationUs(-1),
   mVideoBuffer(nullptr),
   mAudioBuffer(nullptr),
   mIsVideoSeeking(false),
   mAudioMetadataRead(false),
-  mPaused(false)
+  mAudioPaused(false),
+  mVideoPaused(false)
 {
   mLooper = new ALooper;
   mLooper->setName("OmxDecoder");
 
   mReflector = new AHandlerReflector<OmxDecoder>(this);
   // Register AMessage handler to ALooper.
   mLooper->registerHandler(mReflector);
   // Start ALooper thread.
@@ -731,43 +732,54 @@ bool OmxDecoder::ReadAudio(AudioFrame *a
     return false;
   }
 
   return true;
 }
 
 nsresult OmxDecoder::Play()
 {
-  if (!mPaused) {
+  if (!mVideoPaused && !mAudioPaused) {
     return NS_OK;
   }
-  if (mVideoSource.get() && mVideoSource->start() != OK) {
+
+  if (mVideoPaused && mVideoSource.get() && mVideoSource->start() != OK) {
     return NS_ERROR_UNEXPECTED;
   }
+  mVideoPaused = false;
 
-  if (mAudioSource.get()&& mAudioSource->start() != OK) {
+  if (mAudioPaused && mAudioSource.get() && mAudioSource->start() != OK) {
     return NS_ERROR_UNEXPECTED;
   }
-  mPaused = false;
+  mAudioPaused = false;
+
   return NS_OK;
 }
 
+// AOSP didn't give implementation on OMXCodec::Pause() and not define
+// OMXCodec::Start() should be called for resuming the decoding. Currently
+// it is customized by a specific open source repository only.
+// ToDo The one not supported OMXCodec::Pause() should return error code here,
+// so OMXCodec::Start() doesn't be called again for resuming. But if someone
+// implement the OMXCodec::Pause() and need a following OMXCodec::Read() with
+// seek option (define in MediaSource.h) then it is still not supported here.
+// We need to fix it until it is really happened.
 void OmxDecoder::Pause()
 {
-  if (mPaused) {
+  if (mVideoPaused || mAudioPaused) {
     return;
   }
-  if (mVideoSource.get()) {
-    mVideoSource->pause();
+
+  if (mVideoSource.get() && mVideoSource->pause() == OK) {
+    mVideoPaused = true;
   }
 
-  if (mAudioSource.get()) {
-    mAudioSource->pause();
+  if (mAudioSource.get() && mAudioSource->pause() == OK) {
+    mAudioPaused = true;
   }
-  mPaused = true;
 }
 
 // Called on ALooper thread.
 void OmxDecoder::onMessageReceived(const sp<AMessage> &msg)
 {
   switch (msg->what()) {
     case kNotifyPostReleaseVideoBuffer:
     {
--- a/content/media/omx/OmxDecoder.h
+++ b/content/media/omx/OmxDecoder.h
@@ -153,17 +153,18 @@ class OmxDecoder : public OMXCodecProxy:
   void CbYCrYFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
   void SemiPlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
   void SemiPlanarYVU420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
   bool ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame);
   bool ToAudioFrame(AudioFrame *aFrame, int64_t aTimeUs, void *aData, size_t aDataOffset, size_t aSize,
                     int32_t aAudioChannels, int32_t aAudioSampleRate);
 
   //True if decoder is in a paused state
-  bool mPaused;
+  bool mAudioPaused;
+  bool mVideoPaused;
 
 public:
   OmxDecoder(MediaResource *aResource, AbstractMediaDecoder *aDecoder);
   ~OmxDecoder();
 
   // MediaResourceManagerClient::EventListener
   virtual void statusChanged();
 
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -362,16 +362,17 @@ WebappsApplication.prototype = {
     this._ondownloadavailable = null;
     this._ondownloadapplied = null;
 
     this._downloadError = null;
 
     this.initHelper(aWindow, ["Webapps:OfflineCache",
                               "Webapps:CheckForUpdate:Return:OK",
                               "Webapps:CheckForUpdate:Return:KO",
+                              "Webapps:Launch:Return:OK",
                               "Webapps:Launch:Return:KO",
                               "Webapps:PackageEvent",
                               "Webapps:ClearBrowserData:Return"]);
 
     cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
                           ["Webapps:OfflineCache",
                            "Webapps:PackageEvent",
                            "Webapps:CheckForUpdate:Return:OK"]);
@@ -513,16 +514,19 @@ WebappsApplication.prototype = {
         aMessage.name !== "Webapps:OfflineCache" &&
         aMessage.name !== "Webapps:PackageEvent" &&
         aMessage.name !== "Webapps:CheckForUpdate:Return:OK")
       return;
     switch (aMessage.name) {
       case "Webapps:Launch:Return:KO":
         Services.DOMRequest.fireError(req, "APP_INSTALL_PENDING");
         break;
+      case "Webapps:Launch:Return:OK":
+        Services.DOMRequest.fireSuccess(req);
+        break;
       case "Webapps:CheckForUpdate:Return:KO":
         Services.DOMRequest.fireError(req, msg.error);
         break;
       case "Webapps:CheckForUpdate:Return:OK":
         if (msg.manifestURL != this.manifestURL)
           return;
 
         manifestCache.evict(this.manifestURL, this.innerWindowID);
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -608,19 +608,16 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(File, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(ModalContentWindow, nsWindowSH,
                            DEFAULT_SCRIPTABLE_FLAGS |
                            WINDOW_SCRIPTABLE_FLAGS)
 
-  NS_DEFINE_CLASSINFO_DATA(GeoPositionCoords, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
-
   NS_DEFINE_CLASSINFO_DATA(MozPowerManager, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozWakeLock, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozSmsManager, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
@@ -1548,20 +1545,16 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(ModalContentWindow, nsIDOMWindow)
     DOM_CLASSINFO_WINDOW_MAP_ENTRIES(nsGlobalWindow::HasIndexedDBSupport())
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMModalContentWindow)
 #ifdef MOZ_WEBSPEECH
     DOM_CLASSINFO_MAP_ENTRY(nsISpeechSynthesisGetter)
 #endif
   DOM_CLASSINFO_MAP_END
 
-  DOM_CLASSINFO_MAP_BEGIN(GeoPositionCoords, nsIDOMGeoPositionCoords)
-     DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeoPositionCoords)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(MozPowerManager, nsIDOMMozPowerManager)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozPowerManager)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozWakeLock, nsIDOMMozWakeLock)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozWakeLock)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -95,19 +95,16 @@ DOMCI_CLASS(XPathResult)
 DOMCI_CLASS(Storage)
 
 DOMCI_CLASS(Blob)
 DOMCI_CLASS(File)
 
 // DOM modal content window class, almost identical to Window
 DOMCI_CLASS(ModalContentWindow)
 
-// Geolocation
-DOMCI_CLASS(GeoPositionCoords)
-
 DOMCI_CLASS(MozPowerManager)
 DOMCI_CLASS(MozWakeLock)
 
 DOMCI_CLASS(MozSmsManager)
 DOMCI_CLASS(MozMobileMessageManager)
 DOMCI_CLASS(MozSmsMessage)
 DOMCI_CLASS(MozMmsMessage)
 DOMCI_CLASS(MozSmsFilter)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -564,17 +564,18 @@ public:
     return false;
   }
 
   virtual const char *className(JSContext *cx,
                                 JS::Handle<JSObject*> wrapper) MOZ_OVERRIDE;
   virtual void finalize(JSFreeOp *fop, JSObject *proxy) MOZ_OVERRIDE;
 
   // Fundamental traps
-  virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
+  virtual bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
+                           MOZ_OVERRIDE;
   virtual bool preventExtensions(JSContext *cx,
                                  JS::Handle<JSObject*> proxy) MOZ_OVERRIDE;
   virtual bool getPropertyDescriptor(JSContext* cx,
                                      JS::Handle<JSObject*> proxy,
                                      JS::Handle<jsid> id,
                                      JSPropertyDescriptor* desc,
                                      unsigned flags) MOZ_OVERRIDE;
   virtual bool getOwnPropertyDescriptor(JSContext* cx,
@@ -636,21 +637,23 @@ protected:
                                                    JS::Handle<JSObject*> proxy,
                                                    JS::Handle<jsid> id);
 
   bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
                                   JS::AutoIdVector &props);
 };
 
 bool
-nsOuterWindowProxy::isExtensible(JSObject *proxy)
+nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy,
+                                 bool *extensible)
 {
   // If [[Extensible]] could be false, then navigating a window could navigate
   // to a window that's [[Extensible]] after being at one that wasn't: an
   // invariant violation.  So always report true for this.
+  *extensible = true;
   return true;
 }
 
 bool
 nsOuterWindowProxy::preventExtensions(JSContext *cx,
                                       JS::Handle<JSObject*> proxy)
 {
   // See above.
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -139,19 +139,21 @@ DOMProxyHandler::EnsureExpandoObject(JSC
 
   cache->SetPreservingWrapper(true);
   js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
 
   return expando;
 }
 
 bool
-DOMProxyHandler::isExtensible(JSObject *proxy)
+DOMProxyHandler::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
 {
-  return true; // always extensible per WebIDL
+  // always extensible per WebIDL
+  *extensible = true;
+  return true;
 }
 
 bool
 DOMProxyHandler::preventExtensions(JSContext *cx, JS::Handle<JSObject*> proxy)
 {
   // Throw a TypeError, per WebIDL.
   JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
   return false;
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -46,17 +46,17 @@ public:
     return defineProperty(cx, proxy, id, desc, &unused);
   }
   virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                               JSPropertyDescriptor* desc, bool* defined);
   bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
   bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy, JS::AutoIdVector& props) MOZ_OVERRIDE;
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
-  bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
+  bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible) MOZ_OVERRIDE;
 
   static JSObject* GetExpandoObject(JSObject* obj)
   {
     MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
     JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
     if (v.isObject()) {
       return &v.toObject();
     }
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -1,20 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-let DEBUG = false;
-
-function updateDebug(aResult) {
-  DEBUG = !!aResult;
-}
-
+const DEBUG = false;
 function debug(s) { dump("-*- ContactManager: " + s + "\n"); }
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
@@ -27,20 +22,16 @@ XPCOMUtils.defineLazyGetter(Services, "D
 XPCOMUtils.defineLazyServiceGetter(this, "pm",
                                    "@mozilla.org/permissionmanager;1",
                                    "nsIPermissionManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
-XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
-                                   "@mozilla.org/settingsService;1",
-                                   "nsISettingsService");
-
 const CONTACTS_SENDMORE_MINIMUM = 5;
 
 function stringOrBust(aObj) {
   if (typeof aObj != "string") {
     if (DEBUG) debug("Field is not a string and was ignored.");
     return undefined;
   } else {
     return aObj;
@@ -923,26 +914,16 @@ ContactManager.prototype = {
     this.initHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
                               "Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO",
                               "Contact:Save:Return:OK", "Contact:Save:Return:KO",
                               "Contact:Remove:Return:OK", "Contact:Remove:Return:KO",
                               "Contact:Changed",
                               "PermissionPromptHelper:AskPermission:OK",
                               "Contacts:GetAll:Next", "Contacts:Revision",
                               "Contacts:Count"]);
-
-    let lock = gSettingsService.createLock();
-    lock.get("dom.mozContacts.debugging.enabled", {
-      handle: function(aName, aResult) {
-        updateDebug(aResult);
-      },
-      handleError: function(aErrorMessage) {
-        if (DEBUG) debug("Error reading dom.mozContacts.debugging.enabled setting: " + aErrorMessage);
-      }
-    });
   },
 
   // Called from DOMRequestIpcHelper
   uninit: function uninit() {
     if (DEBUG) debug("uninit call");
     if (this._oncontactchange)
       this._oncontactchange = null;
   },
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ['ContactDB'];
 
-let DEBUG = false;
+const DEBUG = false;
 function debug(s) { dump("-*- ContactDB component: " + s + "\n"); }
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
@@ -331,17 +331,19 @@ ContactDB.prototype = {
       } else if (currVersion == 10) {
         if (DEBUG) debug("Adding object store for database revision");
         db.createObjectStore(REVISION_STORE).put(0, REVISION_KEY);
       } else if (currVersion == 11) {
         if (DEBUG) debug("Add a telMatch index with national and international numbers");
         if (!objectStore) {
           objectStore = aTransaction.objectStore(STORE_NAME);
         }
-        objectStore.createIndex("telMatch", "search.parsedTel", {multiEntry: true});
+        if (!objectStore.indexNames.contains("telMatch")) {
+          objectStore.createIndex("telMatch", "search.parsedTel", {multiEntry: true});
+        }
         objectStore.openCursor().onsuccess = function(event) {
           let cursor = event.target.result;
           if (cursor) {
             if (cursor.value.properties.tel) {
               cursor.value.search.parsedTel = [];
               cursor.value.properties.tel.forEach(
                 function(tel) {
                   let parsed = PhoneNumberUtils.parse(tel.value.toString());
@@ -945,16 +947,12 @@ ContactDB.prototype = {
     }.bind(this);
   },
 
   // Enable special phone number substring matching. Does not update existing DB entries.
   enableSubstringMatching: function enableSubstringMatching(aDigits) {
     this.substringMatching = aDigits;
   },
 
-  enableDebugging: function(aEnable) {
-    DEBUG = aEnable;
-  },
-
   init: function init(aGlobal) {
     this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME, SAVED_GETALL_STORE_NAME, REVISION_STORE], aGlobal);
   }
 };
--- a/dom/contacts/fallback/ContactService.jsm
+++ b/dom/contacts/fallback/ContactService.jsm
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-let DEBUG = false;
+const DEBUG = false;
 function debug(s) { dump("-*- Fallback ContactService component: " + s + "\n"); }
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 this.EXPORTED_SYMBOLS = [];
 
@@ -17,20 +17,16 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/ContactDB.jsm");
 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageListenerManager");
 
-XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
-                                   "@mozilla.org/settingsService;1",
-                                   "nsISettingsService");
-
 let myGlobal = this;
 
 let ContactService = {
   init: function() {
     if (DEBUG) debug("Init");
     this._messages = ["Contacts:Find", "Contacts:GetAll", "Contacts:GetAll:SendNow",
                       "Contacts:Clear", "Contact:Save",
                       "Contact:Remove", "Contacts:RegisterForMessages",
@@ -51,65 +47,44 @@ let ContactService = {
     if (Services.prefs.getPrefType("dom.phonenumber.substringmatching." + countryName) == Ci.nsIPrefBranch.PREF_INT) {
       if (DEBUG) debug("Enable Substring Matching for Phone Numbers: " + countryName);
       let val = Services.prefs.getIntPref("dom.phonenumber.substringmatching." + countryName);
       if (val && val > 0) {
         this._db.enableSubstringMatching(val);
       }
     }
 
-    let lock = gSettingsService.createLock();
-    lock.get("dom.mozContacts.debugging.enabled", {
-      handle: function(aName, aResult) {
-        updateDebug(aResult);
-      },
-      handleError: function(aErrorMessage) {
-        if (DEBUG) debug("Error reading dom.mozContacts.debugging.enabled setting: " + aErrorMessage);
-      }
-    });
-
     Services.obs.addObserver(this, "profile-before-change", false);
-    Services.obs.addObserver(this, "mozsettings-changed", false);
     Services.prefs.addObserver("dom.phonenumber.substringmatching", this, false);
   },
 
-  enableDebugging: function(aResult) {
-    this._db.enableDebugging(aResult);
-  },
-
   observe: function(aSubject, aTopic, aData) {
-    if (aTopic === "profile-before-change") {
+    if (aTopic === 'profile-before-change') {
       myGlobal = null;
       this._messages.forEach(function(msgName) {
         ppmm.removeMessageListener(msgName, this);
       }.bind(this));
       Services.obs.removeObserver(this, "profile-before-change");
-      Services.obs.removeObserver(this, "mozsettings-changed");
       Services.prefs.removeObserver("dom.phonenumber.substringmatching", this);
       ppmm = null;
       this._messages = null;
       if (this._db)
         this._db.close();
       this._db = null;
       this._children = null;
       this._cursors = null;
-    } else if (aTopic === "nsPref:changed" && aData.contains("dom.phonenumber.substringmatching")) {
+    } else if (aTopic === 'nsPref:changed' && aData.contains("dom.phonenumber.substringmatching")) {
       // We don't fully support changing substringMatching during runtime. This is mostly for testing.
       let countryName = PhoneNumberUtils.getCountryName();
       if (Services.prefs.getPrefType("dom.phonenumber.substringmatching." + countryName) == Ci.nsIPrefBranch.PREF_INT) {
         let val = Services.prefs.getIntPref("dom.phonenumber.substringmatching." + countryName);
         if (val && val > 0) {
           this._db.enableSubstringMatching(val);
         }
       }
-    } else if (aTopic === "mozsettings-changed") {
-      let data = JSON.parse(aData);
-      if (data.key === "dom.mozContacts.debugging.enabled") {
-        updateDebug(data.value);
-      }
     }
   },
 
   assertPermission: function(aMessage, aPerm) {
     if (!aMessage.target.assertPermission(aPerm)) {
       Cu.reportError("Contacts message " + aMessage.name +
                      " from a content process with no" + aPerm + " privileges.");
       return false;
@@ -271,13 +246,8 @@ let ContactService = {
         break;
       default:
         if (DEBUG) debug("WRONG MESSAGE NAME: " + aMessage.name);
     }
   }
 }
 
 ContactService.init();
-
-function updateDebug(aResult) {
-  DEBUG = !!aResult;
-  ContactService.enableDebugging(DEBUG);
-}
--- a/dom/mobilemessage/interfaces/nsIRilMobileMessageDatabaseService.idl
+++ b/dom/mobilemessage/interfaces/nsIRilMobileMessageDatabaseService.idl
@@ -31,32 +31,35 @@ interface nsIRilMobileMessageDatabaseSer
   /**
    * |aMessage| Object: should contain the following properties for internal use:
    *   - |type| DOMString: "sms" or "mms"
    *   - |sender| DOMString: the phone number of sender
    *   - |timestamp| Number: the timestamp of received message
    *
    *   - If |type| == "sms", we also need:
    *     - |messageClass| DOMString: the message class of received message
+   *     - |receiver| DOMString: the phone number of receiver
    *
    *   - If |type| == "mms", we also need:
    *     - |delivery| DOMString: the delivery state of received message
    *     - |deliveryStatus| DOMString Array: the delivery status of received message
    *     - |receivers| DOMString Array: the phone numbers of receivers
+   *     - |msisdn| DOMString: [optional] my own phone number.
    *     - |transactionId| DOMString: the transaction ID from MMS pdu header.
    *
    * Note: |deliveryStatus| should only contain single string to specify
    *       the delivery status of MMS message for the phone owner self.
    */
   long saveReceivedMessage(in jsval aMessage,
                 [optional] in nsIRilMobileMessageDatabaseCallback aCallback);
 
   /**
    * |aMessage| Object: should contain the following properties for internal use:
    *   - |type| DOMString: "sms" or "mms"
+   *   - |sender| DOMString: the phone number of sender
    *   - |timestamp| Number: the timestamp of sending message
    *   - |deliveryStatusRequested| Bool: true when the delivery report is requested.
    *
    *   - If |type| == "sms", we also need:
    *     - |receiver| DOMString: the phone number of receiver
    *
    *   - If |type| == "mms", we also need:
    *     - |receivers| DOMString Array: the phone numbers of receivers
--- a/dom/mobilemessage/src/ril/MmsService.js
+++ b/dom/mobilemessage/src/ril/MmsService.js
@@ -100,19 +100,21 @@ const PREF_RETRIEVAL_RETRY_INTERVALS = (
 XPCOMUtils.defineLazyServiceGetter(this, "gpps",
                                    "@mozilla.org/network/protocol-proxy-service;1",
                                    "nsIProtocolProxyService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
-XPCOMUtils.defineLazyServiceGetter(this, "gRIL",
-                                   "@mozilla.org/ril;1",
-                                   "nsIRadioInterfaceLayer");
+XPCOMUtils.defineLazyGetter(this, "gRadioInterface", function () {
+  let ril = Cc["@mozilla.org/ril;1"].getService(Ci["nsIRadioInterfaceLayer"]);
+  // TODO: Bug 854326 - B2G Multi-SIM: support multiple SIM cards for SMS/MMS
+  return ril.getRadioInterface(0);
+});
 
 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageDatabaseService",
                                    "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1",
                                    "nsIRilMobileMessageDatabaseService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
                                    "@mozilla.org/mobilemessage/mobilemessageservice;1",
                                    "nsIMobileMessageService");
@@ -171,17 +173,17 @@ XPCOMUtils.defineLazyGetter(this, "gMmsC
     },
 
     /**
      * Callback when |disconnectTimer| is timeout or cancelled by shutdown.
      */
     onDisconnectTimerTimeout: function onDisconnectTimerTimeout() {
       if (DEBUG) debug("onDisconnectTimerTimeout: deactivate the MMS data call.");
       if (this.connected) {
-        gRIL.deactivateDataCallByType("mms");
+        gRadioInterface.deactivateDataCallByType("mms");
       }
     },
 
     init: function init() {
       Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic,
                                false);
       Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
       this.settings.forEach(function(name) {
@@ -202,27 +204,27 @@ XPCOMUtils.defineLazyGetter(this, "gMmsC
 
       try {
         this.radioDisabled = Services.prefs.getBoolPref("ril.radio.disabled");
       } catch (e) {
         if (DEBUG) debug("Getting preference 'ril.radio.disabled' fails.");
         this.radioDisabled = false;
       }
 
-      this.connected = gRIL.getDataCallStateByType("mms") ==
+      this.connected = gRadioInterface.getDataCallStateByType("mms") ==
         Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
     },
 
     /**
      * Return the roaming status of voice call.
      *
      * @return true if voice call is roaming.
      */
     isVoiceRoaming: function isVoiceRoaming() {
-      let isRoaming = gRIL.rilContext.voice.roaming;
+      let isRoaming = gRadioInterface.rilContext.voice.roaming;
       if (DEBUG) debug("isVoiceRoaming = " + isRoaming);
       return isRoaming;
     },
 
     /**
      * Acquire the MMS network connection.
      *
      * @param callback
@@ -236,17 +238,17 @@ XPCOMUtils.defineLazyGetter(this, "gMmsC
     acquire: function acquire(callback) {
       this.connectTimer.cancel();
 
       // If the MMS network is not yet connected, buffer the
       // MMS request and try to setup the MMS network first.
       if (!this.connected) {
         if (DEBUG) debug("acquire: buffer the MMS request and setup the MMS data call.");
         this.pendingCallbacks.push(callback);
-        gRIL.setupDataCallByType("mms");
+        gRadioInterface.setupDataCallByType("mms");
 
         // Set a timer to clear the buffered MMS requests if the
         // MMS network fails to be connected within a time period.
         this.connectTimer.
           initWithCallback(this.onConnectTimerTimeout.bind(this),
                            TIME_TO_BUFFER_MMS_REQUESTS,
                            Ci.nsITimer.TYPE_ONE_SHOT);
         return false;
@@ -313,17 +315,17 @@ XPCOMUtils.defineLazyGetter(this, "gMmsC
     },
 
     // nsIObserver
 
     observe: function observe(subject, topic, data) {
       switch (topic) {
         case kNetworkInterfaceStateChangedTopic: {
           this.connected =
-            gRIL.getDataCallStateByType("mms") ==
+            gRadioInterface.getDataCallStateByType("mms") ==
               Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
 
           if (!this.connected) {
             return;
           }
 
           if (DEBUG) debug("Got the MMS network connected! Resend the buffered " +
                            "MMS requests: number: " + this.pendingCallbacks.length);
@@ -1171,16 +1173,28 @@ MmsService.prototype = {
         || (config == CONFIG_SEND_REPORT_DEFAULT_YES)) {
       if (wish != null) {
         config += (wish ? 1 : -1);
       }
     }
     return config >= CONFIG_SEND_REPORT_DEFAULT_YES;
   },
 
+  getMsisdn: function getMsisdn() {
+    let iccInfo = gRadioInterface.rilContext.iccInfo;
+    let number = iccInfo ? iccInfo.msisdn : null;
+
+    // Workaround an xpconnect issue with undefined string objects.
+    // See bug 808220
+    if (number === undefined || number === "undefined") {
+      return null;
+    }
+    return number;
+  },
+
   /**
    * Convert intermediate message to indexedDB savable object.
    *
    * @param retrievalMode
    *        Retrieval mode for MMS receiving setting.
    * @param intermediate
    *        Intermediate MMS message parsed from PDU.
    */
@@ -1212,16 +1226,17 @@ MmsService.prototype = {
     intermediate.sender = null;
     intermediate.transactionId = intermediate.headers["x-mms-transaction-id"];
     if (intermediate.headers.from) {
       intermediate.sender = intermediate.headers.from.address;
     } else {
       intermediate.sender = "anonymous";
     }
     intermediate.receivers = [];
+    intermediate.msisdn = this.getMsisdn();
     return intermediate;
   },
 
   /**
    * Merge the retrieval confirmation into the savable message.
    *
    * @param intermediate
    *        Intermediate MMS message parsed from PDU, which carries
@@ -1621,16 +1636,17 @@ MmsService.prototype = {
       }
     }
 
     // The following attributes are needed for saving message into DB.
     message["type"] = "mms";
     message["deliveryStatusRequested"] = true;
     message["timestamp"] = Date.now();
     message["receivers"] = receivers;
+    message["sender"] = this.getMsisdn();
 
     if (DEBUG) debug("createSavableFromParams: message: " + JSON.stringify(message));
     return message;
   },
 
   // nsIMmsService
 
   send: function send(aParams, aRequest) {
@@ -1694,17 +1710,17 @@ MmsService.prototype = {
       if (gMmsConnection.radioDisabled) {
         if (DEBUG) debug("Error! Radio is disabled when sending MMS.");
         sendTransactionCb(aDomMessage,
                           Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR);
         return;
       }
 
       // For SIM card is not ready.
-      if (gRIL.rilContext.cardState != "ready") {
+      if (gRadioInterface.rilContext.cardState != "ready") {
         if (DEBUG) debug("Error! SIM card is not ready when sending MMS.");
         sendTransactionCb(aDomMessage,
                           Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR);
         return;
       }
 
       let sendTransaction;
       try {
--- a/dom/mobilemessage/src/ril/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/ril/MobileMessageDatabaseService.js
@@ -820,43 +820,46 @@ MobileMessageDatabaseService.prototype =
       debug("findParticipantRecordByAddress("
             + JSON.stringify(aAddress) + ", " + aCreate + ")");
     }
 
     // Two types of input number to match here, international(+886987654321),
     // and local(0987654321) types. The "nationalNumber" parsed from
     // phonenumberutils will be "987654321" in this case.
 
-    let request = aParticipantStore.index("addresses").get(aAddress);
+    // Normalize address before searching for participant record.
+    let normalizedAddress = PhoneNumberUtils.normalize(aAddress, false);
+
+    let request = aParticipantStore.index("addresses").get(normalizedAddress);
     request.onsuccess = (function (event) {
       let participantRecord = event.target.result;
       // 1) First try matching through "addresses" index of participant store.
       //    If we're lucky, return the fetched participant record.
       if (participantRecord) {
         if (DEBUG) {
           debug("findParticipantRecordByAddress: got "
                 + JSON.stringify(participantRecord));
         }
         aCallback(participantRecord);
         return;
       }
 
-      // Only parse aAddress if it's already an international number.
-      let parsedAddress = PhoneNumberUtils.parseWithMCC(aAddress, null);
+      // Only parse normalizedAddress if it's already an international number.
+      let parsedAddress = PhoneNumberUtils.parseWithMCC(normalizedAddress, null);
       // 2) Traverse throught all participants and check all alias addresses.
       aParticipantStore.openCursor().onsuccess = (function (event) {
         let cursor = event.target.result;
         if (!cursor) {
           // Have traversed whole object store but still in vain.
           if (!aCreate) {
             aCallback(null);
             return;
           }
 
-          let participantRecord = { addresses: [aAddress] };
+          let participantRecord = { addresses: [normalizedAddress] };
           let addRequest = aParticipantStore.add(participantRecord);
           addRequest.onsuccess = function (event) {
             participantRecord.id = event.target.result;
             if (DEBUG) {
               debug("findParticipantRecordByAddress: created "
                     + JSON.stringify(participantRecord));
             }
             aCallback(participantRecord);
@@ -877,30 +880,30 @@ MobileMessageDatabaseService.prototype =
             }
           } else {
             // 2-2) Else if the stored number is an international one, then the
             //      input number must be local type.  Then just check whether
             //      does it ends with the national number of the stored number.
             let parsedStoredAddress =
               PhoneNumberUtils.parseWithMCC(storedAddress, null);
             if (parsedStoredAddress
-                && aAddress.endsWith(parsedStoredAddress.nationalNumber)) {
+                && normalizedAddress.endsWith(parsedStoredAddress.nationalNumber)) {
               match = true;
             }
           }
           if (!match) {
             // 3) Else we fail to match current stored participant record.
             continue;
           }
 
           // Match!
           if (aCreate) {
             // In a READ-WRITE transaction, append one more possible address for
             // this participant record.
-            participantRecord.addresses.push(aAddress);
+            participantRecord.addresses.push(normalizedAddress);
             cursor.update(participantRecord);
           }
           if (DEBUG) {
             debug("findParticipantRecordByAddress: got "
                   + JSON.stringify(cursor.value));
           }
           aCallback(participantRecord);
           return;
@@ -1098,28 +1101,16 @@ MobileMessageDatabaseService.prototype =
           insertMessageRecord(threadId);
         };
       });
     }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
     // We return the key that we expect to store in the db
     return aMessageRecord.id;
   },
 
-  getRilIccInfoMsisdn: function getRilIccInfoMsisdn() {
-    let iccInfo = this.mRIL.rilContext.iccInfo;
-    let number = iccInfo ? iccInfo.msisdn : null;
-
-    // Workaround an xpconnect issue with undefined string objects.
-    // See bug 808220
-    if (number === undefined || number === "undefined") {
-      return null;
-    }
-    return number;
-  },
-
   /**
    * nsIRilMobileMessageDatabaseService API
    */
 
   saveReceivedMessage: function saveReceivedMessage(aMessage, aCallback) {
     if ((aMessage.type != "sms" && aMessage.type != "mms") ||
         (aMessage.type == "sms" && aMessage.messageClass == undefined) ||
         (aMessage.type == "mms" && (aMessage.delivery == undefined ||
@@ -1128,23 +1119,18 @@ MobileMessageDatabaseService.prototype =
                                     !Array.isArray(aMessage.receivers))) ||
         aMessage.sender == undefined ||
         aMessage.timestamp == undefined) {
       if (aCallback) {
         aCallback.notify(Cr.NS_ERROR_FAILURE, null);
       }
       return;
     }
-    let self = this.getRilIccInfoMsisdn();
     let threadParticipants = [aMessage.sender];
-    if (aMessage.type == "sms") {
-      // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's own
-      // phone number), thus setting the SMS' receiver to be null.
-      aMessage.receiver = self;
-    } else if (aMessage.type == "mms" && !DISABLE_MMS_GROUPING_FOR_RECEIVING) {
+    if (aMessage.type == "mms" && !DISABLE_MMS_GROUPING_FOR_RECEIVING) {
       let receivers = aMessage.receivers;
       // If we don't want to disable the MMS grouping for receiving, we need to
       // add the receivers (excluding the user's own number) to the participants
       // for creating the thread. Some cases might be investigated as below:
       //
       // 1. receivers.length == 0
       //    This usually happens when receiving an MMS notification indication
       //    which doesn't carry any receivers.
@@ -1152,18 +1138,18 @@ MobileMessageDatabaseService.prototype =
       //    If the receivers contain single phone number, we don't need to
       //    add it into participants because we know that number is our own.
       // 3. receivers.length >= 2
       //    If the receivers contain multiple phone numbers, we need to add all
       //    of them but not the user's own number into participants.
       if (receivers.length >= 2) {
         let isSuccess = false;
         let slicedReceivers = receivers.slice();
-        if (self) {
-          let found = slicedReceivers.indexOf(self);
+        if (aMessage.msisdn) {
+          let found = slicedReceivers.indexOf(aMessage.msisdn);
           if (found !== -1) {
             isSuccess = true;
             slicedReceivers.splice(found, 1);
           }
         }
 
         if (!isSuccess) {