Merge mc->Maple
authorBenoit Girard <b56girard@gmail.com>
Thu, 08 Mar 2012 10:22:42 -0500
changeset 92621 217568c1b04763eee6cf086d30a8a3a2c9b15cc6
parent 92620 4012267a4f08f2b689da1bb6e1ee426c4fb3805e (current diff)
parent 91395 8219e651919031b7eec9aa82c5c91b5d87db234a (diff)
child 92622 c6dcca94622e4b66c69ef48a03c256b5fde85295
push idunknown
push userunknown
push dateunknown
milestone13.0a1
Merge mc->Maple
browser/app/profile/firefox.js
browser/base/content/aboutHome-snippet1.png
browser/base/content/aboutHome-snippet2.png
browser/base/content/aboutHome.css
browser/base/content/aboutHome.js
browser/base/content/aboutHome.xhtml
browser/devtools/debugger/debugger-view.js
browser/devtools/debugger/debugger.js
browser/devtools/debugger/test/browser_dbg_clean-exit.js
browser/devtools/debugger/test/browser_dbg_propertyview-01.js
browser/devtools/debugger/test/browser_dbg_propertyview-07.js
browser/devtools/debugger/test/browser_dbg_propertyview-08.js
browser/devtools/debugger/test/browser_dbg_script-switching.js
browser/devtools/debugger/test/browser_dbg_select-line.js
browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
browser/devtools/debugger/test/head.js
browser/devtools/styleinspector/CssLogic.jsm
browser/devtools/styleinspector/CssRuleView.jsm
browser/devtools/styleinspector/test/Makefile.in
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
browser/themes/gnomestripe/devtools/csshtmltree.css
browser/themes/pinstripe/devtools/csshtmltree.css
browser/themes/winstripe/devtools/csshtmltree.css
configure.in
dom/base/Navigator.cpp
embedding/android/GeckoAppShell.java
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jsexn.cpp
js/xpconnect/src/XPCJSRuntime.cpp
layout/base/nsBidiPresUtils.cpp
layout/forms/nsComboboxControlFrame.cpp
layout/generic/nsContainerFrame.cpp
layout/generic/nsFrame.h
layout/generic/nsObjectFrame.cpp
layout/generic/nsTextFrameThebes.cpp
layout/tables/nsTableFrame.cpp
mobile/android/app/mobile.js
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/chrome/content/browser.js
toolkit/xre/nsWindowsDllBlocklist.cpp
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
xpcom/ds/nsDoubleHashtable.h
--- a/accessible/public/nsIAccessibleEditableText.idl
+++ b/accessible/public/nsIAccessibleEditableText.idl
@@ -38,17 +38,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIEditor;
 
-[scriptable, uuid(52837507-202d-4e72-a482-5f068a1fd720)]
+[scriptable, uuid(e242d495-5cde-4b1c-8c84-2525b14939f5)]
 interface nsIAccessibleEditableText : nsISupports
 {
   /**
    * Sets the attributes for the text between the two given indices. The old
    * attributes are replaced by the new list of attributes. For example,
    * sets font styles, such as italic, bold...
    *
    * @param startPos - start index of the text whose attributes are modified.
@@ -98,24 +98,9 @@ interface nsIAccessibleEditableText : ns
 
   /**
    * Pastes text from the clipboard.
    *
    * @param position - index at which to insert the text from the system
    *                   clipboard into the text represented by this object.
    */
   void pasteText (in long position);
-
-  /**
-   * Returns an editor associated with the accessible.
-   */
-  [noscript] readonly attribute nsIEditor associatedEditor;
 };
-
-/*
- Assumptions:
-
- selectAttributes method takes an nsISupports parameter.
-        'set' methods throw exception on failure.
- 'wstring' inputs are potentially multibyte (UTF-16 for
-        instance); 'string' and UTF-8 may be a better choice.
-
-*/
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -675,18 +675,17 @@ NotificationController::CreateTextChange
     return;
 
   nsHyperTextAccessible* textAccessible = container->AsHyperText();
   if (!textAccessible)
     return;
 
   // Don't fire event for the first html:br in an editor.
   if (aEvent->mAccessible->Role() == roles::WHITESPACE) {
-    nsCOMPtr<nsIEditor> editor;
-    textAccessible->GetAssociatedEditor(getter_AddRefs(editor));
+    nsCOMPtr<nsIEditor> editor = textAccessible->GetEditor();
     if (editor) {
       bool isEmpty = false;
       editor->GetDocumentIsEmpty(&isEmpty);
       if (isEmpty)
         return;
     }
   }
 
--- a/accessible/src/base/nsAccessible.h
+++ b/accessible/src/base/nsAccessible.h
@@ -210,16 +210,21 @@ public:
 
   /**
    * Return the states of accessible, not taking into account ARIA states.
    * Use State() to get complete set of states.
    */
   virtual PRUint64 NativeState();
 
   /**
+   * Return bit set of invisible and offscreen states.
+   */
+  PRUint64 VisibilityState();
+
+  /**
    * Returns attributes for accessible without explicitly setted ARIA
    * attributes.
    */
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
 
   /**
    * Used by ChildAtPoint() method to get direct or deepest child at point.
    */
@@ -697,18 +702,16 @@ protected:
   /**
    * Return ARIA role (helper method).
    */
   mozilla::a11y::role ARIARoleInternal();
 
   virtual nsIFrame* GetBoundsFrame();
   virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame);
 
-  PRUint64 VisibilityState(); 
-
   //////////////////////////////////////////////////////////////////////////////
   // Name helpers
 
   /**
    * Compute the name of HTML node.
    */
   nsresult GetHTMLName(nsAString& aName);
 
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -328,18 +328,17 @@ nsDocAccessible::NativeState()
     state |= states::BUSY;
 
   nsIFrame* frame = GetFrame();
   if (!frame ||
       !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
     state |= states::INVISIBLE | states::OFFSCREEN;
   }
 
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  nsCOMPtr<nsIEditor> editor = GetEditor();
   state |= editor ? states::EDITABLE : states::READONLY;
 
   return state;
 }
 
 // nsAccessible public method
 void
 nsDocAccessible::ApplyARIAState(PRUint64* aState)
@@ -548,47 +547,42 @@ nsDocAccessible::GetVirtualCursor(nsIAcc
     mVirtualCursor = new nsAccessiblePivot(this);
     mVirtualCursor->AddObserver(this);
   }
 
   NS_ADDREF(*aVirtualCursor = mVirtualCursor);
   return NS_OK;
 }
 
-// nsIAccessibleHyperText method
-NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor)
+// nsHyperTextAccessible method
+already_AddRefed<nsIEditor>
+nsDocAccessible::GetEditor() const
 {
-  NS_ENSURE_ARG_POINTER(aEditor);
-  *aEditor = nsnull;
-
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
-
   // Check if document is editable (designMode="on" case). Otherwise check if
   // the html:body (for HTML document case) or document element is editable.
   if (!mDocument->HasFlag(NODE_IS_EDITABLE) &&
       !mContent->HasFlag(NODE_IS_EDITABLE))
-    return NS_OK;
+    return nsnull;
 
   nsCOMPtr<nsISupports> container = mDocument->GetContainer();
   nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(container));
   if (!editingSession)
-    return NS_OK; // No editing session interface
+    return nsnull; // No editing session interface
 
   nsCOMPtr<nsIEditor> editor;
   editingSession->GetEditorForWindow(mDocument->GetWindow(), getter_AddRefs(editor));
-  if (!editor) {
-    return NS_OK;
-  }
-  bool isEditable;
+  if (!editor)
+    return nsnull;
+
+  bool isEditable = false;
   editor->GetIsDocumentEditable(&isEditable);
-  if (isEditable) {
-    NS_ADDREF(*aEditor = editor);
-  }
-  return NS_OK;
+  if (isEditable)
+    return editor.forget();
+
+  return nsnull;
 }
 
 // nsDocAccessible public method
 nsAccessible*
 nsDocAccessible::GetAccessible(nsINode* aNode) const
 {
   nsAccessible* accessible = mNodeToAccessibleMap.Get(aNode);
 
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -128,18 +128,18 @@ public:
   virtual void ApplyARIAState(PRUint64* aState);
 
   virtual void SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry);
 
 #ifdef DEBUG_ACCDOCMGR
   virtual nsresult HandleAccEvent(AccEvent* aAccEvent);
 #endif
 
-  // nsIAccessibleText
-  NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor);
+  // nsHyperTextAccessible
+  virtual already_AddRefed<nsIEditor> GetEditor() const;
 
   // nsDocAccessible
 
   /**
    * Return presentation shell for this document accessible.
    */
   nsIPresShell* PresShell() const { return mPresShell; }
 
--- a/accessible/src/html/nsHTMLFormControlAccessible.cpp
+++ b/accessible/src/html/nsHTMLFormControlAccessible.cpp
@@ -539,39 +539,40 @@ NS_IMETHODIMP nsHTMLTextFieldAccessible:
     if ( element ) {
       return element->Focus();
     }
     return NS_ERROR_FAILURE;
   }
   return NS_ERROR_INVALID_ARG;
 }
 
-NS_IMETHODIMP nsHTMLTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor)
+already_AddRefed<nsIEditor>
+nsHTMLTextFieldAccessible::GetEditor() const
 {
-  *aEditor = nsnull;
   nsCOMPtr<nsIDOMNSEditableElement> editableElt(do_QueryInterface(mContent));
-  NS_ENSURE_TRUE(editableElt, NS_ERROR_FAILURE);
+  if (!editableElt)
+    return nsnull;
 
   // nsGenericHTMLElement::GetEditor has a security check.
   // Make sure we're not restricted by the permissions of
   // whatever script is currently running.
   nsCOMPtr<nsIJSContextStack> stack =
     do_GetService("@mozilla.org/js/xpc/ContextStack;1");
   bool pushed = stack && NS_SUCCEEDED(stack->Push(nsnull));
 
   nsCOMPtr<nsIEditor> editor;
-  nsresult rv = editableElt->GetEditor(aEditor);
+  editableElt->GetEditor(getter_AddRefs(editor));
 
   if (pushed) {
     JSContext* cx;
     stack->Pop(&cx);
     NS_ASSERTION(!cx, "context should be null");
   }
 
-  return rv;
+  return editor.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHTMLTextFieldAccessible: Widgets
 
 bool
 nsHTMLTextFieldAccessible::IsWidget() const
 {
--- a/accessible/src/html/nsHTMLFormControlAccessible.h
+++ b/accessible/src/html/nsHTMLFormControlAccessible.h
@@ -133,18 +133,18 @@ public:
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessible
   NS_IMETHOD GetValue(nsAString& _retval); 
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 index);
 
-  // nsIAccessibleEditableText
-  NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor);
+  // nsHyperTextAccessible
+  virtual already_AddRefed<nsIEditor> GetEditor() const;
 
   // nsAccessible
   virtual void ApplyARIAState(PRUint64* aState);
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 State();
   virtual PRUint64 NativeState();
 
--- a/accessible/src/html/nsHyperTextAccessible.cpp
+++ b/accessible/src/html/nsHyperTextAccessible.cpp
@@ -161,18 +161,17 @@ nsHyperTextAccessible::NativeRole()
   return roles::TEXT_CONTAINER; // In ATK this works
 }
 
 PRUint64
 nsHyperTextAccessible::NativeState()
 {
   PRUint64 states = nsAccessibleWrap::NativeState();
 
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  nsCOMPtr<nsIEditor> editor = GetEditor();
   if (editor) {
     PRUint32 flags;
     editor->GetFlags(&flags);
     if (0 == (flags & nsIPlaintextEditor::eEditorReadonlyMask)) {
       states |= states::EDITABLE;
     }
   } else if (mContent->Tag() == nsGkAtoms::article) {
     // We want <article> to behave like a document in terms of readonly state.
@@ -706,18 +705,17 @@ nsHyperTextAccessible::HypertextOffsetsT
   *aEndNode = nsnull;
 
   NS_ENSURE_ARG_POINTER(aEndOffset);
   *aEndOffset = -1;
 
   // If the given offsets are 0 and associated editor is empty then return
   // collapsed range with editor root element as range container.
   if (aStartHTOffset == 0 && aEndHTOffset == 0) {
-    nsCOMPtr<nsIEditor> editor;
-    GetAssociatedEditor(getter_AddRefs(editor));
+    nsCOMPtr<nsIEditor> editor = GetEditor();
     if (editor) {
       bool isEmpty = false;
       editor->GetDocumentIsEmpty(&isEmpty);
       if (isEmpty) {
         nsCOMPtr<nsIDOMElement> editorRootElm;
         editor->GetRootElement(getter_AddRefs(editorRootElm));
 
         nsCOMPtr<nsIDOMNode> editorRoot(do_QueryInterface(editorRootElm));
@@ -1450,118 +1448,121 @@ NS_IMETHODIMP nsHyperTextAccessible::Set
     return InsertText(aText, 0);
   }
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsHyperTextAccessible::InsertText(const nsAString &aText, PRInt32 aPosition)
 {
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  if (IsDefunct())
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIEditor> editor = GetEditor();
 
   nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
   NS_ENSURE_STATE(peditor);
 
   nsresult rv = SetSelectionRange(aPosition, aPosition);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return peditor->InsertText(aText);
 }
 
 NS_IMETHODIMP
 nsHyperTextAccessible::CopyText(PRInt32 aStartPos, PRInt32 aEndPos)
 {
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  if (IsDefunct())
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIEditor> editor = GetEditor();
   NS_ENSURE_STATE(editor);
 
   nsresult rv = SetSelectionRange(aStartPos, aEndPos);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return editor->Copy();
 }
 
 NS_IMETHODIMP
 nsHyperTextAccessible::CutText(PRInt32 aStartPos, PRInt32 aEndPos)
 {
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  if (IsDefunct())
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIEditor> editor = GetEditor();
   NS_ENSURE_STATE(editor);
 
   nsresult rv = SetSelectionRange(aStartPos, aEndPos);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return editor->Cut();
 }
 
 NS_IMETHODIMP
 nsHyperTextAccessible::DeleteText(PRInt32 aStartPos, PRInt32 aEndPos)
 {
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  if (IsDefunct())
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIEditor> editor = GetEditor();
   NS_ENSURE_STATE(editor);
 
   nsresult rv = SetSelectionRange(aStartPos, aEndPos);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return editor->DeleteSelection(nsIEditor::eNone);
 }
 
 NS_IMETHODIMP
 nsHyperTextAccessible::PasteText(PRInt32 aPosition)
 {
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  if (IsDefunct())
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIEditor> editor = GetEditor();
   NS_ENSURE_STATE(editor);
 
   nsresult rv = SetSelectionRange(aPosition, aPosition);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return editor->Paste(nsIClipboard::kGlobalClipboard);
 }
 
-NS_IMETHODIMP
-nsHyperTextAccessible::GetAssociatedEditor(nsIEditor **aEditor)
+already_AddRefed<nsIEditor>
+nsHyperTextAccessible::GetEditor() const
 {
-  NS_ENSURE_ARG_POINTER(aEditor);
-  *aEditor = nsnull;
-
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
-
   if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
     // If we're inside an editable container, then return that container's editor
-    nsCOMPtr<nsIAccessible> ancestor, current = this;
-    while (NS_SUCCEEDED(current->GetParent(getter_AddRefs(ancestor))) && ancestor) {
-      nsRefPtr<nsHyperTextAccessible> ancestorTextAccessible;
-      ancestor->QueryInterface(NS_GET_IID(nsHyperTextAccessible),
-                               getter_AddRefs(ancestorTextAccessible));
-      if (ancestorTextAccessible) {
+    nsAccessible* ancestor = Parent();
+    while (ancestor) {
+      nsHyperTextAccessible* hyperText = ancestor->AsHyperText();
+      if (hyperText) {
         // Recursion will stop at container doc because it has its own impl
-        // of GetAssociatedEditor()
-        return ancestorTextAccessible->GetAssociatedEditor(aEditor);
+        // of GetEditor()
+        return hyperText->GetEditor();
       }
-      current = ancestor;
+
+      ancestor = ancestor->Parent();
     }
-    return NS_OK;
+
+    return nsnull;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
     nsCoreUtils::GetDocShellTreeItemFor(mContent);
   nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(docShellTreeItem));
   if (!editingSession)
-    return NS_OK; // No editing session interface
-
-  NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE);
-  nsIDocument* docNode = mDoc->GetDocumentNode();
-  NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE);
+    return nsnull; // No editing session interface
 
   nsCOMPtr<nsIEditor> editor;
-  return editingSession->GetEditorForWindow(docNode->GetWindow(), aEditor);
+  nsIDocument* docNode = mDoc->GetDocumentNode();
+  editingSession->GetEditorForWindow(docNode->GetWindow(),
+                                     getter_AddRefs(editor));
+  return editor.forget();
 }
 
 /**
   * =================== Caret & Selection ======================
   */
 
 nsresult
 nsHyperTextAccessible::SetSelectionRange(PRInt32 aStartPos, PRInt32 aEndPos)
@@ -1765,18 +1766,17 @@ nsHyperTextAccessible::GetSelectionDOMRa
     return;
 
   nsISelection* domSel = frameSelection->GetSelection(aType);
   if (!domSel)
     return;
 
   nsCOMPtr<nsINode> startNode = GetNode();
 
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  nsCOMPtr<nsIEditor> editor = GetEditor();
   if (editor) {
     nsCOMPtr<nsIDOMElement> editorRoot;
     editor->GetRootElement(getter_AddRefs(editorRoot));
     startNode = do_QueryInterface(editorRoot);
   }
 
   if (!startNode)
     return;
--- a/accessible/src/html/nsHyperTextAccessible.h
+++ b/accessible/src/html/nsHyperTextAccessible.h
@@ -259,16 +259,24 @@ public:
    *
    * @param  aOffset  [in] the given text offset
    */
   nsAccessible* GetChildAtOffset(PRUint32 aOffset)
   {
     return GetChildAt(GetChildIndexAtOffset(aOffset));
   }
 
+  //////////////////////////////////////////////////////////////////////////////
+  // EditableTextAccessible
+
+  /**
+   * Return the editor associated with the accessible.
+   */
+  virtual already_AddRefed<nsIEditor> GetEditor() const;
+
 protected:
   // nsHyperTextAccessible
 
   /**
    * Transform magic offset into text offset.
    */
   inline PRInt32 ConvertMagicOffset(PRInt32 aOffset)
   {
--- a/accessible/src/xforms/nsXFormsAccessible.cpp
+++ b/accessible/src/xforms/nsXFormsAccessible.cpp
@@ -268,34 +268,36 @@ nsXFormsEditableAccessible::NativeState(
     bool isRelevant = false;
     rv = sXFormsService->IsRelevant(DOMNode, &isRelevant);
     NS_ENSURE_SUCCESS(rv, state);
     if (isRelevant) {
       state |= states::EDITABLE | states::SELECTABLE_TEXT;
     }
   }
 
-  nsCOMPtr<nsIEditor> editor;
-  GetAssociatedEditor(getter_AddRefs(editor));
+  nsCOMPtr<nsIEditor> editor = GetEditor();
   NS_ENSURE_TRUE(editor, state);
   PRUint32 flags;
   editor->GetFlags(&flags);
   if (flags & nsIPlaintextEditor::eEditorSingleLineMask)
     state |= states::SINGLE_LINE;
   else
     state |= states::MULTI_LINE;
 
   return state;
 }
 
-NS_IMETHODIMP
-nsXFormsEditableAccessible::GetAssociatedEditor(nsIEditor **aEditor)
+already_AddRefed<nsIEditor>
+nsXFormsEditableAccessible::GetEditor() const
 {
   nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(mContent));
-  return sXFormsService->GetEditor(DOMNode, aEditor);
+
+  nsCOMPtr<nsIEditor> editor;
+  sXFormsService->GetEditor(DOMNode, getter_AddRefs(editor));
+  return editor.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsXFormsSelectableAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 nsXFormsSelectableAccessible::
   nsXFormsSelectableAccessible(nsIContent* aContent, nsDocAccessible* aDoc) :
--- a/accessible/src/xforms/nsXFormsAccessible.h
+++ b/accessible/src/xforms/nsXFormsAccessible.h
@@ -139,18 +139,18 @@ public:
  * The class is base for accessible objects for XForms elements that have
  * editable area.
  */
 class nsXFormsEditableAccessible : public nsXFormsAccessible
 {
 public:
   nsXFormsEditableAccessible(nsIContent* aContent, nsDocAccessible* aDoc);
 
-  // nsIAccessibleEditableText
-  NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor);
+  // nsHyperTextAccessible
+  virtual already_AddRefed<nsIEditor> GetEditor() const;
 
   // nsAccessible
   virtual PRUint64 NativeState();
 };
 
 
 /**
  * The class is base for accessible objects for XForms select and XForms
--- a/accessible/src/xul/nsXULFormControlAccessible.cpp
+++ b/accessible/src/xul/nsXULFormControlAccessible.cpp
@@ -845,24 +845,27 @@ NS_IMETHODIMP nsXULTextFieldAccessible::
 }
 
 bool
 nsXULTextFieldAccessible::CanHaveAnonChildren()
 {
   return false;
 }
 
-NS_IMETHODIMP nsXULTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor)
+already_AddRefed<nsIEditor>
+nsXULTextFieldAccessible::GetEditor() const
 {
-  *aEditor = nsnull;
-
   nsCOMPtr<nsIContent> inputField = GetInputField();
   nsCOMPtr<nsIDOMNSEditableElement> editableElt(do_QueryInterface(inputField));
-  NS_ENSURE_TRUE(editableElt, NS_ERROR_FAILURE);
-  return editableElt->GetEditor(aEditor);
+  if (!editableElt)
+    return nsnull;
+
+  nsCOMPtr<nsIEditor> editor;
+  editableElt->GetEditor(getter_AddRefs(editor));
+  return editor.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULTextFieldAccessible: nsAccessible protected
 
 void
 nsXULTextFieldAccessible::CacheChildren()
 {
--- a/accessible/src/xul/nsXULFormControlAccessible.h
+++ b/accessible/src/xul/nsXULFormControlAccessible.h
@@ -255,18 +255,18 @@ public:
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessible
   NS_IMETHOD GetValue(nsAString& aValue);
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 index);
 
-  // nsIAccessibleEditableText
-  NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor);
+  // nsHyperTextAccessible
+  virtual already_AddRefed<nsIEditor> GetEditor() const;
 
   // nsAccessible
   virtual void ApplyARIAState(PRUint64* aState);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
   virtual bool CanHaveAnonChildren();
 
   // ActionAccessible
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -608,14 +608,14 @@ function getNodePrettyName(aNode)
   } catch (e) {
     return "' no node info '";
   }
 }
 
 function getObjAddress(aObj)
 {
   var exp = /native\s*@\s*(0x[a-f0-9]+)/g;
-  var match = exp.exec(aObj.valueOf());
+  var match = exp.exec(aObj.toString());
   if (match)
     return match[1];
 
-  return aObj.valueOf();
+  return aObj.toString();
 }
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -299,22 +299,30 @@ function eventQueue(aEventType)
         SimpleTest.finish();
 
       return;
     }
 
     // Start processing of next invoker.
     invoker = this.getNextInvoker();
 
+    this.setEventHandler(invoker);
+
     if (gLogger.isEnabled()) {
       gLogger.logToConsole("Event queue: \n  invoke: " + invoker.getID());
       gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true);
     }
 
-    this.setEventHandler(invoker);
+    var infoText = "Invoke the '" + invoker.getID() + "' test { ";
+    for (var idx = 0; idx < this.mEventSeq.length; idx++) {
+      infoText += this.isEventUnexpected(idx) ? "un" : "";
+      infoText += "expected '" + this.getEventTypeAsString(idx) + "' event; ";
+    }
+    infoText += " }";
+    info(infoText);
 
     if (invoker.invoke() == INVOKER_ACTION_FAILED) {
       // Invoker failed to prepare action, fail and finish tests.
       this.processNextInvoker();
       return;
     }
 
     if (this.areAllEventsUnexpected())
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -61,17 +61,17 @@ pref("extensions.strictCompatibility", f
 // for it to be compatible by default.
 pref("extensions.minCompatibleAppVersion", "4.0");
 
 // Preferences for AMO integration
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.maxResults", 15);
 pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%");
 pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
-pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%");
+pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
 pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox");
 pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
 
 // Blocklist preferences
 pref("extensions.blocklist.enabled", true);
 pref("extensions.blocklist.interval", 86400);
 // Controls what level the blocklist switches from warning about items to forcibly
 // blocking them.
@@ -209,16 +209,17 @@ pref("app.update.service.enabled", true)
 // Symmetric (can be overridden by individual extensions) update preferences.
 // e.g.
 //  extensions.{GUID}.update.enabled
 //  extensions.{GUID}.update.url
 //  .. etc ..
 //
 pref("extensions.update.enabled", true);
 pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
+pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 pref("extensions.update.interval", 86400);  // Check for updates to Extensions and 
                                             // Themes every day
 // Non-symmetric (not shared by extensions) extension-specific [update] preferences
 pref("extensions.getMoreThemesURL", "https://addons.mozilla.org/%LOCALE%/firefox/getpersonas");
 pref("extensions.dss.enabled", false);          // Dynamic Skin Switching                                               
 pref("extensions.dss.switchPending", false);    // Non-dynamic switch pending after next
                                                 // restart.
 
@@ -1036,16 +1037,19 @@ pref("devtools.inspector.sidebarOpen", f
 pref("devtools.inspector.activeSidebar", "ruleview");
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", false);
 
 // The default Debugger UI height
 pref("devtools.debugger.ui.height", 250);
 
+// Disable remote debugging protocol logging
+pref("devtools.debugger.log", false);
+
 // Enable the style inspector
 pref("devtools.styleinspector.enabled", true);
 
 // Enable the Tilt inspector
 pref("devtools.tilt.enabled", true);
 pref("devtools.tilt.intro_transition", true);
 pref("devtools.tilt.outro_transition", true);
 
--- a/browser/base/content/newtab/dropTargetShim.js
+++ b/browser/base/content/newtab/dropTargetShim.js
@@ -35,20 +35,22 @@ let gDropTargetShim = {
     node.addEventListener("dragend", this._end.bind(this), true);
   },
 
   /**
    * Handles the 'dragstart' event.
    * @param aEvent The 'dragstart' event.
    */
   _start: function DropTargetShim_start(aEvent) {
-    gGrid.lock();
+    if (aEvent.target.classList.contains("site")) {
+      gGrid.lock();
 
-    // XXX bug 505521 - Listen for dragover on the document.
-    document.documentElement.addEventListener("dragover", this._dragover, false);
+      // XXX bug 505521 - Listen for dragover on the document.
+      document.documentElement.addEventListener("dragover", this._dragover, false);
+    }
   },
 
   /**
    * Handles the 'drag' event and determines the current drop target.
    * @param aEvent The 'drag' event.
    */
   _drag: function DropTargetShim_drag(aEvent) {
     // Let's see if we find a drop target.
--- a/browser/base/content/test/newtab/Makefile.in
+++ b/browser/base/content/test/newtab/Makefile.in
@@ -16,13 +16,14 @@ include $(topsrcdir)/config/rules.mk
 	browser_newtab_disable.js \
 	browser_newtab_drag_drop.js \
 	browser_newtab_drop_preview.js \
 	browser_newtab_private_browsing.js \
 	browser_newtab_reset.js \
 	browser_newtab_tabsync.js \
 	browser_newtab_unpin.js \
 	browser_newtab_bug723102.js \
+	browser_newtab_bug723121.js \
 	head.js \
 	$(NULL)
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug723121.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function runTests() {
+  setLinks("0,1,2,3,4,5,6,7,8");
+  setPinnedLinks("");
+
+  yield addNewTabPageTab();
+  checkGridLocked(false, "grid is unlocked");
+
+  let cell = cells[0].node;
+  let site = cells[0].site.node;
+
+  sendDragEvent(site, "dragstart");
+  checkGridLocked(true, "grid is now locked");
+
+  sendDragEvent(site, "dragend");
+  checkGridLocked(false, "grid isn't locked anymore");
+
+  sendDragEvent(cell, "dragstart");
+  checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+}
+
+function checkGridLocked(aLocked, aMessage) {
+  is(cw.gGrid.node.hasAttribute("locked"), aLocked, aMessage);
+}
+
+function sendDragEvent(aNode, aType) {
+  let ifaceReq = cw.QueryInterface(Ci.nsIInterfaceRequestor);
+  let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
+
+  let dataTransfer = {
+    mozUserCancelled: false,
+    setData: function () null,
+    setDragImage: function () null,
+    getData: function () "about:blank"
+  };
+
+  let event = cw.document.createEvent("DragEvents");
+  event.initDragEvent(aType, true, true, cw, 0, 0, 0, 0, 0,
+                      false, false, false, false, 0, null, dataTransfer);
+
+  windowUtils.dispatchDOMEventViaPresShell(aNode, event, true);
+}
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -406,28 +406,30 @@ BrowserGlue.prototype = {
 
     // If there are plugins installed that are outdated, and the user hasn't
     // been warned about them yet, open the plugins update page.
     if (Services.prefs.getBoolPref(PREF_PLUGINS_NOTIFYUSER))
       this._showPluginUpdatePage();
 
     // For any add-ons that were installed disabled and can be enabled offer
     // them to the user
-    var win = this.getMostRecentBrowserWindow();
-    var browser = win.gBrowser;
     var changedIDs = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED);
-    AddonManager.getAddonsByIDs(changedIDs, function(aAddons) {
-      aAddons.forEach(function(aAddon) {
-        // If the add-on isn't user disabled or can't be enabled then skip it
-        if (!aAddon.userDisabled || !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE))
-          return;
+    if (changedIDs.length > 0) {
+      AddonManager.getAddonsByIDs(changedIDs, function(aAddons) {
+        var win = this.getMostRecentBrowserWindow();
+        var browser = win.gBrowser;
+        aAddons.forEach(function(aAddon) {
+          // If the add-on isn't user disabled or can't be enabled then skip it.
+          if (!aAddon.userDisabled || !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE))
+            return;
 
-        browser.selectedTab = browser.addTab("about:newaddon?id=" + aAddon.id);
-      })
-    });
+          browser.selectedTab = browser.addTab("about:newaddon?id=" + aAddon.id);
+        })
+      });
+    }
 
     let keywordURLUserSet = Services.prefs.prefHasUserValue("keyword.URL");
     Services.telemetry.getHistogramById("FX_KEYWORD_URL_USERSET").add(keywordURLUserSet);
 
     // Perform default browser checking.
     var shell;
     try {
       shell = Components.classes["@mozilla.org/browser/shell-service;1"]
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -19,16 +19,17 @@
  *   Mozilla Foundation
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Dave Camp <dcamp@mozilla.com> (original author)
  *   Panos Astithas <past@mozilla.com>
  *   Victor Porof <vporof@mozilla.com>
+ *   Mihai Sucan <mihai.sucan@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -55,20 +56,48 @@ let EXPORTED_SYMBOLS = ["DebuggerUI"];
 
 /**
  * Creates a pane that will host the debugger UI.
  */
 function DebuggerPane(aTab) {
   this._tab = aTab;
   this._close = this.close.bind(this);
   this._debugTab = this.debugTab.bind(this);
+  this.breakpoints = {};
 }
 
 DebuggerPane.prototype = {
   /**
+   * Skip editor breakpoint change events.
+   *
+   * This property tells the source editor event handler to skip handling of
+   * the BREAKPOINT_CHANGE events. This is used when the debugger adds/removes
+   * breakpoints from the editor. Typically, the BREAKPOINT_CHANGE event handler
+   * adds/removes events from the debugger, but when breakpoints are added from
+   * the public debugger API, we need to do things in reverse.
+   *
+   * This implementation relies on the fact that the source editor fires the
+   * BREAKPOINT_CHANGE events synchronously.
+   *
+   * @private
+   * @type boolean
+   */
+  _skipEditorBreakpointChange: false,
+
+  /**
+   * The list of breakpoints in the debugger as tracked by the current
+   * DebuggerPane instance. This an object where the values are BreakpointActor
+   * objects received from the client, while the keys are actor names, for
+   * example "conn0.breakpoint3".
+   *
+   * @type object
+   */
+  breakpoints: null,
+
+  /**
    * Creates and initializes the widgets contained in the debugger UI.
    */
   create: function DP_create(gBrowser) {
     this._tab._scriptDebugger = this;
 
     this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser);
     this._splitter = gBrowser.parentNode.ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "hud-splitter");
@@ -82,44 +111,251 @@ DebuggerPane.prototype = {
 
     this.frame.addEventListener("DOMContentLoaded", function initPane(aEvent) {
       if (aEvent.target != self.frame.contentDocument) {
         return;
       }
       self.frame.removeEventListener("DOMContentLoaded", initPane, true);
       // Initialize the source editor.
       self.frame.contentWindow.editor = self.editor = new SourceEditor();
+      self.frame.contentWindow.updateEditorBreakpoints =
+        self._updateEditorBreakpoints.bind(self);
 
       let config = {
         mode: SourceEditor.MODES.JAVASCRIPT,
         showLineNumbers: true,
-        readOnly: true
+        readOnly: true,
+        showAnnotationRuler: true,
+        showOverviewRuler: true,
       };
 
       let editorPlaceholder = self.frame.contentDocument.getElementById("editor");
       self.editor.init(editorPlaceholder, config, self._onEditorLoad.bind(self));
     }, true);
     this.frame.addEventListener("DebuggerClose", this._close, true);
 
     this.frame.setAttribute("src", "chrome://browser/content/debugger.xul");
   },
 
   /**
    * The load event handler for the source editor. This method does post-load
    * editor initialization.
    */
   _onEditorLoad: function DP__onEditorLoad() {
+    this.editor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                                 this._onEditorBreakpointChange.bind(this));
     // Connect to the debugger server.
     this.connect();
   },
 
   /**
+   * Event handler for breakpoint changes that happen in the editor. This
+   * function syncs the breakpoint changes in the editor to those in the
+   * debugger.
+   *
+   * @private
+   * @param object aEvent
+   *        The SourceEditor.EVENTS.BREAKPOINT_CHANGE event object.
+   */
+  _onEditorBreakpointChange: function DP__onEditorBreakpointChange(aEvent) {
+    if (this._skipEditorBreakpointChange) {
+      return;
+    }
+
+    aEvent.added.forEach(this._onEditorBreakpointAdd, this);
+    aEvent.removed.forEach(this._onEditorBreakpointRemove, this);
+  },
+
+  /**
+   * Retrieve the URL of the selected script in the debugger view.
+   *
+   * @private
+   * @return string
+   *         The URL of the selected script.
+   */
+  _selectedScript: function DP__selectedScript() {
+    return this.debuggerWindow ?
+           this.debuggerWindow.DebuggerView.Scripts.selected : null;
+  },
+
+  /**
+   * Event handler for new breakpoints that come from the editor.
+   *
+   * @private
+   * @param object aBreakpoint
+   *        The breakpoint object coming from the editor.
+   */
+  _onEditorBreakpointAdd: function DP__onEditorBreakpointAdd(aBreakpoint) {
+    let location = {
+      url: this._selectedScript(),
+      line: aBreakpoint.line + 1,
+    };
+
+    if (location.url) {
+      let callback = function (aClient, aError) {
+        if (aError) {
+          this._skipEditorBreakpointChange = true;
+          let result = this.editor.removeBreakpoint(aBreakpoint.line);
+          this._skipEditorBreakpointChange = false;
+        }
+      }.bind(this);
+      this.addBreakpoint(location, callback, true);
+    }
+  },
+
+  /**
+   * Event handler for breakpoints that are removed from the editor.
+   *
+   * @private
+   * @param object aBreakpoint
+   *        The breakpoint object that was removed from the editor.
+   */
+  _onEditorBreakpointRemove: function DP__onEditorBreakpointRemove(aBreakpoint) {
+    let url = this._selectedScript();
+    let line = aBreakpoint.line + 1;
+    if (!url) {
+      return;
+    }
+
+    let breakpoint = this.getBreakpoint(url, line);
+    if (breakpoint) {
+      this.removeBreakpoint(breakpoint, null, true);
+    }
+  },
+
+  /**
+   * Update the breakpoints in the editor view. This function takes the list of
+   * breakpoints in the debugger and adds them back into the editor view. This
+   * is invoked when the selected script is changed.
+   *
+   * @private
+   */
+  _updateEditorBreakpoints: function DP__updateEditorBreakpoints()
+  {
+    let url = this._selectedScript();
+    if (!url) {
+      return;
+    }
+
+    this._skipEditorBreakpointChange = true;
+    for each (let breakpoint in this.breakpoints) {
+      if (breakpoint.location.url == url) {
+        this.editor.addBreakpoint(breakpoint.location.line - 1);
+      }
+    }
+    this._skipEditorBreakpointChange = false;
+  },
+
+  /**
+   * Add a breakpoint.
+   *
+   * @param object aLocation
+   *        The location where you want the breakpoint. This object must have
+   *        two properties:
+   *          - url - the URL of the script.
+   *          - line - the line number (starting from 1).
+   * @param function [aCallback]
+   *        Optional function to invoke once the breakpoint is added. The
+   *        callback is invoked with two arguments:
+   *          - aBreakpointClient - the BreakpointActor client object, if the
+   *          breakpoint has been added successfully.
+   *          - aResponseError - if there was any error.
+   * @param boolean [aNoEditorUpdate=false]
+   *        Tells if you want to skip editor updates. Typically the editor is
+   *        updated to visually indicate that a breakpoint has been added.
+   */
+  addBreakpoint:
+  function DP_addBreakpoint(aLocation, aCallback, aNoEditorUpdate) {
+    let breakpoint = this.getBreakpoint(aLocation.url, aLocation.line);
+    if (breakpoint) {
+      aCallback && aCallback(breakpoint);
+      return;
+    }
+
+    this.activeThread.setBreakpoint(aLocation, function(aResponse, aBpClient) {
+      if (!aResponse.error) {
+        this.breakpoints[aBpClient.actor] = aBpClient;
+
+        if (!aNoEditorUpdate) {
+          let url = this._selectedScript();
+          if (url == aLocation.url) {
+            this._skipEditorBreakpointChange = true;
+            this.editor.addBreakpoint(aLocation.line - 1);
+            this._skipEditorBreakpointChange = false;
+          }
+        }
+      }
+
+      aCallback && aCallback(aBpClient, aResponse.error);
+    }.bind(this));
+  },
+
+  /**
+   * Remove a breakpoint.
+   *
+   * @param object aBreakpoint
+   *        The breakpoint you want to remove.
+   * @param function [aCallback]
+   *        Optional function to invoke once the breakpoint is removed. The
+   *        callback is invoked with one argument: the breakpoint location
+   *        object which holds the url and line properties.
+   * @param boolean [aNoEditorUpdate=false]
+   *        Tells if you want to skip editor updates. Typically the editor is
+   *        updated to visually indicate that a breakpoint has been removed.
+   */
+  removeBreakpoint:
+  function DP_removeBreakpoint(aBreakpoint, aCallback, aNoEditorUpdate) {
+    if (!(aBreakpoint.actor in this.breakpoints)) {
+      aCallback && aCallback(aBreakpoint.location);
+      return;
+    }
+
+    aBreakpoint.remove(function() {
+      delete this.breakpoints[aBreakpoint.actor];
+
+      if (!aNoEditorUpdate) {
+        let url = this._selectedScript();
+        if (url == aBreakpoint.location.url) {
+          this._skipEditorBreakpointChange = true;
+          this.editor.removeBreakpoint(aBreakpoint.location.line - 1);
+          this._skipEditorBreakpointChange = false;
+        }
+      }
+
+      aCallback && aCallback(aBreakpoint.location);
+    }.bind(this));
+  },
+
+  /**
+   * Get the breakpoint object at the given location.
+   *
+   * @param string aUrl
+   *        The URL of where the breakpoint is.
+   * @param number aLine
+   *        The line number where the breakpoint is.
+   * @return object
+   *         The BreakpointActor object.
+   */
+  getBreakpoint: function DP_getBreakpoint(aUrl, aLine) {
+    for each (let breakpoint in this.breakpoints) {
+      if (breakpoint.location.url == aUrl && breakpoint.location.line == aLine) {
+        return breakpoint;
+      }
+    }
+    return null;
+  },
+
+  /**
    * Closes the debugger UI removing child nodes and event listeners.
    */
   close: function DP_close() {
+    for each (let breakpoint in this.breakpoints) {
+      this.removeBreakpoint(breakpoint);
+    }
+
     if (this._tab) {
       this._tab._scriptDebugger = null;
       this._tab = null;
     }
     if (this.frame) {
       DebuggerUIPreferences.height = this.frame.height;
 
       this.frame.removeEventListener("unload", this._close, true);
@@ -187,17 +423,17 @@ DebuggerPane.prototype = {
             self.onConnected(self);
           }
         });
       });
     });
   },
 
   get debuggerWindow() {
-    return this.frame.contentWindow;
+    return this.frame ? this.frame.contentWindow : null;
   },
 
   get debuggerClient() {
     return this._client;
   },
 
   get activeThread() {
     try {
@@ -335,16 +571,17 @@ DebuggerUI.prototype = {
     let doc = dbg.frame.contentDocument;
     let scripts = doc.getElementById("scripts");
     let elt = scripts.getElementsByAttribute("value", aSourceUrl)[0];
     let script = elt.getUserData("sourceScript");
     script.loaded = true;
     script.text = aSourceText;
     script.contentType = aContentType;
     elt.setUserData("sourceScript", script, null);
+    dbg._updateEditorBreakpoints();
   }
 };
 
 /**
  * Various debugger UI preferences (currently just the pane height).
  */
 let DebuggerUIPreferences = {
 
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  *   Mozilla Foundation
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Victor Porof <vporof@mozilla.com> (original author)
+ *   Mihai Sucan <mihai.sucan@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -194,17 +195,16 @@ DebuggerView.Stackframes = {
    * @param boolean aSelect
    *        True if the frame should be selected, false otherwise.
    */
   highlightFrame: function DVF_highlightFrame(aDepth, aSelect) {
     let frame = document.getElementById("stackframe-" + aDepth);
 
     // the list item wasn't found in the stackframe container
     if (!frame) {
-      dump("The frame list item wasn't found in the stackframes container.");
       return;
     }
 
     // add the 'selected' css class if the frame isn't already selected
     if (aSelect && !frame.classList.contains("selected")) {
       frame.classList.add("selected");
 
     // remove the 'selected' css class if the frame is already selected
@@ -351,17 +351,16 @@ DebuggerView.Properties = {
     // compute the id of the element if not specified
     aId = aId || (aName.toLowerCase().trim().replace(" ", "-") + "-scope");
 
     // contains generic nodes and functionality
     let element = this._createPropertyElement(aName, aId, "scope", this._vars);
 
     // make sure the element was created successfully
     if (!element) {
-      dump("The debugger scope container wasn't created properly: " + aId);
       return null;
     }
 
     /**
      * @see DebuggerView.Properties._addVar
      */
     element.addVar = this._addVar.bind(this, element);
 
@@ -393,17 +392,16 @@ DebuggerView.Properties = {
     aId = aId || (aScope.id + "->" + aName + "-variable");
 
     // contains generic nodes and functionality
     let element = this._createPropertyElement(aName, aId, "variable",
                                               aScope.querySelector(".details"));
 
     // make sure the element was created successfully
     if (!element) {
-      dump("The debugger variable container wasn't created properly: " + aId);
       return null;
     }
 
     /**
      * @see DebuggerView.Properties._setGrip
      */
     element.setGrip = this._setGrip.bind(this, element);
 
@@ -461,17 +459,16 @@ DebuggerView.Properties = {
     if (!aVar) {
       return null;
     }
 
     let info = aVar.querySelector(".info") || aVar.target.info;
 
     // make sure the info node exists
     if (!info) {
-      dump("Could not set the grip for the corresponding variable: " + aVar.id);
       return null;
     }
 
     info.textContent = this._propertyString(aGrip);
     info.classList.add(this._propertyColor(aGrip));
 
     return aVar;
   },
@@ -564,17 +561,16 @@ DebuggerView.Properties = {
     aId = aId || (aVar.id + "->" + aProperty[0] + "-property");
 
     // contains generic nodes and functionality
     let element = this._createPropertyElement(aName, aId, "property",
                                               aVar.querySelector(".details"));
 
     // make sure the element was created successfully
     if (!element) {
-      dump("The debugger property container wasn't created properly.");
       return null;
     }
 
     /**
      * @see DebuggerView.Properties._setGrip
      */
     element.setGrip = this._setGrip.bind(this, element);
 
@@ -705,21 +701,19 @@ DebuggerView.Properties = {
    * @param object aParent
    *        The parent node which will contain the element.
    * @return object
    *         The newly created html node representing the generic elem.
    */
   _createPropertyElement: function DVP__createPropertyElement(aName, aId, aClass, aParent) {
     // make sure we don't duplicate anything and the parent exists
     if (document.getElementById(aId)) {
-      dump("Duplicating a property element id is not allowed.");
       return null;
     }
     if (!aParent) {
-      dump("A property element must have a valid parent node specified.");
       return null;
     }
 
     let element = document.createElement("div");
     let arrow = document.createElement("span");
     let name = document.createElement("span");
     let title = document.createElement("div");
     let details = document.createElement("div");
@@ -1122,16 +1116,25 @@ DebuggerView.Scripts = {
     for (let i = 0; i < this._scripts.itemCount; i++) {
       if (this._scripts.getItemAtIndex(i).value == aUrl) {
         this._scripts.selectedIndex = i;
         break;
       }
     }
   },
 
+   /**
+   	* Retrieve the URL of the selected script.
+   	* @return string|null
+   	*/
+   get selected() {
+    return this._scripts.selectedItem ?
+           this._scripts.selectedItem.value : null;
+   },
+
   /**
    * Adds a script to the scripts container.
    * If the script already exists (was previously added), null is returned.
    * Otherwise, the newly created element is returned.
    *
    * @param string aLabel
    *        The simplified script location to be shown.
    * @param string aScript
--- a/browser/devtools/debugger/debugger.js
+++ b/browser/devtools/debugger/debugger.js
@@ -73,17 +73,18 @@ function startDebuggingTab(aClient, aTab
 {
   gClient = aClient;
 
   gClient.attachTab(aTabGrip.actor, function(aResponse, aTabClient) {
     if (aTabClient) {
       gTabClient = aTabClient;
       gClient.attachThread(aResponse.threadActor, function(aResponse, aThreadClient) {
         if (!aThreadClient) {
-          dump("Couldn't attach to thread: "+aResponse.error+"\n");
+          Components.utils.reportError("Couldn't attach to thread: " +
+                                       aResponse.error);
           return;
         }
         ThreadState.connect(aThreadClient, function() {
           StackFrames.connect(aThreadClient, function() {
             SourceScripts.connect(aThreadClient, function() {
               aThreadClient.resume();
             });
           });
@@ -607,16 +608,17 @@ var SourceScripts = {
     if (!aScript.loaded) {
       // Notify the chrome code that we need to load a script file.
       var evt = document.createEvent("CustomEvent");
       evt.initCustomEvent("Debugger:LoadSource", true, false, aScript.url);
       document.documentElement.dispatchEvent(evt);
       window.editor.setText(DebuggerView.getStr("loadingText"));
     } else {
       window.editor.setText(aScript.text);
+      window.updateEditorBreakpoints();
     }
   }
 };
 
 SourceScripts.onPaused = SourceScripts.onPaused.bind(SourceScripts);
 SourceScripts.onScripts = SourceScripts.onScripts.bind(SourceScripts);
 SourceScripts.onNewScript = SourceScripts.onNewScript.bind(SourceScripts);
 SourceScripts.onScriptsCleared = SourceScripts.onScriptsCleared.bind(SourceScripts);
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -68,16 +68,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_dbg_stack-03.js \
 	browser_dbg_stack-04.js \
 	browser_dbg_location-changes.js \
 	browser_dbg_script-switching.js \
 	browser_dbg_pause-resume.js \
 	browser_dbg_update-editor-mode.js \
 	browser_dbg_select-line.js \
 	browser_dbg_clean-exit.js \
+	browser_dbg_bug723069_editor-breakpoints.js \
 	head.js \
 	$(NULL)
 
 _BROWSER_TEST_PAGES = \
 	browser_dbg_tab1.html \
 	browser_dbg_tab2.html \
 	browser_dbg_debuggerstatement.html \
 	browser_dbg_stack.html \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js
@@ -0,0 +1,274 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 723069: test the debugger breakpoint API and connection to the source
+ * editor.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+
+let gPane = null;
+let gTab = null;
+let gDebuggee = null;
+let gDebugger = null;
+let gScripts = null;
+let gEditor = null;
+let gBreakpoints = null;
+
+function test()
+{
+  let tempScope = {};
+  Cu.import("resource:///modules/source-editor.jsm", tempScope);
+  let SourceEditor = tempScope.SourceEditor;
+
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.debuggerWindow;
+
+    gPane.activeThread.addOneTimeListener("scriptsadded", function() {
+      Services.tm.currentThread.dispatch({ run: onScriptsAdded }, 0);
+    });
+    gDebuggee.firstCall();
+  });
+
+  function onScriptsAdded()
+  {
+    gScripts = gDebugger.DebuggerView.Scripts;
+
+    is(gDebugger.StackFrames.activeThread.state, "paused",
+      "Should only be getting stack frames while paused.");
+
+    is(gScripts._scripts.itemCount, 2, "Found the expected number of scripts.");
+
+    gEditor = gDebugger.editor;
+
+    isnot(gEditor.getText().indexOf("debugger"), -1,
+          "The correct script was loaded initially.");
+    isnot(gScripts.selected, gScripts.scriptLocations()[0],
+          "the correct sccript is selected");
+
+    gBreakpoints = gPane.breakpoints;
+    is(Object.keys(gBreakpoints), 0, "no breakpoints");
+    ok(!gPane.getBreakpoint("foo", 3), "getBreakpoint('foo', 3) returns falsey");
+
+    is(gEditor.getBreakpoints().length, 0, "no breakpoints in the editor");
+
+
+    info("add the first breakpoint");
+    gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                             onEditorBreakpointAddFirst);
+    let location = {url: gScripts.selected, line: 6};
+    executeSoon(function() {
+      gPane.addBreakpoint(location, onBreakpointAddFirst);
+    });
+  }
+
+  let breakpointsAdded = 0;
+  let breakpointsRemoved = 0;
+  let editorBreakpointChanges = 0;
+
+  function onEditorBreakpointAddFirst(aEvent)
+  {
+    gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                                onEditorBreakpointAddFirst);
+    editorBreakpointChanges++;
+
+    ok(aEvent, "breakpoint1 added to the editor");
+    is(aEvent.added.length, 1, "one breakpoint added to the editor");
+    is(aEvent.removed.length, 0, "no breakpoint was removed from the editor");
+    is(aEvent.added[0].line, 5, "editor breakpoint line is correct");
+
+    is(gEditor.getBreakpoints().length, 1,
+       "editor.getBreakpoints().length is correct");
+  }
+
+  function onBreakpointAddFirst(aBreakpointClient, aResponseError)
+  {
+    breakpointsAdded++;
+
+    ok(aBreakpointClient, "breakpoint1 added, client received");
+    ok(!aResponseError, "breakpoint1 added without errors");
+    is(aBreakpointClient.location.url, gScripts.selected,
+       "breakpoint1 client url is correct");
+    is(aBreakpointClient.location.line, 6,
+       "breakpoint1 client line is correct");
+
+    executeSoon(function() {
+      ok(aBreakpointClient.actor in gBreakpoints,
+         "breakpoint1 client found in the list of debugger breakpoints");
+      is(Object.keys(gBreakpoints).length, 1,
+         "the list of debugger breakpoints holds only one breakpoint");
+      is(gPane.getBreakpoint(gScripts.selected, 6), aBreakpointClient,
+         "getBreakpoint(selectedScript, 2) returns the correct breakpoint");
+
+      info("remove the first breakpoint");
+      gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                               onEditorBreakpointRemoveFirst);
+      gPane.removeBreakpoint(aBreakpointClient, onBreakpointRemoveFirst);
+    });
+  }
+
+  function onBreakpointRemoveFirst(aLocation)
+  {
+    breakpointsRemoved++;
+
+    ok(aLocation, "breakpoint1 removed");
+    is(aLocation.url, gScripts.selected, "breakpoint1 remove: url is correct");
+    is(aLocation.line, 6, "breakpoint1 remove: line is correct");
+
+    executeSoon(testBreakpointAddBackground);
+  }
+
+  function onEditorBreakpointRemoveFirst(aEvent)
+  {
+    gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                                onEditorBreakpointRemoveFirst);
+    editorBreakpointChanges++;
+
+    ok(aEvent, "breakpoint1 removed from the editor");
+    is(aEvent.added.length, 0, "no breakpoint was added to the editor");
+    is(aEvent.removed.length, 1, "one breakpoint was removed from the editor");
+    is(aEvent.removed[0].line, 5, "editor breakpoint line is correct");
+
+    is(gEditor.getBreakpoints().length, 0, "editor.getBreakpoints().length is correct");
+  }
+
+  function testBreakpointAddBackground()
+  {
+    info("add a breakpoint to the second script which is not selected");
+
+    is(Object.keys(gBreakpoints).length, 0, "no breakpoints in the debugger");
+    ok(!gPane.getBreakpoint(gScripts.selected, 6),
+       "getBreakpoint(selectedScript, 6) returns no breakpoint");
+
+    let script0 = gScripts.scriptLocations()[0];
+    isnot(script0, gScripts.selected,
+          "first script location is not the currently selected script");
+
+    let location = {url: script0, line: 5};
+    gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                             onEditorBreakpointAddBackgroundTrap);
+    gPane.addBreakpoint(location, onBreakpointAddBackground);
+  }
+
+  function onEditorBreakpointAddBackgroundTrap(aEvent)
+  {
+    // trap listener: no breakpoint must be added to the editor when a breakpoint
+    // is added to a script that is not currently selected.
+    gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                                onEditorBreakpointAddBackgroundTrap);
+    editorBreakpointChanges++;
+    ok(false, "breakpoint2 must not be added to the editor");
+  }
+
+  function onBreakpointAddBackground(aBreakpointClient, aResponseError)
+  {
+    breakpointsAdded++;
+
+    ok(aBreakpointClient, "breakpoint2 added, client received");
+    ok(!aResponseError, "breakpoint2 added without errors");
+    is(aBreakpointClient.location.url, gScripts.scriptLocations()[0],
+       "breakpoint2 client url is correct");
+    is(aBreakpointClient.location.line, 5,
+       "breakpoint2 client line is correct");
+
+    executeSoon(function() {
+      ok(aBreakpointClient.actor in gBreakpoints,
+         "breakpoint2 client found in the list of debugger breakpoints");
+      is(Object.keys(gBreakpoints).length, 1, "one breakpoint in the debugger");
+      is(gPane.getBreakpoint(gScripts.scriptLocations()[0], 5), aBreakpointClient,
+         "getBreakpoint(scriptLocations[0], 5) returns the correct breakpoint");
+
+      // remove the trap listener
+      gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                                  onEditorBreakpointAddBackgroundTrap);
+
+      gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                               onEditorBreakpointAddSwitch);
+      gEditor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+                               onEditorTextChanged);
+
+      info("switch to the second script");
+
+      gScripts._scripts.selectedIndex = 0;
+      gDebugger.SourceScripts.onChange({ target: gScripts._scripts });
+    });
+  }
+
+  function onEditorBreakpointAddSwitch(aEvent)
+  {
+    gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                                onEditorBreakpointAddSwitch);
+    editorBreakpointChanges++;
+
+    ok(aEvent, "breakpoint2 added to the editor");
+    is(aEvent.added.length, 1, "one breakpoint added to the editor");
+    is(aEvent.removed.length, 0, "no breakpoint was removed from the editor");
+    is(aEvent.added[0].line, 4, "editor breakpoint line is correct");
+
+    is(gEditor.getBreakpoints().length, 1,
+       "editor.getBreakpoints().length is correct");
+  }
+
+  function onEditorTextChanged()
+  {
+    gEditor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+                                onEditorTextChanged);
+
+    is(gEditor.getText().indexOf("debugger"), -1,
+       "The second script is no longer displayed.");
+
+    isnot(gEditor.getText().indexOf("firstCall"), -1,
+          "The first script is displayed.");
+
+    executeSoon(function() {
+      info("remove the second breakpoint using the mouse");
+
+      gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                               onEditorBreakpointRemoveSecond);
+
+      let testWin = gEditor.editorElement.ownerDocument.defaultView;
+      EventUtils.synthesizeMouse(gEditor.editorElement, 10, 70, {}, testWin);
+    });
+
+  }
+
+  function onEditorBreakpointRemoveSecond(aEvent)
+  {
+    gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE,
+                                onEditorBreakpointRemoveSecond);
+    editorBreakpointChanges++;
+
+    ok(aEvent, "breakpoint2 removed from the editor");
+    is(aEvent.added.length, 0, "no breakpoint was added to the editor");
+    is(aEvent.removed.length, 1, "one breakpoint was removed from the editor");
+    is(aEvent.removed[0].line, 4, "editor breakpoint line is correct");
+
+    is(gEditor.getBreakpoints().length, 0, "editor.getBreakpoints().length is correct");
+
+    executeSoon(function() {
+      gDebugger.StackFrames.activeThread.resume(finish);
+    });
+  }
+
+  registerCleanupFunction(function() {
+    is(Object.keys(gBreakpoints).length, 0, "no breakpoint in the debugger");
+    ok(!gPane.getBreakpoint(gScripts.scriptLocations()[0], 5),
+       "getBreakpoint(scriptLocations[0], 5) returns no breakpoint");
+
+    removeTab(gTab);
+    is(breakpointsAdded, 2, "correct number of breakpoints have been added");
+    is(breakpointsRemoved, 1, "correct number of breakpoints have been removed");
+    is(editorBreakpointChanges, 4, "correct number of editor breakpoint changes");
+    gPane = null;
+    gTab = null;
+    gDebuggee = null;
+    gDebugger = null;
+    gScripts = null;
+    gEditor = null;
+    gBreakpoints = null;
+  });
+}
--- a/browser/devtools/debugger/test/browser_dbg_clean-exit.js
+++ b/browser/devtools/debugger/test/browser_dbg_clean-exit.js
@@ -2,39 +2,41 @@
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Test that closing a tab with the debugger in a paused state exits cleanly.
 
 var gPane = null;
 var gTab = null;
-var gDebuggee = null;
 var gDebugger = null;
 
 const DEBUGGER_TAB_URL = EXAMPLE_URL + "browser_dbg_debuggerstatement.html";
 
 function test() {
   debug_tab_pane(DEBUGGER_TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
 
     testCleanExit();
   });
 }
 
 function testCleanExit() {
   gPane.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
       is(gDebugger.StackFrames.activeThread.paused, true,
         "Should be paused after the debugger statement.");
 
-      gPane._client.addOneTimeListener("tabDetached", function () {
-        finish();
-      });
-      removeTab(gTab);
+      closeDebuggerAndFinish(gTab);
     }}, 0);
   });
 
   gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement();
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_location-changes.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes.js
@@ -50,15 +50,22 @@ function testSimpleCall() {
 function testLocationChange()
 {
   gDebugger.StackFrames.activeThread.resume(function() {
     gPane._client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) {
       ok(true, "tabNavigated event was fired.");
       gPane._client.addOneTimeListener("tabAttached", function(aEvent, aPacket) {
         ok(true, "Successfully reattached to the tab again.");
 
-        removeTab(gTab);
-        finish();
+        closeDebuggerAndFinish(gTab);
       });
     });
     content.location = TAB1_URL;
   });
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_pause-resume.js
+++ b/browser/devtools/debugger/test/browser_dbg_pause-resume.js
@@ -1,23 +1,21 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 var gPane = null;
 var gTab = null;
-var gDebuggee = null;
 var gDebugger = null;
 
 function test() {
   debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
 
     testPause();
   });
 }
 
 function testPause() {
@@ -58,17 +56,22 @@ function testResume() {
 
       is(gDebugger.StackFrames.activeThread.paused, false,
         "Should be paused after an interrupt request.");
 
       let button = gDebugger.document.getElementById("resume");
       is(button.label, gDebugger.DebuggerView.getStr("pauseLabel"),
         "Button label should be pause when running.");
 
-      removeTab(gTab);
-      finish();
+      closeDebuggerAndFinish(gTab);
     }}, 0);
   });
 
   EventUtils.sendMouseEvent({ type: "click" },
     gDebugger.document.getElementById("resume"),
     gDebugger);
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
@@ -129,14 +129,20 @@ function resumeAndFinish() {
       ok(gDebugger.DebuggerView.Scripts.containsLabel("x/script.js"),
         "Script (4) label is incorrect.");
       ok(gDebugger.DebuggerView.Scripts.containsLabel("x/y/script.js"),
         "Script (5) label is incorrect.");
 
       is(vs._scripts.itemCount, 6,
         "Got too many script items in the list!");
 
-
-      removeTab(gTab);
-      finish();
+      closeDebuggerAndFinish(gTab);
     });
   });
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-02.js
@@ -111,21 +111,24 @@ function testSimpleCall() {
 
       EventUtils.sendMouseEvent({ type: "click" },
         testScope.querySelector(".title"),
         gDebugger);
 
       ok(!testScope.expanded,
         "Clicking again the testScope tilte should collapse it.");
 
-      resumeAndFinish();
+      gDebugger.StackFrames.activeThread.resume(function() {
+        closeDebuggerAndFinish(gTab);
+      });
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
 
-function resumeAndFinish() {
-  gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
-  });
-}
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-03.js
@@ -113,21 +113,24 @@ function testSimpleCall() {
 
       testScope.remove();
       is(removeCallbackSender, testScope,
         "The removeCallback wasn't called as it should.");
 
       is(gDebugger.DebuggerView.Properties._vars.childNodes.length, 4,
         "The scope should have been removed from the parent container tree.");
 
-      resumeAndFinish();
+      gDebugger.StackFrames.activeThread.resume(function() {
+        closeDebuggerAndFinish(gTab);
+      });
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
 
-function resumeAndFinish() {
-  gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
-  });
-}
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-04.js
@@ -68,21 +68,24 @@ function testSimpleCall() {
       is(testVar.querySelector(".details").childNodes.length, 0,
         "The var should remove all it's details container tree children.");
 
       testVar.remove();
 
       is(testScope.querySelector(".details").childNodes.length, 0,
         "The var should have been removed from the parent container tree.");
 
-      resumeAndFinish();
+      gDebugger.StackFrames.activeThread.resume(function() {
+        closeDebuggerAndFinish(gTab);
+      });
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
 
-function resumeAndFinish() {
-  gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
-  });
-}
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-05.js
@@ -76,21 +76,24 @@ function testSimpleCall() {
       is(testVar.querySelector(".details").childNodes.length, 0,
         "The var should remove all it's details container tree children.");
 
       testVar.remove();
 
       is(testScope.querySelector(".details").childNodes.length, 0,
         "The var should have been removed from the parent container tree.");
 
-      resumeAndFinish();
+      gDebugger.StackFrames.activeThread.resume(function() {
+        closeDebuggerAndFinish(gTab);
+      });
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
 
-function resumeAndFinish() {
-  gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
-  });
-}
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-06.js
@@ -112,22 +112,24 @@ function testSimpleCall() {
         "The grip information for the localVar3 wasn't set correctly.");
 
       is(localVar4.querySelector(".info").textContent, "null",
         "The grip information for the localVar4 wasn't set correctly.");
 
       is(localVar5.querySelector(".info").textContent, "[object Object]",
         "The grip information for the localVar5 wasn't set correctly.");
 
-
-      resumeAndFinish();
+      gDebugger.StackFrames.activeThread.resume(function() {
+        closeDebuggerAndFinish(gTab);
+      });
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
 
-function resumeAndFinish() {
-  gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
-  });
-}
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
@@ -5,24 +5,22 @@
 /**
  * Make sure that the property view displays function parameters.
  */
 
 const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
 
 var gPane = null;
 var gTab = null;
-var gDebuggee = null;
 var gDebugger = null;
 
 function test()
 {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
 
     testFrameParameters();
   });
 }
 
 function testFrameParameters()
@@ -82,15 +80,21 @@ function testFrameParameters()
 function resumeAndFinish() {
   gPane.activeThread.addOneTimeListener("framescleared", function() {
     Services.tm.currentThread.dispatch({ run: function() {
       var frames = gDebugger.DebuggerView.Stackframes._frames;
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 0,
         "Should have no frames.");
 
-      removeTab(gTab);
-      finish();
+      closeDebuggerAndFinish(gTab);
     }}, 0);
   });
 
   gDebugger.StackFrames.activeThread.resume();
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -5,24 +5,22 @@
 /**
  * Make sure that the property view displays the properties of objects.
  */
 
 const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
 
 var gPane = null;
 var gTab = null;
-var gDebuggee = null;
 var gDebugger = null;
 
 function test()
 {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.debuggerWindow;
 
     testFrameParameters();
   });
 }
 
 function testFrameParameters()
@@ -98,15 +96,21 @@ function testFrameParameters()
 function resumeAndFinish() {
   gPane.activeThread.addOneTimeListener("framescleared", function() {
     Services.tm.currentThread.dispatch({ run: function() {
       var frames = gDebugger.DebuggerView.Stackframes._frames;
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 0,
         "Should have no frames.");
 
-      removeTab(gTab);
-      finish();
+      closeDebuggerAndFinish(gTab);
     }}, 0);
   });
 
   gDebugger.StackFrames.activeThread.resume();
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_script-switching.js
+++ b/browser/devtools/debugger/test/browser_dbg_script-switching.js
@@ -98,11 +98,18 @@ function testSwitchPaused()
 function testSwitchRunning()
 {
   ok(gDebugger.editor.getText().search(/debugger/) != -1,
     "The second script is displayed again.");
 
   ok(gDebugger.editor.getText().search(/firstCall/) == -1,
     "The first script is no longer displayed.");
 
+  closeDebuggerAndFinish(gTab);
+}
+
+registerCleanupFunction(function() {
   removeTab(gTab);
-  finish();
-}
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_select-line.js
+++ b/browser/devtools/debugger/test/browser_dbg_select-line.js
@@ -63,23 +63,39 @@ function testSelectLine() {
           // Yield control back to the event loop so that the debugger has a
           // chance to highlight the proper line.
           executeSoon(function(){
             // getCaretPosition is 0-based.
             is(gDebugger.editor.getCaretPosition().line, 4,
                "The correct line is selected.");
 
             gDebugger.StackFrames.activeThread.resume(function() {
-              removeTab(gTab);
-              finish();
+              closeDebuggerAndFinish(gTab);
             });
           });
         });
 
+        // Scroll all the way down to ensure stackframe-3 is visible.
+        let stackframes = gDebugger.document.getElementById("stackframes");
+        stackframes.scrollTop = stackframes.scrollHeight;
+
         // Click the oldest stack frame.
+        let frames = gDebugger.DebuggerView.Stackframes._frames;
+        is(frames.querySelectorAll(".dbg-stackframe").length, 4,
+          "Should have four frames.");
+
         let element = gDebugger.document.getElementById("stackframe-3");
+        isnot(element, null, "Found the third stack frame.");
         EventUtils.synthesizeMouseAtCenter(element, {}, gDebugger);
       });
     }}, 0);
   });
 
   gDebuggee.firstCall();
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_stack-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-01.js
@@ -31,21 +31,24 @@ function testSimpleCall() {
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 1,
         "Should have only one frame.");
 
       is(childNodes.length, frames.querySelectorAll(".dbg-stackframe").length,
         "All children should be frames.");
 
-      resumeAndFinish();
+      gDebugger.StackFrames.activeThread.resume(function() {
+        closeDebuggerAndFinish(gTab);
+      });
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
 
-function resumeAndFinish() {
-  gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
-  });
-}
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_stack-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-02.js
@@ -62,22 +62,24 @@ function testEvalCall() {
         gDebugger);
 
       ok(frames.querySelector("#stackframe-0").classList.contains("selected"),
          "First frame should be selected after click inside the first frame.");
 
       ok(!frames.querySelector("#stackframe-1").classList.contains("selected"),
          "Second frame should not be selected after click inside the first frame.");
 
-      resumeAndFinish();
+      gDebugger.StackFrames.activeThread.resume(function() {
+        closeDebuggerAndFinish(gTab);
+      });
     }}, 0);
   });
 
   gDebuggee.evalCall();
 }
 
-function resumeAndFinish() {
-  gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
-  });
-}
-
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_stack-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-03.js
@@ -42,27 +42,30 @@ function testRecurse() {
 
         is(frames.querySelectorAll(".dbg-stackframe").length, pageSize * 2,
           "Should now have twice the max limit of frames.");
 
         gPane.activeThread.addOneTimeListener("framesadded", function() {
           is(frames.querySelectorAll(".dbg-stackframe").length, recurseLimit,
             "Should have reached the recurse limit.");
 
-          resumeAndFinish();
+          gDebugger.StackFrames.activeThread.resume(function() {
+            closeDebuggerAndFinish(gTab);
+          });
         });
 
         frames.scrollTop = frames.scrollHeight;
       });
 
       frames.scrollTop = frames.scrollHeight;
     }}, 0);
   });
 
   gDebuggee.recurse();
 }
 
-function resumeAndFinish() {
-  gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
-  });
-}
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_stack-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-04.js
@@ -43,18 +43,25 @@ function testEvalCallResume() {
           "Should have no frames after resume");
 
         is(childNodes.length, 1,
           "Should only have one child.");
 
         is(frames.querySelectorAll(".empty").length, 1,
            "Should have the empty list explanation.");
 
-        removeTab(gTab);
-        finish();
+        closeDebuggerAndFinish(gTab);
       });
 
       gPane.activeThread.resume();
     }}, 0);
   });
 
   gDebuggee.evalCall();
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
+++ b/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
@@ -67,12 +67,20 @@ function testSwitchPaused()
 
   ok(gDebugger.editor.getText().search(/firstCall/) != -1,
     "The first script is displayed.");
 
   is(gDebugger.editor.getMode(), SourceEditor.MODES.JAVASCRIPT,
      "Found the expected editor mode.");
 
   gDebugger.StackFrames.activeThread.resume(function() {
-    removeTab(gTab);
-    finish();
+    closeDebuggerAndFinish(gTab);
   });
 }
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+  gScripts = null;
+});
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -44,16 +44,24 @@ function addTab(aURL, aOnload)
 
   return tab;
 }
 
 function removeTab(aTab) {
   gBrowser.removeTab(aTab);
 }
 
+function closeDebuggerAndFinish(aTab) {
+  DebuggerUI.aWindow.addEventListener("Debugger:Shutdown", function cleanup() {
+    DebuggerUI.aWindow.removeEventListener("Debugger:Shutdown", cleanup, false);
+    finish();
+  }, false);
+  DebuggerUI.getDebugger(aTab).close();
+}
+
 function get_tab_actor_for_url(aClient, aURL, aCallback) {
   aClient.listTabs(function(aResponse) {
     for each (let tab in aResponse.tabs) {
       if (tab.url == aURL) {
         aCallback(tab);
         return;
       }
     }
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -18,16 +18,17 @@
  * The Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Mihai Sucan <mihai.sucan@gmail.com> (original author)
  *   Kenny Heaton <kennyheaton@gmail.com>
  *   Spyros Livathinos <livathinos.spyros@gmail.com>
+ *   Allen Eubank <adeubank@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -119,16 +120,28 @@ const DEFAULT_KEYBINDINGS = [
     accel: true,
     shift: true,
   },
   {
     action: "Unindent Lines",
     code: Ci.nsIDOMKeyEvent.DOM_VK_TAB,
     shift: true,
   },
+  {
+    action: "Move Lines Up",
+    code: Ci.nsIDOMKeyEvent.DOM_VK_UP,
+    ctrl: Services.appinfo.OS == "Darwin",
+    alt: true,
+  },
+  {
+    action: "Move Lines Down",
+    code: Ci.nsIDOMKeyEvent.DOM_VK_DOWN,
+    ctrl: Services.appinfo.OS == "Darwin",
+    alt: true,
+  },
 ];
 
 var EXPORTED_SYMBOLS = ["SourceEditor"];
 
 /**
  * The SourceEditor object constructor. The SourceEditor component allows you to
  * provide users with an editor tailored to the specific needs of editing source
  * code, aimed primarily at web developers.
@@ -362,26 +375,35 @@ SourceEditor.prototype = {
       "redo": [this.redo, this],
       "tab": [this._doTab, this],
       "Unindent Lines": [this._doUnindentLines, this],
       "enter": [this._doEnter, this],
       "Find...": [this.ui.find, this.ui],
       "Find Next Occurrence": [this.ui.findNext, this.ui],
       "Find Previous Occurrence": [this.ui.findPrevious, this.ui],
       "Goto Line...": [this.ui.gotoLine, this.ui],
+      "Move Lines Down": [this._moveLines, this],
     };
 
     for (let name in actions) {
       let action = actions[name];
       this._view.setAction(name, action[0].bind(action[1]));
     }
 
+    this._view.setAction("Move Lines Up", this._moveLines.bind(this, true));
+
     let keys = (config.keys || []).concat(DEFAULT_KEYBINDINGS);
     keys.forEach(function(aKey) {
-      let binding = new KeyBinding(aKey.code, aKey.accel, aKey.shift, aKey.alt);
+      // In Orion mod1 refers to Cmd on Macs and Ctrl on Windows and Linux.
+      // So, if ctrl is in aKey we use it on Windows and Linux, otherwise
+      // we use aKey.accel for mod1.
+      let mod1 = Services.appinfo.OS != "Darwin" &&
+                 "ctrl" in aKey ? aKey.ctrl : aKey.accel;
+      let binding = new KeyBinding(aKey.code, mod1, aKey.shift, aKey.alt,
+                                  aKey.ctrl);
       this._view.setKeyBinding(binding, aKey.action);
 
       if (aKey.callback) {
         this._view.setAction(aKey.action, aKey.callback);
       }
     }, this);
 
     this._initEventTarget();
@@ -574,16 +596,88 @@ SourceEditor.prototype = {
     }
 
     this.setText(this.getLineDelimiter() + prefix, selection.start,
                  selection.end);
     return true;
   },
 
   /**
+   * Move lines upwards or downwards, relative to the current caret location.
+   *
+   * @private
+   * @param boolean aLineAbove
+   *        True if moving lines up, false to move lines down.
+   */
+  _moveLines: function SE__moveLines(aLineAbove)
+  {
+    if (this.readOnly) {
+      return false;
+    }
+
+    let model = this._model;
+    let selection = this.getSelection();
+    let firstLine = model.getLineAtOffset(selection.start);
+    if (firstLine == 0 && aLineAbove) {
+      return true;
+    }
+
+    let lastLine = model.getLineAtOffset(selection.end);
+    let firstLineStart = model.getLineStart(firstLine);
+    let lastLineStart = model.getLineStart(lastLine);
+    if (selection.start != selection.end && lastLineStart == selection.end) {
+      lastLine--;
+    }
+    if (!aLineAbove && (lastLine + 1) == this.getLineCount()) {
+      return true;
+    }
+
+    let lastLineEnd = model.getLineEnd(lastLine, true);
+    let text = this.getText(firstLineStart, lastLineEnd);
+
+    if (aLineAbove) {
+      let aboveLine = firstLine - 1;
+      let aboveLineStart = model.getLineStart(aboveLine);
+
+      this.startCompoundChange();
+      if (lastLine == (this.getLineCount() - 1)) {
+        let delimiterStart = model.getLineEnd(aboveLine);
+        let delimiterEnd = model.getLineEnd(aboveLine, true);
+        let lineDelimiter = this.getText(delimiterStart, delimiterEnd);
+        text += lineDelimiter;
+        this.setText("", firstLineStart - lineDelimiter.length, lastLineEnd);
+      } else {
+        this.setText("", firstLineStart, lastLineEnd);
+      }
+      this.setText(text, aboveLineStart, aboveLineStart);
+      this.endCompoundChange();
+      this.setSelection(aboveLineStart, aboveLineStart + text.length);
+    } else {
+      let belowLine = lastLine + 1;
+      let belowLineEnd = model.getLineEnd(belowLine, true);
+
+      let insertAt = belowLineEnd - lastLineEnd + firstLineStart;
+      let lineDelimiter = "";
+      if (belowLine == this.getLineCount() - 1) {
+        let delimiterStart = model.getLineEnd(lastLine);
+        lineDelimiter = this.getText(delimiterStart, lastLineEnd);
+        text = lineDelimiter + text.substr(0, text.length -
+                                              lineDelimiter.length);
+      }
+      this.startCompoundChange();
+      this.setText("", firstLineStart, lastLineEnd);
+      this.setText(text, insertAt, insertAt);
+      this.endCompoundChange();
+      this.setSelection(insertAt + lineDelimiter.length,
+                        insertAt + text.length);
+    }
+    return true;
+  },
+
+  /**
    * The Orion Selection event handler. The current caret line is
    * highlighted and for Linux users the selected text is copied into the X11
    * PRIMARY buffer.
    *
    * @private
    * @param object aEvent
    *        The Orion Selection event object.
    */
--- a/browser/devtools/sourceeditor/source-editor.jsm
+++ b/browser/devtools/sourceeditor/source-editor.jsm
@@ -187,16 +187,17 @@ SourceEditor.DEFAULTS = {
   highlightCurrentLine: true,
 
   /**
    * An array of objects that allows you to define custom editor keyboard
    * bindings. Each object can have:
    *   - action - name of the editor action to invoke.
    *   - code - keyCode for the shortcut.
    *   - accel - boolean for the Accel key (Cmd on Macs, Ctrl on Linux/Windows).
+   *   - ctrl - boolean for the Control key
    *   - shift - boolean for the Shift key.
    *   - alt - boolean for the Alt key.
    *   - callback - optional function to invoke, if the action is not predefined
    *   in the editor.
    * @type array
    */
   keys: null,
 
--- a/browser/devtools/sourceeditor/test/Makefile.in
+++ b/browser/devtools/sourceeditor/test/Makefile.in
@@ -53,13 +53,14 @@ include $(topsrcdir)/config/rules.mk
 		browser_bug684546_reset_undo.js \
 		browser_bug695035_middle_click_paste.js \
 		browser_bug687160_line_api.js \
 		browser_bug650345_find.js \
 		browser_bug703692_focus_blur.js \
 		browser_bug725388_mouse_events.js \
 		browser_bug707987_debugger_breakpoints.js \
 		browser_bug712982_line_ruler_click.js \
+		browser_bug725618_moveLines_shortcut.js \
 		browser_bug700893_dirty_state.js \
 		head.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug725618_moveLines_shortcut.js
@@ -0,0 +1,117 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let tempScope = {};
+Cu.import("resource:///modules/source-editor.jsm", tempScope);
+let SourceEditor = tempScope.SourceEditor;
+
+let editor;
+let testWin;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  const windowUrl = "data:application/vnd.mozilla.xul+xml,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='test for bug 725618 - moveLines shortcut' width='300' height='500'>" +
+    "<box flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+}
+
+function initEditor()
+{
+  let box = testWin.document.querySelector("box");
+
+  let text = "target\nfoo\nbar"
+  let config = {
+    initialText: text,
+  };
+
+  editor = new SourceEditor();
+  editor.init(box, config, editorLoaded);
+}
+
+function editorLoaded()
+{
+  editor.focus();
+
+  editor.setCaretOffset(0);
+
+  let modifiers = {altKey: true, ctrlKey: Services.appinfo.OS == "Darwin"};
+
+  EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
+  is(editor.getText(), "foo\ntarget\nbar", "Move lines down works");
+  is(editor.getSelectedText(), "target\n", "selection is correct");
+
+  EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
+  is(editor.getText(), "foo\nbar\ntarget", "Move lines down works");
+  is(editor.getSelectedText(), "target", "selection is correct");
+
+  EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
+  is(editor.getText(), "foo\nbar\ntarget", "Check for bottom of editor works");
+  is(editor.getSelectedText(), "target", "selection is correct");
+
+  EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
+  is(editor.getText(), "foo\ntarget\nbar", "Move lines up works");
+  is(editor.getSelectedText(), "target\n", "selection is correct");
+
+  EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
+  is(editor.getText(), "target\nfoo\nbar", "Move lines up works");
+  is(editor.getSelectedText(), "target\n", "selection is correct");
+
+  EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
+  is(editor.getText(), "target\nfoo\nbar", "Check for top of editor works");
+  is(editor.getSelectedText(), "target\n", "selection is correct");
+
+  editor.setSelection(0, 10);
+  info("text within selection =" + editor.getSelectedText());
+
+  EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
+  is(editor.getText(), "bar\ntarget\nfoo", "Multiple line move down works");
+  is(editor.getSelectedText(), "target\nfoo", "selection is correct");
+
+  EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
+  is(editor.getText(), "bar\ntarget\nfoo",
+      "Check for bottom of editor works with multiple line selection");
+  is(editor.getSelectedText(), "target\nfoo", "selection is correct");
+
+  EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
+  is(editor.getText(), "target\nfoo\nbar", "Multiple line move up works");
+  is(editor.getSelectedText(), "target\nfoo\n", "selection is correct");
+
+  EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
+  is(editor.getText(), "target\nfoo\nbar",
+      "Check for top of editor works with multiple line selection");
+  is(editor.getSelectedText(), "target\nfoo\n", "selection is correct");
+
+  editor.readOnly = true;
+
+  editor.setCaretOffset(0);
+
+  EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
+  is(editor.getText(), "target\nfoo\nbar",
+     "Check for readOnly mode works with move lines up");
+
+  EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
+  is(editor.getText(), "target\nfoo\nbar",
+     "Check for readOnly mode works with move lines down");
+
+  finish();
+}
+
+registerCleanupFunction(function()
+{
+  editor.destroy();
+  testWin.close();
+  testWin = editor = null;
+});
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -1193,36 +1193,51 @@ CssSheet.prototype = {
  * argument must point to the element.
  * @constructor
  */
 function CssRule(aCssSheet, aDomRule, aElement)
 {
   this._cssSheet = aCssSheet;
   this._domRule = aDomRule;
 
+  let parentRule = aDomRule.parentRule;
+  if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
+    this.mediaText = parentRule.media.mediaText;
+  }
+
   if (this._cssSheet) {
     // parse _domRule.selectorText on call to this.selectors
     this._selectors = null;
     this.line = this._cssSheet._cssLogic.domUtils.getRuleLine(this._domRule);
     this.source = this._cssSheet.shortSource + ":" + this.line;
+    if (this.mediaText) {
+      this.source += " @media " + this.mediaText;
+    }
     this.href = this._cssSheet.href;
     this.contentRule = this._cssSheet.contentSheet;
   } else if (aElement) {
     this._selectors = [ new CssSelector(this, "@element.style") ];
     this.line = -1;
     this.source = CssLogic.l10n("rule.sourceElement");
     this.href = "#";
     this.contentRule = true;
     this.sourceElement = aElement;
   }
 }
 
 CssRule.prototype = {
   _passId: null,
 
+  mediaText: "",
+
+  get isMediaRule()
+  {
+    return !!this.mediaText;
+  },
+
   /**
    * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
    *
    * @return {boolean} true if the parent stylesheet is allowed by the current
    * sourceFilter, or false otherwise.
    */
   get sheetAllowed()
   {
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -351,20 +351,30 @@ ElementStyle.prototype = {
  */
 function Rule(aElementStyle, aOptions)
 {
   this.elementStyle = aElementStyle;
   this.domRule = aOptions.domRule || null;
   this.style = aOptions.style || this.domRule.style;
   this.selectorText = aOptions.selectorText || this.domRule.selectorText;
   this.inherited = aOptions.inherited || null;
+
+  if (this.domRule) {
+    let parentRule = this.domRule.parentRule;
+    if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
+      this.mediaText = parentRule.media.mediaText;
+    }
+  }
+
   this._getTextProperties();
 }
 
 Rule.prototype = {
+  mediaText: "",
+
   get title()
   {
     if (this._title) {
       return this._title;
     }
     this._title = CssLogic.shortSource(this.sheet);
     if (this.domRule) {
       this._title += ":" + this.ruleLine;
@@ -375,17 +385,17 @@ Rule.prototype = {
       if (this.inherited.id) {
         eltText += "#" + this.inherited.id;
       }
       let args = [eltText, this._title];
       this._title = CssLogic._strings.formatStringFromName("rule.inheritedSource",
                                                            args, args.length);
     }
 
-    return this._title;
+    return this._title + (this.mediaText ? " @media " + this.mediaText : "");
   },
 
   /**
    * The rule's stylesheet.
    */
   get sheet()
   {
     return this.domRule ? this.domRule.parentStyleSheet : null;
--- a/browser/devtools/styleinspector/test/Makefile.in
+++ b/browser/devtools/styleinspector/test/Makefile.in
@@ -56,28 +56,31 @@ include $(topsrcdir)/config/rules.mk
   browser_csslogic_inherited.js \
   browser_ruleview_editor.js \
   browser_ruleview_editor_changedvalues.js \
   browser_ruleview_inherit.js \
   browser_ruleview_manipulation.js \
   browser_ruleview_override.js \
   browser_ruleview_ui.js \
   browser_bug705707_is_content_stylesheet.js \
+  browser_bug722196_property_view_media_queries.js \
+  browser_bug722196_rule_view_media_queries.js \
   browser_bug_592743_specificity.js \
   head.js \
   $(NULL)
 
 _BROWSER_TEST_PAGES = \
   browser_bug683672.html \
   browser_bug705707_is_content_stylesheet.html \
   browser_bug705707_is_content_stylesheet_imported.css \
   browser_bug705707_is_content_stylesheet_imported2.css \
   browser_bug705707_is_content_stylesheet_linked.css \
   browser_bug705707_is_content_stylesheet_script.css \
   browser_bug705707_is_content_stylesheet.xul \
   browser_bug705707_is_content_stylesheet_xul.css \
+  browser_bug722196_identify_media_queries.html \
   $(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs:: $(_BROWSER_TEST_PAGES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug722196_identify_media_queries.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+  <title>test</title>
+  <script type="application/javascript;version=1.7">
+
+  </script>
+  <style>
+    div {
+      width: 1000px;
+      height: 100px;
+      background-color: #f00;
+    }
+
+    @media screen and (min-width: 1px) {
+      div {
+        width: 200px;
+      }
+    }
+  </style>
+</head>
+<body>
+<div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug722196_property_view_media_queries.js
@@ -0,0 +1,68 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that we correctly display appropriate media query titles in the
+// property view.
+
+let doc;
+let stylePanel;
+
+const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/" +
+  "test/browser_bug722196_identify_media_queries.html";
+
+function test()
+{
+  waitForExplicitFinish();
+  addTab(TEST_URI);
+  browser.addEventListener("load", docLoaded, true);
+}
+
+function docLoaded()
+{
+  browser.removeEventListener("load", docLoaded, true);
+  doc = content.document;
+  stylePanel = new StyleInspector(window);
+  Services.obs.addObserver(checkSheets, "StyleInspector-opened", false);
+  stylePanel.createPanel(false, function() {
+    stylePanel.open(doc.body);
+  });
+}
+
+function checkSheets()
+{
+  Services.obs.removeObserver(checkSheets, "StyleInspector-opened", false);
+
+  ok(stylePanel.isOpen(), "style inspector is open");
+
+  var div = doc.querySelector("div");
+  ok(div, "captain, we have the div");
+
+  stylePanel.selectNode(div);
+
+  let cssLogic = stylePanel.cssLogic;
+  cssLogic.processMatchedSelectors();
+
+  let _strings = Services.strings
+    .createBundle("chrome://browser/locale/devtools/styleinspector.properties");
+
+  let inline = _strings.GetStringFromName("rule.sourceInline");
+
+  let source1 = inline + ":8";
+  let source2 = inline + ":15 @media screen and (min-width: 1px)";
+  is(cssLogic._matchedRules[0][0].source, source1,
+    "rule.source gives correct output for rule 1");
+  is(cssLogic._matchedRules[1][0].source, source2,
+    "rule.source gives correct output for rule 2");
+
+  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
+  stylePanel.close();
+}
+
+function finishUp()
+{
+  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
+  doc = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug722196_rule_view_media_queries.js
@@ -0,0 +1,55 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that we correctly display appropriate media query titles in the
+// rule view.
+
+let tempScope = {};
+Cu.import("resource:///modules/devtools/CssRuleView.jsm", tempScope);
+let _ElementStyle = tempScope._ElementStyle;
+let doc;
+
+const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/" +
+  "test/browser_bug722196_identify_media_queries.html";
+
+function test()
+{
+  waitForExplicitFinish();
+  addTab(TEST_URI);
+  browser.addEventListener("load", docLoaded, true);
+}
+
+function docLoaded()
+{
+  browser.removeEventListener("load", docLoaded, true);
+  doc = content.document;
+  checkSheets();
+}
+
+function checkSheets()
+{
+  var div = doc.querySelector("div");
+  ok(div, "captain, we have the div");
+
+  let elementStyle = new _ElementStyle(div);
+  is(elementStyle.rules.length, 3, "Should have 3 rules.");
+
+  let _strings = Services.strings
+    .createBundle("chrome://browser/locale/devtools/styleinspector.properties");
+
+  let inline = _strings.GetStringFromName("rule.sourceInline");
+
+  is(elementStyle.rules[0].title, inline, "check rule 0 title");
+  is(elementStyle.rules[1].title, inline +
+    ":15 @media screen and (min-width: 1px)", "check rule 1 title");
+  is(elementStyle.rules[2].title, inline + ":8", "check rule 2 title");
+  finishUp();
+}
+
+function finishUp()
+{
+  doc = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
--- a/browser/devtools/webconsole/GcliCommands.jsm
+++ b/browser/devtools/webconsole/GcliCommands.jsm
@@ -15,16 +15,17 @@
  *
  * The Initial Developer of the Original Code is
  * The Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Joe Walker <jwalker@mozilla.com> (original author)
+ *   Mihai Sucan <mihai.sucan@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -155,18 +156,16 @@ gcli.addCommand({
    ],
    exec: function(args, context) {
      let hud = HUDService.getHudReferenceById(context.environment.hudId);
      let StyleEditor = hud.gcliterm.document.defaultView.StyleEditor;
      StyleEditor.openChrome(args.resource.element, args.line);
    }
 });
 
-let breakpoints = [];
-
 /**
  * 'break' command
  */
 gcli.addCommand({
   name: "break",
   description: gcli.lookup("breakDesc"),
   manual: gcli.lookup("breakManual")
 });
@@ -175,27 +174,35 @@ gcli.addCommand({
 /**
  * 'break list' command
  */
 gcli.addCommand({
   name: "break list",
   description: gcli.lookup("breaklistDesc"),
   returnType: "html",
   exec: function(args, context) {
-    if (breakpoints.length === 0) {
+    let win = HUDService.currentContext();
+    let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab);
+    if (!dbg) {
+      return gcli.lookup("breakaddDebuggerStopped");
+    }
+    let breakpoints = dbg.breakpoints;
+
+    if (Object.keys(breakpoints).length === 0) {
       return gcli.lookup("breaklistNone");
     }
 
     let reply = gcli.lookup("breaklistIntro");
     reply += "<ol>";
-    breakpoints.forEach(function(breakpoint) {
+    for each (let breakpoint in breakpoints) {
       let text = gcli.lookupFormat("breaklistLineEntry",
-                                   [breakpoint.file, breakpoint.line]);
+                                   [breakpoint.location.url,
+                                    breakpoint.location.line]);
       reply += "<li>" + text + "</li>";
-    });
+    };
     reply += "</ol>";
     return reply;
   }
 });
 
 
 /**
  * 'break add' command
@@ -243,24 +250,21 @@ gcli.addCommand({
     args.type = "line";
     let win = HUDService.currentContext();
     let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab);
     if (!dbg) {
       return gcli.lookup("breakaddDebuggerStopped");
     }
     var promise = context.createPromise();
     let position = { url: args.file, line: args.line };
-    dbg.activeThread.setBreakpoint(position, function(aResponse, aBpClient) {
-      if (aResponse.error) {
-        promise.resolve(gcli.lookupFormat("breakaddFailed",
-                        [ aResponse.error ]));
+    dbg.addBreakpoint(position, function(aBreakpoint, aError) {
+      if (aError) {
+        promise.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
         return;
       }
-      args.client = aBpClient;
-      breakpoints.push(args);
       promise.resolve(gcli.lookup("breakaddAdded"));
     });
     return promise;
   }
 });
 
 
 /**
@@ -270,28 +274,46 @@ gcli.addCommand({
   name: "break del",
   description: gcli.lookup("breakdelDesc"),
   params: [
     {
       name: "breakid",
       type: {
         name: "number",
         min: 0,
-        max: function() { return breakpoints.length - 1; }
+        max: function() {
+          let win = HUDService.currentContext();
+          let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab);
+          if (!dbg) {
+            return gcli.lookup("breakaddDebuggerStopped");
+          }
+          return Object.keys(dbg.breakpoints).length - 1;
+        },
       },
       description: gcli.lookup("breakdelBreakidDesc")
     }
   ],
   returnType: "html",
   exec: function(args, context) {
-    let breakpoint = breakpoints.splice(args.breakid, 1)[0];
-    var promise = context.createPromise();
+    let win = HUDService.currentContext();
+    let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab);
+    if (!dbg) {
+      return gcli.lookup("breakaddDebuggerStopped");
+    }
+
+    let breakpoints = dbg.breakpoints;
+    let id = Object.keys(dbg.breakpoints)[args.breakid];
+    if (!id || !(id in breakpoints)) {
+      return gcli.lookup("breakNotFound");
+    }
+
+    let promise = context.createPromise();
     try {
-      breakpoint.client.remove(function(aResponse) {
-                                 promise.resolve(gcli.lookup("breakdelRemoved"));
-                               });
+      dbg.removeBreakpoint(breakpoints[id], function() {
+        promise.resolve(gcli.lookup("breakdelRemoved"));
+      });
     } catch (ex) {
       // If the debugger has been closed already, don't scare the user.
       promise.resolve(gcli.lookup("breakdelRemoved"));
     }
     return promise;
   }
 });
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -250,16 +250,20 @@ breakdelDesc=Remove a breakpoint
 # LOCALIZATION NOTE (breakdelBreakidDesc) A very short string used to describe
 # the function of the index parameter in the 'break del' command.
 breakdelBreakidDesc=Index of breakpoint
 
 # LOCALIZATION NOTE (breakdelRemoved) Used in the output of the 'break del'
 # command to explain that a breakpoint was removed.
 breakdelRemoved=Breakpoint removed
 
+# LOCALIZATION NOTE (breakNotFound) Used in the output of the 'break del'
+# command to explain that the breakpoint was not found.
+breakNotFound=Breakpoint was not found
+
 # LOCALIZATION NOTE (consolecloseDesc) A very short description of the
 # 'console close' command. This string is designed to be shown in a menu
 # alongside the command name, which is why it should be as short as possible.
 consolecloseDesc=Close the console
 
 # LOCALIZATION NOTE (editDesc) A very short description of the 'edit'
 # command. See editManual for a fuller description of what it does. This
 # string is designed to be shown in a menu alongside the command name, which
--- a/browser/themes/gnomestripe/devtools/csshtmltree.css
+++ b/browser/themes/gnomestripe/devtools/csshtmltree.css
@@ -141,16 +141,17 @@
   cursor: pointer;
 }
 
 /* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
 .rule-text {
   direction: ltr;
   padding: 0;
   -moz-padding-start: 20px;
+  vertical-align: text-bottom;
 }
 
 .bestmatch {
   color: black;
 }
 .matched {
   text-decoration: line-through;
 }
--- a/browser/themes/pinstripe/devtools/csshtmltree.css
+++ b/browser/themes/pinstripe/devtools/csshtmltree.css
@@ -143,16 +143,17 @@
   cursor: pointer;
 }
 
 /* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
 .rule-text {
   direction: ltr;
   padding: 0;
   -moz-padding-start: 20px;
+  vertical-align: text-bottom;
 }
 
 .bestmatch {
   color: black;
 }
 .matched {
   text-decoration: line-through;
 }
--- a/browser/themes/winstripe/devtools/csshtmltree.css
+++ b/browser/themes/winstripe/devtools/csshtmltree.css
@@ -141,16 +141,17 @@
   cursor: pointer;
 }
 
 /* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
 .rule-text {
   direction: ltr;
   padding: 0;
   -moz-padding-start: 20px;
+  vertical-align: text-bottom;
 }
 
 .bestmatch {
   color: black;
 }
 .matched {
   text-decoration: line-through;
 }
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -390,16 +390,17 @@ user_pref("camino.warn_when_closing", fa
 // Make url-classifier updates so rare that they won't affect tests
 user_pref("urlclassifier.updateinterval", 172800);
 // Point the url-classifier to the local testing server for fast failures
 user_pref("browser.safebrowsing.provider.0.gethashURL", "http://%(server)s/safebrowsing-dummy/gethash");
 user_pref("browser.safebrowsing.provider.0.keyURL", "http://%(server)s/safebrowsing-dummy/newkey");
 user_pref("browser.safebrowsing.provider.0.updateURL", "http://%(server)s/safebrowsing-dummy/update");
 // Point update checks to the local testing server for fast failures
 user_pref("extensions.update.url", "http://%(server)s/extensions-dummy/updateURL");
+user_pref("extensions.update.background.url", "http://%(server)s/extensions-dummy/updateBackgroundURL");
 user_pref("extensions.blocklist.url", "http://%(server)s/extensions-dummy/blocklistURL");
 user_pref("extensions.hotfix.url", "http://%(server)s/extensions-dummy/hotfixURL");
 // Make sure opening about:addons won't hit the network
 user_pref("extensions.webservice.discoverURL", "http://%(server)s/extensions-dummy/discoveryURL");
 // Make sure AddonRepository won't hit the network
 user_pref("extensions.getAddons.maxResults", 0);
 user_pref("extensions.getAddons.get.url", "http://%(server)s/extensions-dummy/repositoryGetURL");
 user_pref("extensions.getAddons.getWithPerformance.url", "http://%(server)s/extensions-dummy/repositoryGetWithPerformanceURL");
@@ -732,18 +733,21 @@ user_pref("camino.use_system_proxy_setti
         self.log.info("Failed to read image from %s", imgoutput)
 
     import base64
     encoded = base64.b64encode(image)
     self.log.info("SCREENSHOT: data:image/png;base64,%s", encoded)
 
   def killAndGetStack(self, proc, utilityPath, debuggerInfo):
     """Kill the process, preferrably in a way that gets us a stack trace."""
-    if not debuggerInfo and not self.haveDumpedScreen:
-      self.dumpScreen(utilityPath)
+    if not debuggerInfo:
+      if self.haveDumpedScreen:
+        self.log.info("Not taking screenshot here: see the one that was previously logged")
+      else:
+        self.dumpScreen(utilityPath)
 
     if self.CRASHREPORTER and not debuggerInfo:
       if self.UNIXISH:
         # ABRT will get picked up by Breakpad's signal handler
         os.kill(proc.pid, signal.SIGABRT)
         return
       elif self.IS_WIN32:
         # We should have a "crashinject" program in our utility path
@@ -790,18 +794,21 @@ user_pref("camino.use_system_proxy_setti
       while line != "" and not didTimeout:
         if logger:
           logger.log(line)
         if "TEST-START" in line and "|" in line:
           self.lastTestSeen = line.split("|")[1].strip()
         if stackFixerFunction:
           line = stackFixerFunction(line)
         self.log.info(line.rstrip().decode("UTF-8", "ignore"))
-        if not debuggerInfo and not self.haveDumpedScreen and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line:
-          self.dumpScreen(utilityPath)
+        if not debuggerInfo and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line:
+          if self.haveDumpedScreen:
+            self.log.info("Not taking screenshot here: see the one that was previously logged")
+          else:
+            self.dumpScreen(utilityPath)
 
         (line, didTimeout) = self.readWithTimeout(logsource, timeout)
         if not hitMaxTime and maxTime and datetime.now() - startTime > timedelta(seconds = maxTime):
           # Kill the application, but continue reading from stack fixer so as not to deadlock on stackFixerProcess.wait().
           hitMaxTime = True
           self.log.info("TEST-UNEXPECTED-FAIL | %s | application ran for longer than allowed maximum time of %d seconds", self.lastTestSeen, int(maxTime))
           self.killAndGetStack(proc, utilityPath, debuggerInfo)
       if didTimeout:
--- a/build/mobile/robocop/Makefile.in
+++ b/build/mobile/robocop/Makefile.in
@@ -61,17 +61,20 @@ JAVAFILES = \
   FennecNativeDriver.java \
   FennecNativeElement.java \
   RoboCopException.java \
   PaintedSurface.java \
   $(NULL)
 
 _JAVA_TESTS = $(patsubst $(TESTPATH)/%.in,%,$(wildcard $(TESTPATH)/*.java.in))
 
-_TEST_FILES = $(wildcard $(TESTPATH)/*.html)
+_TEST_FILES = \
+  $(wildcard $(TESTPATH)/*.html) \
+  $(wildcard $(TESTPATH)/*.sjs) \
+  $(NULL)
 
 _ROBOCOP_TOOLS = \
   $(TESTPATH)/robocop.ini \
   parse_ids.py \
   $(NULL)
 
 GARBAGE += \
   AndroidManifest.xml \
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -296,16 +296,20 @@ MOZ_GIO_CFLAGS = @MOZ_GIO_CFLAGS@
 MOZ_GIO_LIBS = @MOZ_GIO_LIBS@
 
 MOZ_NATIVE_NSPR = @MOZ_NATIVE_NSPR@
 MOZ_NATIVE_NSS = @MOZ_NATIVE_NSS@
 
 MOZ_B2G_RIL = @MOZ_B2G_RIL@
 MOZ_B2G_BT = @MOZ_B2G_BT@
 
+MOZ_ASAN = @MOZ_ASAN@
+MOZ_CFLAGS_NSS = @MOZ_CFLAGS_NSS@
+MOZ_NO_WLZDEFS = @MOZ_NO_WLZDEFS@
+
 BUILD_CTYPES = @BUILD_CTYPES@
 
 COMPILE_ENVIRONMENT = @COMPILE_ENVIRONMENT@
 CROSS_COMPILE   = @CROSS_COMPILE@
 
 WCHAR_CFLAGS	= @WCHAR_CFLAGS@
 
 OS_CPPFLAGS	= @CPPFLAGS@
--- a/configure.in
+++ b/configure.in
@@ -1833,27 +1833,59 @@ if test -n "$CLANG_CC"; then
     _WARNINGS_CFLAGS="-Qunused-arguments ${_WARNINGS_CFLAGS}"
     CPPFLAGS="-Qunused-arguments ${CPPFLAGS}"
 fi
 if test -n "$CLANG_CXX"; then
     _WARNINGS_CXXFLAGS="-Qunused-arguments ${_WARNINGS_CXXFLAGS}"
 fi
 
 dnl ========================================================
+dnl = Use Address Sanitizer
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(address-sanitizer,
+[  --enable-address-sanitizer       Enable Address Sanitizer (default=no)],
+    MOZ_ASAN=1,
+    MOZ_ASAN= )
+if test -n "$MOZ_ASAN"; then
+    MOZ_LLVM_HACKS=1
+    AC_DEFINE(MOZ_ASAN)
+fi
+AC_SUBST(MOZ_ASAN)
+
+dnl ========================================================
+dnl = Enable hacks required for LLVM instrumentations
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(llvm-hacks,
+[  --enable-llvm-hacks       Enable workarounds required for several LLVM instrumentations (default=no)],
+    MOZ_LLVM_HACKS=1,
+    MOZ_LLVM_HACKS= )
+if test -n "$MOZ_LLVM_HACKS"; then
+    MOZ_NO_WLZDEFS=1
+    MOZ_CFLAGS_NSS=1
+fi
+AC_SUBST(MOZ_NO_WLZDEFS)
+AC_SUBST(MOZ_CFLAGS_NSS)
+
+dnl ========================================================
 dnl GNU specific defaults
 dnl ========================================================
 if test "$GNU_CC"; then
     # FIXME: Let us build with strict aliasing. bug 414641.
     CFLAGS="$CFLAGS -fno-strict-aliasing"
     MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@'
     MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@'
     DSO_LDOPTS='-shared'
     if test "$GCC_USE_GNU_LD"; then
-        # Don't allow undefined symbols in libraries
-        DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs"
+        # Some tools like ASan use a runtime library that is only
+        # linked against executables, so we must allow undefined
+        # symbols for shared objects in some cases.
+        if test -z "$MOZ_NO_WLZDEFS"; then
+            # Don't allow undefined symbols in libraries
+            DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs"
+        fi
     fi
     WARNINGS_AS_ERRORS='-Werror -Wno-error=uninitialized'
     DSO_CFLAGS=''
     DSO_PIC_CFLAGS='-fPIC'
     ASFLAGS="$ASFLAGS -fPIC"
     _MOZ_RTTI_FLAGS_ON=-frtti
     _MOZ_RTTI_FLAGS_OFF=-fno-rtti
 
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -749,17 +749,17 @@ protected:
     // extensions
     enum WebGLExtensionID {
         WebGL_OES_texture_float,
         WebGL_OES_standard_derivatives,
         WebGL_EXT_texture_filter_anisotropic,
         WebGL_MOZ_WEBGL_lose_context,
         WebGLExtensionID_Max
     };
-    nsCOMPtr<WebGLExtension> mEnabledExtensions[WebGLExtensionID_Max];
+    nsRefPtr<WebGLExtension> mEnabledExtensions[WebGLExtensionID_Max];
     bool IsExtensionEnabled(WebGLExtensionID ext) const {
         NS_ABORT_IF_FALSE(ext >= 0 && ext < WebGLExtensionID_Max, "bogus index!");
         return mEnabledExtensions[ext] != nsnull;
     }
     bool IsExtensionSupported(WebGLExtensionID ei);
 
     bool InitAndValidateGL();
     bool ValidateBuffers(PRInt32* maxAllowedCount, const char *info);
--- a/content/canvas/test/webgl/test_webgl_conformance_test_suite.html
+++ b/content/canvas/test/webgl/test_webgl_conformance_test_suite.html
@@ -372,17 +372,17 @@ function start() {
       case reportType.FINISH_PAGE:
         return this.finishPage(success);
       case reportType.FINISHED_ALL_TESTS:
         this.finishedTestSuite();
         return true;
       default:
         throw 'unhandled';
         break;
-    };
+    }
   };
 
   var getURLOptions = function(obj) {
     var s = window.location.href;
     var q = s.indexOf("?");
     var e = s.indexOf("#");
     if (e < 0) {
       e = s.length;
@@ -399,48 +399,47 @@ function start() {
 
   getURLOptions(OPTIONS);
 
   function runTestSuite() {
     var reporter = new Reporter();
 
     // try to create a dummy WebGL context, just to catch context creation failures once here,
     // rather than having them result in 100's of failures (one in each test page)
-    var canvas = document.getElementById("webglcheck-default");
     var ctx;
     try {
-        ctx = canvas.getContext("experimental-webgl");
-    } catch(e) {
-        ok(false, "canvas.getContext() failed", e);
-    }
-
-    if (ctx) {
-        statusTextNode.textContent = 'Loading test lists...';
-        var iframe = document.getElementById("testframe");
-        var testHarness = new WebGLTestHarnessModule.TestHarness(
-            iframe,
-            '00_test_list.txt',
-            function(type, msg, success) {
-                return reporter.reportFunc(type, msg, success);
-            },
-            OPTIONS);
-        testHarness.setTimeoutDelay(20000); // and make it much higher when running under valgrind.
-        window.webglTestHarness = testHarness;
-    } else {
+      ctx = document.getElementById("webglcheck-default")
+                    .getContext("experimental-webgl");
+    } catch(e) {}
+    if (!ctx) {
         var errmsg = "Can't create a WebGL context";
         reporter.fullResultsNode.textContent = errmsg;
         // Workaround for SeaMonkey tinderboxes which don't support WebGL.
         if (navigator.userAgent.match(/ SeaMonkey\//))
           todo(false, errmsg + " (This is expected on SeaMonkey (tinderboxes).)");
         else
           ok(false, errmsg);
         dump("WebGL mochitest failed: " + errmsg + "\n");
         reporter.finishedTestSuite();
+        return;
     }
-  };
+
+    statusTextNode.textContent = 'Loading test lists...';
+    var iframe = document.getElementById("testframe");
+    var testHarness = new WebGLTestHarnessModule.TestHarness(
+        iframe,
+        '00_test_list.txt',
+        function(type, msg, success) {
+          return reporter.reportFunc(type, msg, success);
+        },
+        OPTIONS);
+    // Make timeout delay much higher when running under valgrind.
+    testHarness.setTimeoutDelay(20000);
+    window.webglTestHarness = testHarness;
+  }
 
   SimpleTest.requestLongerTimeout(3);
 
   var statusElem = document.getElementById("status");
   var statusTextNode = document.createTextNode('');
   statusElem.appendChild(statusTextNode);
 
   var expectedtofailElem = document.getElementById("expectedtofail");
--- a/content/html/document/src/ImageDocument.cpp
+++ b/content/html/document/src/ImageDocument.cpp
@@ -686,44 +686,27 @@ ImageDocument::CreateSyntheticDocument()
   return NS_OK;
 }
 
 nsresult
 ImageDocument::CheckOverflowing(bool changeState)
 {
   /* Create a scope so that the style context gets destroyed before we might
    * call RebuildStyleData.  Also, holding onto pointers to the
-   * presentatation through style resolution is potentially dangerous.
+   * presentation through style resolution is potentially dangerous.
    */
   {
     nsIPresShell *shell = GetShell();
     if (!shell) {
       return NS_OK;
     }
 
     nsPresContext *context = shell->GetPresContext();
     nsRect visibleArea = context->GetVisibleArea();
 
-    Element* body = GetBodyElement();
-    if (!body) {
-      NS_WARNING("no body on image document!");
-      return NS_ERROR_FAILURE;
-    }
-
-    nsRefPtr<nsStyleContext> styleContext =
-      context->StyleSet()->ResolveStyleFor(body, nsnull);
-
-    nsMargin m;
-    if (styleContext->GetStyleMargin()->GetMargin(m))
-      visibleArea.Deflate(m);
-    m = styleContext->GetStyleBorder()->GetActualBorder();
-    visibleArea.Deflate(m);
-    if (styleContext->GetStylePadding()->GetPadding(m))
-      visibleArea.Deflate(m);
-
     mVisibleWidth = nsPresContext::AppUnitsToIntCSSPixels(visibleArea.width);
     mVisibleHeight = nsPresContext::AppUnitsToIntCSSPixels(visibleArea.height);
   }
 
   bool imageWasOverflowing = mImageIsOverflowing;
   mImageIsOverflowing =
     mImageWidth > mVisibleWidth || mImageHeight > mVisibleHeight;
   bool windowBecameBigEnough = imageWasOverflowing && !mImageIsOverflowing;
--- a/content/html/document/test/test_bug369370.html
+++ b/content/html/document/test/test_bug369370.html
@@ -34,18 +34,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 
         // Need to use innerWidth/innerHeight of the window
         // since the containing image is absolutely positioned,
         // causing clientHeight to be zero.
         is(kidWin.innerWidth, 400, "Checking doc width");
         is(kidWin.innerHeight, 300, "Checking doc height");
 
         // Image just loaded and is scaled to window size.
-        is(img.width,  378, "image width");
-        is(img.height, 284, "image height");
+        is(img.width,  400, "image width");
+        is(img.height, 300, "image height");
         is(kidDoc.body.scrollLeft,  0, "Checking scrollLeft");
         is(kidDoc.body.scrollTop,   0, "Checking scrollTop");
 
         // ========== test 1 ==========
         // Click in the upper left to zoom in
         var event = makeClickFor(25,25);
         img.dispatchEvent(event);
         ok(true, "----- click 1 -----");
@@ -56,18 +56,18 @@ https://bugzilla.mozilla.org/show_bug.cg
         is(kidDoc.body.scrollTop,   0, "Checking scrollTop");
 
         // ========== test 2 ==========
         // Click there again to zoom out
         event = makeClickFor(25,25);
         img.dispatchEvent(event);
         ok(true, "----- click 2 -----");
 
-        is(img.width,  378, "image width");
-        is(img.height, 284, "image height");
+        is(img.width,  400, "image width");
+        is(img.height, 300, "image height");
         is(kidDoc.body.scrollLeft,  0, "Checking scrollLeft");
         is(kidDoc.body.scrollTop,   0, "Checking scrollTop");
 
         // ========== test 3 ==========
         // Click in the lower right to zoom in
         event = makeClickFor(350, 250);
         img.dispatchEvent(event);
         ok(true, "----- click 3 -----");
@@ -78,18 +78,18 @@ https://bugzilla.mozilla.org/show_bug.cg
         is(kidDoc.body.scrollTop,   300, "Checking scrollTop");
 
         // ========== test 4 ==========
         // Click there again to zoom out
         event = makeClickFor(350, 250);
         img.dispatchEvent(event);
         ok(true, "----- click 4 -----");
 
-        is(img.width,  378, "image width");
-        is(img.height, 284, "image height");
+        is(img.width,  400, "image width");
+        is(img.height, 300, "image height");
         is(kidDoc.body.scrollLeft,  0, "Checking scrollLeft");
         is(kidDoc.body.scrollTop,   0, "Checking scrollTop");
 
         kidWin.close();
         netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
         prefs.clearUserPref("browser.enable_automatic_image_resizing");
         SimpleTest.finish();
     }
--- a/content/svg/content/src/nsSVGUseElement.cpp
+++ b/content/svg/content/src/nsSVGUseElement.cpp
@@ -423,19 +423,17 @@ nsSVGUseElement::SyncWidthOrHeight(nsIAt
     if (mLengthAttributes[index].IsExplicitlySet()) {
       target->SetLength(aName, mLengthAttributes[index]);
       return;
     }
     if (svg) {
       // Our width/height attribute is now no longer explicitly set, so we
       // need to revert the clone's width/height to the width/height of the
       // content that's being cloned.
-      nsSVGSVGElement* svgElement =
-        static_cast<nsSVGSVGElement*>(mSource.get());
-      svgElement->SyncWidthOrHeight(aName, target);
+      TriggerReclone();
       return;
     }
     // Our width/height attribute is now no longer explicitly set, so we
     // need to set the value to 100%
     nsSVGLength2 length;
     length.Init(nsSVGUtils::XY, 0xff,
                 100, nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE);
     target->SetLength(aName, length);
--- a/content/xbl/src/nsBindingManager.cpp
+++ b/content/xbl/src/nsBindingManager.cpp
@@ -36,17 +36,16 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsCOMPtr.h"
 #include "nsIXBLService.h"
 #include "nsIInputStream.h"
-#include "nsDoubleHashtable.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIChannel.h"
 #include "nsXPIDLString.h"
 #include "nsIParser.h"
 #include "nsParserCIID.h"
 #include "nsNetUtil.h"
 #include "plstr.h"
--- a/content/xslt/src/xml/txDOM.h
+++ b/content/xslt/src/xml/txDOM.h
@@ -50,17 +50,18 @@
 #define MITRE_DOM
 
 #ifdef __BORLANDC__
 #include <stdlib.h>
 #endif
 
 #include "txList.h"
 #include "nsIAtom.h"
-#include "nsDoubleHashtable.h"
+#include "nsTHashtable.h"
+#include "nsBaseHashtable.h"
 #include "nsString.h"
 #include "txCore.h"
 #include "nsAutoPtr.h"
 
 #define kTxNsNodeIndexOffset 0x00000000;
 #define kTxAttrIndexOffset 0x40000000;
 #define kTxChildIndexOffset 0x80000000;
 
@@ -224,34 +225,17 @@ class NodeDefinition : public Node
     // Helperfunction for compareDocumentOrder
     OrderInfo* getOrderInfo();
 };
 
 //
 //Definition and Implementation of a Document.
 //
 
-/**
- * nsDoubleHashtable definitions for IDs
- *
- * It may be possible to share the key value with the element,
- * but that may leave entries without keys, as the entries
- * are constructed from the key value and the setting of mElement
- * happens late. As pldhash.h ain't clear on this, we store the
- * key by inheriting from PLDHashStringEntry.
- */
-class txIDEntry : public PLDHashStringEntry
-{
-public:
-    txIDEntry(const void* aKey) : PLDHashStringEntry(aKey), mElement(nsnull)
-    {
-    }
-    Element* mElement;
-};
-DECL_DHASH_WRAPPER(txIDMap, txIDEntry, nsAString&)
+typedef nsTHashtable<nsBaseHashtableET<nsStringHashKey, Element*> > txIDMap;
 
 class Document : public NodeDefinition
 {
   public:
     Document();
 
     Element* getDocumentElement();
 
--- a/content/xslt/src/xslt/txKey.h
+++ b/content/xslt/src/xslt/txKey.h
@@ -34,17 +34,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef txKey_h__
 #define txKey_h__
 
-#include "nsDoubleHashtable.h"
+#include "nsTHashtable.h"
 #include "txNodeSet.h"
 #include "txList.h"
 #include "txXSLTPatterns.h"
 #include "txXMLUtils.h"
 
 class txPattern;
 class Expr;
 class txExecutionState;
@@ -63,31 +63,41 @@ public:
 
     txExpandedName mKeyName;
     nsString mKeyValue;
     PRInt32 mRootIdentifier;
 };
 
 struct txKeyValueHashEntry : public PLDHashEntryHdr
 {
-    txKeyValueHashEntry(const void* aKey)
-        : mKey(*static_cast<const txKeyValueHashKey*>(aKey)),
-          mNodeSet(new txNodeSet(nsnull))
-    {
-    }
+public:
+    typedef const txKeyValueHashKey& KeyType;
+    typedef const txKeyValueHashKey* KeyTypePointer;
+
+    txKeyValueHashEntry(KeyTypePointer aKey)
+        : mKey(*aKey),
+          mNodeSet(new txNodeSet(nsnull)) { }
 
-    // @see nsDoubleHashtable.h
-    bool MatchEntry(const void* aKey) const;
-    static PLDHashNumber HashKey(const void* aKey);
+    txKeyValueHashEntry(const txKeyValueHashEntry& entry)
+        : mKey(entry.mKey),
+          mNodeSet(entry.mNodeSet) { }
+
+    bool KeyEquals(KeyTypePointer aKey) const;
+
+    static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+    static PLDHashNumber HashKey(KeyTypePointer aKey);
+
+    enum { ALLOW_MEMMOVE = true };
     
     txKeyValueHashKey mKey;
     nsRefPtr<txNodeSet> mNodeSet;
 };
 
-DECL_DHASH_WRAPPER(txKeyValueHash, txKeyValueHashEntry, txKeyValueHashKey&)
+typedef nsTHashtable<txKeyValueHashEntry> txKeyValueHash;
 
 class txIndexedKeyHashKey
 {
 public:
     txIndexedKeyHashKey(txExpandedName aKeyName,
                         PRInt32 aRootIdentifier)
         : mKeyName(aKeyName),
           mRootIdentifier(aRootIdentifier)
@@ -95,32 +105,41 @@ public:
     }
 
     txExpandedName mKeyName;
     PRInt32 mRootIdentifier;
 };
 
 struct txIndexedKeyHashEntry : public PLDHashEntryHdr
 {
-    txIndexedKeyHashEntry(const void* aKey)
-        : mKey(*static_cast<const txIndexedKeyHashKey*>(aKey)),
-          mIndexed(false)
-    {
-    }
+public:
+    typedef const txIndexedKeyHashKey& KeyType;
+    typedef const txIndexedKeyHashKey* KeyTypePointer;
+
+    txIndexedKeyHashEntry(KeyTypePointer aKey)
+        : mKey(*aKey),
+          mIndexed(false) { }
 
-    // @see nsDoubleHashtable.h
-    bool MatchEntry(const void* aKey) const;
-    static PLDHashNumber HashKey(const void* aKey);
+    txIndexedKeyHashEntry(const txIndexedKeyHashEntry& entry)
+        : mKey(entry.mKey),
+          mIndexed(entry.mIndexed) { }
+
+    bool KeyEquals(KeyTypePointer aKey) const;
+
+    static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+    static PLDHashNumber HashKey(KeyTypePointer aKey);
+
+    enum { ALLOW_MEMMOVE = true };
 
     txIndexedKeyHashKey mKey;
     bool mIndexed;
 };
 
-DECL_DHASH_WRAPPER(txIndexedKeyHash, txIndexedKeyHashEntry,
-                   txIndexedKeyHashKey&)
+typedef nsTHashtable<txIndexedKeyHashEntry> txIndexedKeyHash;
 
 /**
  * Class holding all <xsl:key>s of a particular expanded name in the
  * stylesheet.
  */
 class txXSLKey {
     
 public:
--- a/content/xslt/src/xslt/txKeyFunctionCall.cpp
+++ b/content/xslt/src/xslt/txKeyFunctionCall.cpp
@@ -147,78 +147,60 @@ txKeyFunctionCall::getNameAtom(nsIAtom**
     return NS_OK;
 }
 #endif
 
 /**
  * Hash functions
  */
 
-DHASH_WRAPPER(txKeyValueHash, txKeyValueHashEntry, txKeyValueHashKey&)
-DHASH_WRAPPER(txIndexedKeyHash, txIndexedKeyHashEntry, txIndexedKeyHashKey&)
-
 bool
-txKeyValueHashEntry::MatchEntry(const void* aKey) const
+txKeyValueHashEntry::KeyEquals(KeyTypePointer aKey) const
 {
-    const txKeyValueHashKey* key =
-        static_cast<const txKeyValueHashKey*>(aKey);
-
-    return mKey.mKeyName == key->mKeyName &&
-           mKey.mRootIdentifier == key->mRootIdentifier &&
-           mKey.mKeyValue.Equals(key->mKeyValue);
+    return mKey.mKeyName == aKey->mKeyName &&
+           mKey.mRootIdentifier == aKey->mRootIdentifier &&
+           mKey.mKeyValue.Equals(aKey->mKeyValue);
 }
 
 PLDHashNumber
-txKeyValueHashEntry::HashKey(const void* aKey)
+txKeyValueHashEntry::HashKey(KeyTypePointer aKey)
 {
-    const txKeyValueHashKey* key =
-        static_cast<const txKeyValueHashKey*>(aKey);
-
-    return key->mKeyName.mNamespaceID ^
-           NS_PTR_TO_INT32(key->mKeyName.mLocalName.get()) ^
-           key->mRootIdentifier ^
-           HashString(key->mKeyValue);
+    return aKey->mKeyName.mNamespaceID ^
+           NS_PTR_TO_INT32(aKey->mKeyName.mLocalName.get()) ^
+           aKey->mRootIdentifier ^
+           HashString(aKey->mKeyValue);
 }
 
 bool
-txIndexedKeyHashEntry::MatchEntry(const void* aKey) const
+txIndexedKeyHashEntry::KeyEquals(KeyTypePointer aKey) const
 {
-    const txIndexedKeyHashKey* key =
-        static_cast<const txIndexedKeyHashKey*>(aKey);
-
-    return mKey.mKeyName == key->mKeyName &&
-           mKey.mRootIdentifier == key->mRootIdentifier;
+    return mKey.mKeyName == aKey->mKeyName &&
+           mKey.mRootIdentifier == aKey->mRootIdentifier;
 }
 
 PLDHashNumber
-txIndexedKeyHashEntry::HashKey(const void* aKey)
+txIndexedKeyHashEntry::HashKey(KeyTypePointer aKey)
 {
-    const txIndexedKeyHashKey* key =
-        static_cast<const txIndexedKeyHashKey*>(aKey);
-
-    return key->mKeyName.mNamespaceID ^
-           NS_PTR_TO_INT32(key->mKeyName.mLocalName.get()) ^
-           key->mRootIdentifier;
+    return aKey->mKeyName.mNamespaceID ^
+           NS_PTR_TO_INT32(aKey->mKeyName.mLocalName.get()) ^
+           aKey->mRootIdentifier;
 }
 
 /*
  * Class managing XSLT-keys
  */
 
 nsresult
 txKeyHash::getKeyNodes(const txExpandedName& aKeyName,
                        const txXPathNode& aRoot,
                        const nsAString& aKeyValue,
                        bool aIndexIfNotFound,
                        txExecutionState& aEs,
                        txNodeSet** aResult)
 {
-    NS_ENSURE_TRUE(mKeyValues.mHashTable.ops && mIndexedKeys.mHashTable.ops,
-                   NS_ERROR_OUT_OF_MEMORY);
-
     *aResult = nsnull;
 
     PRInt32 identifier = txXPathNodeUtils::getUniqueIdentifier(aRoot);
 
     txKeyValueHashKey valueKey(aKeyName, identifier, aKeyValue);
     txKeyValueHashEntry* valueEntry = mKeyValues.GetEntry(valueKey);
     if (valueEntry) {
         *aResult = valueEntry->mNodeSet;
@@ -236,17 +218,17 @@ txKeyHash::getKeyNodes(const txExpandedN
         // indexed, so don't bother investigating.
         *aResult = mEmptyNodeSet;
         NS_ADDREF(*aResult);
 
         return NS_OK;
     }
 
     txIndexedKeyHashKey indexKey(aKeyName, identifier);
-    txIndexedKeyHashEntry* indexEntry = mIndexedKeys.AddEntry(indexKey);
+    txIndexedKeyHashEntry* indexEntry = mIndexedKeys.PutEntry(indexKey);
     NS_ENSURE_TRUE(indexEntry, NS_ERROR_OUT_OF_MEMORY);
 
     if (indexEntry->mIndexed) {
         // The key was indexed and apparently didn't contain this value so
         // return the empty nodeset.
         *aResult = mEmptyNodeSet;
         NS_ADDREF(*aResult);
 
@@ -407,32 +389,32 @@ nsresult txXSLKey::testNode(const txXPat
                                             (static_cast<txAExprResult*>
                                                         (exprResult));
                 PRInt32 i;
                 for (i = 0; i < res->size(); ++i) {
                     val.Truncate();
                     txXPathNodeUtils::appendNodeValue(res->get(i), val);
 
                     aKey.mKeyValue.Assign(val);
-                    txKeyValueHashEntry* entry = aKeyValueHash.AddEntry(aKey);
+                    txKeyValueHashEntry* entry = aKeyValueHash.PutEntry(aKey);
                     NS_ENSURE_TRUE(entry && entry->mNodeSet,
                                    NS_ERROR_OUT_OF_MEMORY);
 
                     if (entry->mNodeSet->isEmpty() ||
                         entry->mNodeSet->get(entry->mNodeSet->size() - 1) !=
                         aNode) {
                         entry->mNodeSet->append(aNode);
                     }
                 }
             }
             else {
                 exprResult->stringValue(val);
 
                 aKey.mKeyValue.Assign(val);
-                txKeyValueHashEntry* entry = aKeyValueHash.AddEntry(aKey);
+                txKeyValueHashEntry* entry = aKeyValueHash.PutEntry(aKey);
                 NS_ENSURE_TRUE(entry && entry->mNodeSet,
                                NS_ERROR_OUT_OF_MEMORY);
 
                 if (entry->mNodeSet->isEmpty() ||
                     entry->mNodeSet->get(entry->mNodeSet->size() - 1) !=
                     aNode) {
                     entry->mNodeSet->append(aNode);
                 }
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -61,20 +61,22 @@ EXTRA_JS_MODULES = ConsoleAPIStorage.jsm
 
 ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 EXTRA_COMPONENTS = \
 	        Webapps.js \
 	        Webapps.manifest \
 		$(NULL)
 
 EXTRA_JS_MODULES += Webapps.jsm \
-        DOMRequestHelper.jsm \
 		$(NULL)
 endif
 
+EXTRA_JS_MODULES += DOMRequestHelper.jsm \
+		$(NULL)
+
 XPIDLSRCS = \
   nsIDOMDOMError.idl \
   nsIDOMDOMRequest.idl \
   nsIEntropyCollector.idl \
   nsIScriptChannel.idl \
   $(NULL)
 
 EXPORTS = \
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -65,16 +65,18 @@
 #include "nsIJSContextStack.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "BatteryManager.h"
 #include "PowerManager.h"
+#include "nsIDOMWakeLock.h"
+#include "nsIPowerManagerService.h"
 #include "SmsManager.h"
 #include "nsISmsService.h"
 #include "mozilla/Hal.h"
 #include "nsIWebNavigation.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "Connection.h"
 
 #ifdef MOZ_B2G_RIL
@@ -167,17 +169,20 @@ Navigator::Invalidate()
     mNotification = nsnull;
   }
 
   if (mBatteryManager) {
     mBatteryManager->Shutdown();
     mBatteryManager = nsnull;
   }
 
-  mPowerManager = nsnull;
+  if (mPowerManager) {
+    mPowerManager->Shutdown();
+    mPowerManager = nsnull;
+  }
 
   if (mSmsManager) {
     mSmsManager->Shutdown();
     mSmsManager = nsnull;
   }
 
 #ifdef MOZ_B2G_RIL
   if (mTelephony) {
@@ -953,25 +958,48 @@ Navigator::GetMozBattery(nsIDOMMozBatter
   NS_ADDREF(*aBattery = mBatteryManager);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Navigator::GetMozPower(nsIDOMMozPowerManager** aPower)
 {
+  *aPower = nsnull;
+
   if (!mPowerManager) {
+    nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mWindow);
+    NS_ENSURE_TRUE(win, NS_OK);
+
     mPowerManager = new power::PowerManager();
+    mPowerManager->Init(win);
   }
 
-  NS_ADDREF(*aPower = mPowerManager);
+  nsCOMPtr<nsIDOMMozPowerManager> power =
+    do_QueryInterface(NS_ISUPPORTS_CAST(nsIDOMMozPowerManager*, mPowerManager));
+  power.forget(aPower);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+Navigator::RequestWakeLock(const nsAString &aTopic, nsIDOMMozWakeLock **aWakeLock)
+{
+  *aWakeLock = nsnull;
+
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mWindow);
+  NS_ENSURE_TRUE(win, NS_OK);
+
+  nsCOMPtr<nsIPowerManagerService> pmService =
+    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(pmService, NS_OK);
+
+  return pmService->NewWakeLock(aTopic, win, aWakeLock);
+}
+
 //*****************************************************************************
 //    Navigator::nsIDOMNavigatorSms
 //*****************************************************************************
 
 bool
 Navigator::IsSmsAllowed() const
 {
   static const bool defaultSmsPermission = false;
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -502,16 +502,17 @@ using mozilla::dom::indexedDB::IDBWrappe
 #include "nsIDOMCustomEvent.h"
 
 #include "nsWrapperCacheInlines.h"
 #include "dombindings.h"
 
 #include "nsIDOMBatteryManager.h"
 #include "BatteryManager.h"
 #include "nsIDOMPowerManager.h"
+#include "nsIDOMWakeLock.h"
 #include "nsIDOMSmsManager.h"
 #include "nsIDOMSmsMessage.h"
 #include "nsIDOMSmsEvent.h"
 #include "nsIDOMSmsRequest.h"
 #include "nsIDOMSmsFilter.h"
 #include "nsIDOMSmsCursor.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIDOMConnection.h"
@@ -1431,16 +1432,19 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozBatteryManager, 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)
 
   NS_DEFINE_CLASSINFO_DATA(MozSmsMessage, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozSmsEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
@@ -4026,16 +4030,20 @@ nsDOMClassInfo::Init()
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozBatteryManager)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   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
+
   DOM_CLASSINFO_MAP_BEGIN(MozSmsManager, nsIDOMMozSmsManager)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsManager)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozSmsMessage, nsIDOMMozSmsMessage)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsMessage)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -424,16 +424,17 @@ DOMCI_CLASS(GeoGeolocation)
 DOMCI_CLASS(GeoPosition)
 DOMCI_CLASS(GeoPositionCoords)
 DOMCI_CLASS(GeoPositionAddress)
 DOMCI_CLASS(GeoPositionError)
 
 DOMCI_CLASS(MozBatteryManager)
 
 DOMCI_CLASS(MozPowerManager)
+DOMCI_CLASS(MozWakeLock)
 
 DOMCI_CLASS(MozSmsManager)
 DOMCI_CLASS(MozSmsMessage)
 DOMCI_CLASS(MozSmsEvent)
 DOMCI_CLASS(MozSmsRequest)
 DOMCI_CLASS(MozSmsFilter)
 DOMCI_CLASS(MozSmsCursor)
 
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -850,17 +850,17 @@ nsFocusManager::ContentRemoved(nsIDocume
         nsCOMPtr<nsISupports> container = subdoc->GetContainer();
         nsCOMPtr<nsPIDOMWindow> childWindow = do_GetInterface(container);
         if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
           ClearFocus(mActiveWindow);
         }
       }
     }
 
-    NotifyFocusStateChange(aContent, shouldShowFocusRing, false);
+    NotifyFocusStateChange(content, shouldShowFocusRing, false);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFocusManager::WindowShown(nsIDOMWindow* aWindow, bool aNeedsFocus)
 {
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -12,16 +12,25 @@ else
   debug = function (s) {}
 
 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");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+XPCOMUtils.defineLazyGetter(Services, "rs", function() {
+  return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService);
+});
+
+XPCOMUtils.defineLazyGetter(this, "cpmm", function() {
+  return Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+});
 
 const nsIClassInfo            = Ci.nsIClassInfo;
 const CONTACTPROPERTIES_CID   = Components.ID("{53ed7c20-ceda-11e0-9572-0800200c9a66}");
 const nsIDOMContactProperties = Ci.nsIDOMContactProperties;
 
 // ContactProperties is not directly instantiated. It is used as interface.
 
 ContactProperties.prototype = {
@@ -179,16 +188,17 @@ const CONTACTMANAGER_CID        = Compon
 const nsIDOMContactManager      = Components.interfaces.nsIDOMContactManager;
 
 function ContactManager()
 {
   debug("Constructor");
 }
 
 ContactManager.prototype = {
+  __proto__: DOMRequestIpcHelper.prototype,
 
   save: function save(aContact) {
     let request;
     if (this.hasPrivileges) {
       debug("save: " + JSON.stringify(aContact) + " :" + aContact.id);
       let newContact = {};
       newContact.properties = {
         name:            [],
@@ -217,31 +227,31 @@ ContactManager.prototype = {
 
       if (aContact.id == "undefined") {
         debug("Create id!");
         aContact.id = this._getRandomId();
       }
 
       this._setMetaData(newContact, aContact);
       debug("send: " + JSON.stringify(newContact));
-      request = this._rs.createRequest(this._window);
-      this._mm.sendAsyncMessage("Contact:Save", {contact: newContact,
-                                                 requestID: this.getRequestId({ request: request })});
+      request = this.createRequest();
+      cpmm.sendAsyncMessage("Contact:Save", {contact: newContact,
+                                             requestID: this.getRequestId(request)});
       return request;
     } else {
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
   },
 
   remove: function removeContact(aRecord) {
     let request;
     if (this.hasPrivileges) {
-      request = this._rs.createRequest(this._window);
-      this._mm.sendAsyncMessage("Contact:Remove", {id: aRecord.id,
-                                                   requestID: this.getRequestId({ request: request })});
+      request = this.createRequest();
+      cpmm.sendAsyncMessage("Contact:Remove", {id: aRecord.id,
+                                               requestID: this.getRequestId(request)});
       return request;
     } else {
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
   },
 
   _setMetaData: function(aNewContact, aRecord) {
     aNewContact.id = aRecord.id;
@@ -255,147 +265,102 @@ ContactManager.prototype = {
       let newContact = new Contact();
       newContact.init(aContacts[i].properties);
       this._setMetaData(newContact, aContacts[i]);
       contacts.push(newContact);
     }
     return contacts;
   },
 
-  getRequestId: function(aRequest) {
-    let id = "id" + this._getRandomId();
-    this._requests[id] = aRequest;
-    return id;
-  },
-
-  getRequest: function(aId) {
-    if (this._requests[aId])
-      return this._requests[aId].request;
-  },
-
-  removeRequest: function(aId) {
-    if (this._requests[aId])
-      delete this._requests[aId];
-  },
-  
-  _getRandomId: function() {
-    return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
-  },
-
   receiveMessage: function(aMessage) {
     debug("Contactmanager::receiveMessage: " + aMessage.name);
     let msg = aMessage.json;
     let contacts = msg.contacts;
 
     switch (aMessage.name) {
       case "Contacts:Find:Return:OK":
         let req = this.getRequest(msg.requestID);
         if (req) {
           let result = this._convertContactsArray(contacts);
           debug("result: " + JSON.stringify(result));
-          this._rs.fireSuccess(req, result);
+          Services.rs.fireSuccess(req, result);
         } else {
           debug("no request stored!" + msg.requestID);
         }
         break;
       case "Contact:Save:Return:OK":
       case "Contacts:Clear:Return:OK":
       case "Contact:Remove:Return:OK":
         req = this.getRequest(msg.requestID);
         if (req)
-          this._rs.fireSuccess(req, 0);
+          Services.rs.fireSuccess(req, null);
         break;
       case "Contacts:Find:Return:KO":
       case "Contact:Save:Return:KO":
       case "Contact:Remove:Return:KO":
       case "Contacts:Clear:Return:KO":
         req = this.getRequest(msg.requestID);
         if (req)
-          this._rs.fireError(req, msg.errorMsg);
+          Services.rs.fireError(req, msg.errorMsg);
         break;
       default: 
         debug("Wrong message: " + aMessage.name);
     }
     this.removeRequest(msg.requestID);
   },
 
   find: function(aOptions) {
     let request;
     if (this.hasPrivileges) {
-      request = this._rs.createRequest(this._window);
-      this._mm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions, 
-                                                  requestID: this.getRequestId({ request: request })});
+      request = this.createRequest();
+      cpmm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions, 
+                                              requestID: this.getRequestId(request)});
       return request;
     } else {
       debug("find not allowed");
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
   },
 
   clear: function() {
     let request;
     if (this.hasPrivileges) {
-      request = this._rs.createRequest(this._window);
-      this._mm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({ request: request })});
+      request = this.createRequest();
+      cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId(request)});
       return request;
     } else {
       debug("clear not allowed");
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
   },
 
   init: function(aWindow) {
     // Set navigator.mozContacts to null.
     if (!Services.prefs.getBoolPref("dom.mozContacts.enabled"))
       return null;
 
-    this._window = aWindow;
-    this._messages = ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
+    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:Remove:Return:OK", "Contact:Remove:Return:KO"]);
 
-    this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
-    this._messages.forEach((function(msgName) {
-      this._mm.addMessageListener(msgName, this);
-    }).bind(this));
-
-    this._rs = Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService);
-    this._requests = [];
     Services.obs.addObserver(this, "inner-window-destroyed", false);
-    let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-    this._innerWindowID = util.currentInnerWindowID;
 
     let principal = aWindow.document.nodePrincipal;
     let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
 
     let perm = principal == secMan.getSystemPrincipal() ? 
                  Ci.nsIPermissionManager.ALLOW_ACTION : 
                  Services.perms.testExactPermission(principal.URI, "webcontacts-manage");
  
     //only pages with perm set can use the contacts
     this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
     debug("has privileges :" + this.hasPrivileges);
   },
 
-  observe: function(aSubject, aTopic, aData) {
-    let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
-    if (wId == this.innerWindowID) {
-      Services.obs.removeObserver(this, "inner-window-destroyed");
-      this._messages.forEach((function(msgName) {
-        this._mm.removeMessageListener(msgName, this);
-      }).bind(this));
-      this._mm = null;
-      this._messages = null;
-      this._requests = null;
-      this._window = null;
-      this._innerWindowID = null;
-    }
-  },
-
   classID : CONTACTMANAGER_CID,
   QueryInterface : XPCOMUtils.generateQI([nsIDOMContactManager, Ci.nsIDOMGlobalPropertyInitializer]),
 
   classInfo : XPCOMUtils.generateCI({classID: CONTACTMANAGER_CID,
                                      contractID: CONTACTMANAGER_CONTRACTID,
                                      classDescription: "ContactManager",
                                      interfaces: [nsIDOMContactManager],
                                      flags: nsIClassInfo.DOM_OBJECT})
--- a/dom/contacts/fallback/ContactService.jsm
+++ b/dom/contacts/fallback/ContactService.jsm
@@ -15,26 +15,28 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 let EXPORTED_SYMBOLS = ["DOMContactManager"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/ContactDB.jsm");
 
+XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
+  return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+});
+
 let myGlobal = this;
 
 let DOMContactManager = {
-
   init: function() {
     debug("Init");
-    this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
     this._messages = ["Contacts:Find", "Contacts:Clear", "Contact:Save", "Contact:Remove"];
     this._messages.forEach((function(msgName) {
-      this._mm.addMessageListener(msgName, this);
+      ppmm.addMessageListener(msgName, this);
     }).bind(this));
 
     var idbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager);
     idbManager.initWindowless(myGlobal);
     this._db = new ContactDB(myGlobal);
 
     Services.obs.addObserver(this, "profile-before-change", false);
 
@@ -47,50 +49,50 @@ let DOMContactManager = {
                              Ci.nsIPermissionManager.ALLOW_ACTION);
       });
     } catch(e) { debug(e); }
   },
 
   observe: function(aSubject, aTopic, aData) {
     myGlobal = null;
     this._messages.forEach((function(msgName) {
-      this._mm.removeMessageListener(msgName, this);
+      ppmm.removeMessageListener(msgName, this);
     }).bind(this));
     Services.obs.removeObserver(this, "profile-before-change");
-    this._mm = null;
+    ppmm = null;
     this._messages = null;
     if (this._db)
       this._db.close();
   },
 
   receiveMessage: function(aMessage) {
     debug("Fallback DOMContactManager::receiveMessage " + aMessage.name);
     let msg = aMessage.json;
     switch (aMessage.name) {
       case "Contacts:Find":
         let result = new Array();
         this._db.find(
           function(contacts) {
             for (let i in contacts)
               result.push(contacts[i]);
             debug("result:" + JSON.stringify(result));
-            this._mm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result});
+            ppmm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result});
           }.bind(this),
-          function(aErrorMsg) { this._mm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this), 
+          function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this), 
           msg.findOptions);
         break;
       case "Contact:Save":
-        this._db.saveContact(msg.contact, function() {this._mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID }); }.bind(this), 
-                             function(aErrorMsg) { this._mm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
+        this._db.saveContact(msg.contact, function() { ppmm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID }); }.bind(this), 
+                             function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
         break;
       case "Contact:Remove":
         this._db.removeContact(msg.id, 
-                               function() {this._mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID }); }.bind(this), 
-                               function(aErrorMsg) {this._mm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
+                               function() { ppmm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID }); }.bind(this), 
+                               function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
         break;
       case "Contacts:Clear":
-        this._db.clear(function() { this._mm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
-                       function(aErrorMsg) { this._mm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
+        this._db.clear(function() { ppmm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
+                       function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this));
     }
   }
 }
 
 DOMContactManager.init();
--- a/dom/interfaces/base/domstubs.idl
+++ b/dom/interfaces/base/domstubs.idl
@@ -126,8 +126,9 @@ interface nsIDOMCrypto;
 interface nsIDOMPkcs11;
 
 // Used font face (for inspector)
 interface nsIDOMFontFace;
 interface nsIDOMFontFaceList;
 
 // Power
 interface nsIDOMMozPowerManager;
+interface nsIDOMMozWakeLock;
--- a/dom/interfaces/base/nsIDOMNavigator.idl
+++ b/dom/interfaces/base/nsIDOMNavigator.idl
@@ -34,17 +34,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "domstubs.idl"
 
-[scriptable, uuid(b1f4b1fa-49c2-4375-9ce8-bf97ecf6b428)]
+[scriptable, uuid(e610c037-db58-4cd7-8ed3-0d7f1422b4d3)]
 interface nsIDOMNavigator : nsISupports
 {
   readonly attribute DOMString             appCodeName;
   readonly attribute DOMString             appName;
   readonly attribute DOMString             appVersion;
   readonly attribute DOMString             language;
   readonly attribute nsIDOMMimeTypeArray   mimeTypes;
   readonly attribute DOMString             platform;
@@ -105,9 +105,39 @@ interface nsIDOMNavigator : nsISupports
    *   pass an even number of elements (that is, if your list ends with b_n
    *   instead of a_n), the final element doesn't specify anything meaningful.
    *
    *   We may throw NS_ERROR_DOM_NOT_SUPPORTED_ERR if the vibration pattern is
    *   too long, or if any of its elements is too large.
    */
   [implicit_jscontext]
   void mozVibrate(in jsval aPattern);
+
+  /**
+   * Request a wake lock for a resource.
+   *
+   * A page holds a wake lock to request that a resource not be turned
+   * off (or otherwise made unavailable).
+   *
+   * The topic is the name of a resource that might be made unavailable for
+   * various reasons. For example, on a mobile device the power manager might
+   * decide to turn off the screen after a period of idle time to save power.
+   *
+   * The resource manager checks the lock state of a topic before turning off
+   * the associated resource. For example, a page could hold a lock on the
+   * "screen" topic to prevent the screensaver from appearing or the screen
+   * from turning off.
+   *
+   * The resource manager defines what each topic means and sets policy.  For
+   * example, the resource manager might decide to ignore 'screen' wake locks
+   * held by pages which are not visible.
+   *
+   * One topic can be locked multiple times; it is considered released only when
+   * all locks on the topic have been released.
+   *
+   * The returned nsIDOMMozWakeLock object is a token of the lock.  You can
+   * unlock the lock via the object's |unlock| method.  The lock is released
+   * automatically when its associated window is unloaded.
+   *
+   * @param aTopic resource name
+   */
+  nsIDOMMozWakeLock requestWakeLock(in DOMString aTopic);
 };
--- a/dom/power/Makefile.in
+++ b/dom/power/Makefile.in
@@ -47,25 +47,29 @@ LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/dom/dom-config.mk
 
 EXPORTS_NAMESPACES = mozilla/dom/power
 
 EXPORTS_mozilla/dom/power = \
   PowerManagerService.h \
+  Types.h \
   $(NULL)
 
 CPPSRCS = \
   PowerManager.cpp \
   PowerManagerService.cpp \
+  WakeLock.cpp \
   $(NULL)
 
 XPIDLSRCS = \
   nsIDOMPowerManager.idl \
+  nsIDOMWakeLock.idl \
+  nsIDOMWakeLockListener.idl \
   nsIPowerManagerService.idl \
   $(NULL)
 
 ifdef ENABLE_TESTS
 DIRS += test
 endif
 
 include $(topsrcdir)/config/config.mk
--- a/dom/power/PowerManager.cpp
+++ b/dom/power/PowerManager.cpp
@@ -31,59 +31,166 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "PowerManager.h"
+#include "WakeLock.h"
 #include "nsContentUtils.h"
 #include "nsDOMClassInfoID.h"
+#include "nsIDOMWakeLockListener.h"
 #include "nsIPowerManagerService.h"
+#include "nsIPrincipal.h"
+#include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 
 DOMCI_DATA(MozPowerManager, mozilla::dom::power::PowerManager)
 
 namespace mozilla {
 namespace dom {
 namespace power {
 
 NS_INTERFACE_MAP_BEGIN(PowerManager)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozPowerManager)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMozPowerManager)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozWakeLockListener)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozPowerManager)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(PowerManager)
 NS_IMPL_RELEASE(PowerManager)
 
+nsresult
+PowerManager::Init(nsIDOMWindow *aWindow)
+{
+  mWindow = do_GetWeakReference(aWindow);
+
+  nsCOMPtr<nsIPowerManagerService> pmService =
+    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+  NS_ENSURE_STATE(pmService);
+
+  // Add ourself to the global notification list.
+  pmService->AddWakeLockListener(this);
+  return NS_OK;
+}
+
+nsresult
+PowerManager::Shutdown()
+{
+  nsCOMPtr<nsIPowerManagerService> pmService =
+    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+  NS_ENSURE_STATE(pmService);
+
+  // Remove ourself from the global notification list.
+  pmService->RemoveWakeLockListener(this);
+  return NS_OK;
+}
+
+nsresult
+PowerManager::CheckPermission()
+{
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mWindow);
+  NS_ENSURE_STATE(win);
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(win->GetExtantDocument());
+  NS_ENSURE_STATE(doc);
+
+  nsCOMPtr<nsIURI> uri;
+  doc->NodePrincipal()->GetURI(getter_AddRefs(uri));
+
+  if (!nsContentUtils::URIIsChromeOrInPref(uri, "dom.power.whitelist")) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 PowerManager::Reboot()
 {
-  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
+  nsresult rv = CheckPermission();
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIPowerManagerService> pmService =
     do_GetService(POWERMANAGERSERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(pmService, NS_OK);
+  NS_ENSURE_STATE(pmService);
 
   pmService->Reboot();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PowerManager::PowerOff()
 {
-  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
+  nsresult rv = CheckPermission();
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIPowerManagerService> pmService =
     do_GetService(POWERMANAGERSERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(pmService, NS_OK);
+  NS_ENSURE_STATE(pmService);
 
   pmService->PowerOff();
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PowerManager::AddWakeLockListener(nsIDOMMozWakeLockListener *aListener)
+{
+  nsresult rv = CheckPermission();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // already added? bail out.
+  if (mListeners.Contains(aListener))
+    return NS_OK;
+
+  mListeners.AppendElement(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PowerManager::RemoveWakeLockListener(nsIDOMMozWakeLockListener *aListener)
+{
+  nsresult rv = CheckPermission();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mListeners.RemoveElement(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PowerManager::GetWakeLockState(const nsAString &aTopic, nsAString &aState)
+{
+  nsresult rv = CheckPermission();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIPowerManagerService> pmService =
+    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+  NS_ENSURE_STATE(pmService);
+
+  return pmService->GetWakeLockState(aTopic, aState);
+}
+
+NS_IMETHODIMP
+PowerManager::Callback(const nsAString &aTopic, const nsAString &aState)
+{
+  /**
+   * We maintain a local listener list instead of using the global
+   * list so that when the window is destroyed we don't have to
+   * cleanup the mess.
+   * Copy the listeners list before we walk through the callbacks
+   * because the callbacks may install new listeners. We expect no
+   * more than one listener per window, so it shouldn't be too long.
+   */
+  nsAutoTArray<nsCOMPtr<nsIDOMMozWakeLockListener>, 2> listeners(mListeners);
+  for (PRUint32 i = 0; i < listeners.Length(); ++i) {
+    listeners[i]->Callback(aTopic, aState);
+  }
+
+  return NS_OK;
+}
+
 } // power
 } // dom
 } // mozilla
--- a/dom/power/PowerManager.h
+++ b/dom/power/PowerManager.h
@@ -32,30 +32,46 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #ifndef mozilla_dom_power_PowerManager_h
 #define mozilla_dom_power_PowerManager_h
 
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
 #include "nsIDOMPowerManager.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIDOMWindow.h"
+#include "nsWeakReference.h"
 
 namespace mozilla {
 namespace dom {
 namespace power {
 
 class PowerManager
   : public nsIDOMMozPowerManager
+  , public nsIDOMMozWakeLockListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMMOZPOWERMANAGER
+  NS_DECL_NSIDOMMOZWAKELOCKLISTENER
 
   PowerManager() {};
   virtual ~PowerManager() {};
+
+  nsresult Init(nsIDOMWindow *aWindow);
+  nsresult Shutdown();
+
+private:
+  nsresult CheckPermission();
+
+  nsWeakPtr mWindow;
+  nsTArray<nsCOMPtr<nsIDOMMozWakeLockListener> > mListeners;
 };
 
 } // namespace power
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_power_PowerManager_h
--- a/dom/power/PowerManagerService.cpp
+++ b/dom/power/PowerManagerService.cpp
@@ -31,43 +31,145 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "mozilla/Hal.h"
+#include "mozilla/HalWakeLock.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIDOMWindow.h"
 #include "PowerManagerService.h"
+#include "WakeLock.h"
 
 namespace mozilla {
 namespace dom {
 namespace power {
 
 NS_IMPL_ISUPPORTS1(PowerManagerService, nsIPowerManagerService)
 
+/* static */ nsRefPtr<PowerManagerService> PowerManagerService::sSingleton;
+
 /* static */ already_AddRefed<nsIPowerManagerService>
 PowerManagerService::GetInstance()
 {
-  nsCOMPtr<nsIPowerManagerService> pmService;
+  if (!sSingleton) {
+    sSingleton = new PowerManagerService();
+    sSingleton->Init();
+    ClearOnShutdown(&sSingleton);
+  }
+
+  nsCOMPtr<nsIPowerManagerService> service(do_QueryInterface(sSingleton));
+  return service.forget();
+}
+
+void
+PowerManagerService::Init()
+{
+  hal::RegisterWakeLockObserver(this);
+}
+
+PowerManagerService::~PowerManagerService()
+{
+  hal::UnregisterWakeLockObserver(this);
+}
 
-  pmService = new PowerManagerService();
+void
+PowerManagerService::ComputeWakeLockState(const hal::WakeLockInformation& aWakeLockInfo,
+                                          nsAString &aState)
+{
+  hal::WakeLockState state = hal::ComputeWakeLockState(aWakeLockInfo.numLocks(),
+                                                       aWakeLockInfo.numHidden());
+  switch (state) {
+  case hal::WAKE_LOCK_STATE_UNLOCKED:
+    aState.AssignLiteral("unlocked");
+    break;
+  case hal::WAKE_LOCK_STATE_HIDDEN:
+    aState.AssignLiteral("locked-background");
+    break;
+  case hal::WAKE_LOCK_STATE_VISIBLE:
+    aState.AssignLiteral("locked-foreground");
+    break;
+  }
+}
 
-  return pmService.forget();
+void
+PowerManagerService::Notify(const hal::WakeLockInformation& aWakeLockInfo)
+{
+  nsAutoString state;
+  ComputeWakeLockState(aWakeLockInfo, state);
+
+  /**
+   * Copy the listeners list before we walk through the callbacks
+   * because the callbacks may install new listeners. We expect no
+   * more than one listener per window, so it shouldn't be too long.
+   */
+  nsAutoTArray<nsCOMPtr<nsIDOMMozWakeLockListener>, 2> listeners(mWakeLockListeners);
+
+  for (PRUint32 i = 0; i < listeners.Length(); ++i) {
+    listeners[i]->Callback(aWakeLockInfo.topic(), state);
+  }
 }
 
 NS_IMETHODIMP
 PowerManagerService::Reboot()
 {
   hal::Reboot();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PowerManagerService::PowerOff()
 {
   hal::PowerOff();
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PowerManagerService::AddWakeLockListener(nsIDOMMozWakeLockListener *aListener)
+{
+  if (mWakeLockListeners.Contains(aListener))
+    return NS_OK;
+
+  mWakeLockListeners.AppendElement(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PowerManagerService::RemoveWakeLockListener(nsIDOMMozWakeLockListener *aListener)
+{
+  mWakeLockListeners.RemoveElement(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PowerManagerService::GetWakeLockState(const nsAString &aTopic, nsAString &aState)
+{
+  hal::WakeLockInformation info;
+  hal::GetWakeLockInfo(aTopic, &info);
+
+  ComputeWakeLockState(info, aState);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PowerManagerService::NewWakeLock(const nsAString &aTopic,
+                                 nsIDOMWindow *aWindow,
+                                 nsIDOMMozWakeLock **aWakeLock)
+{
+  nsRefPtr<WakeLock> wakelock = new WakeLock();
+  nsresult rv = wakelock->Init(aTopic, aWindow);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIDOMMozWakeLock> wl =
+    do_QueryInterface(NS_ISUPPORTS_CAST(nsIDOMMozWakeLock*, wakelock));
+  wl.forget(aWakeLock);
+
+  return NS_OK;
+}
+
 } // power
 } // dom
 } // mozilla
--- a/dom/power/PowerManagerService.h
+++ b/dom/power/PowerManagerService.h
@@ -32,30 +32,52 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #ifndef mozilla_dom_power_PowerManagerService_h
 #define mozilla_dom_power_PowerManagerService_h
 
+#include "nsCOMPtr.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
 #include "nsIPowerManagerService.h"
-#include "nsCOMPtr.h" // for already_AddRefed
+#include "mozilla/Observer.h"
+#include "Types.h"
 
 namespace mozilla {
 namespace dom {
 namespace power {
 
 class PowerManagerService
   : public nsIPowerManagerService
+  , public WakeLockObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPOWERMANAGERSERVICE
 
   static already_AddRefed<nsIPowerManagerService> GetInstance();
+
+  void Init();
+
+  // Implement WakeLockObserver
+  void Notify(const hal::WakeLockInformation& aWakeLockInfo);
+
+private:
+
+  ~PowerManagerService();
+
+  void ComputeWakeLockState(const hal::WakeLockInformation& aWakeLockInfo,
+                            nsAString &aState);
+
+  static nsRefPtr<PowerManagerService> sSingleton;
+
+  nsTArray<nsCOMPtr<nsIDOMMozWakeLockListener> > mWakeLockListeners;
 };
 
 } // namespace power
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_power_PowerManagerService_h
new file mode 100644
--- /dev/null
+++ b/dom/power/Types.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#ifndef mozilla_dom_power_Types_h
+#define mozilla_dom_power_Types_h
+
+namespace mozilla {
+namespace hal {
+class WakeLockInformation;
+} // namespace hal
+
+template <class T>
+class Observer;
+
+typedef Observer<hal::WakeLockInformation> WakeLockObserver;
+
+} // namespace mozilla
+
+#endif // mozilla_dom_power_Types_h
+
new file mode 100644
--- /dev/null
+++ b/dom/power/WakeLock.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/Hal.h"
+#include "mozilla/HalWakeLock.h"
+#include "nsDOMClassInfoID.h"
+#include "nsDOMError.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMEventTarget.h"
+#include "nsPIDOMWindow.h"
+#include "PowerManager.h"
+#include "WakeLock.h"
+
+DOMCI_DATA(MozWakeLock, mozilla::dom::power::WakeLock)
+
+namespace mozilla {
+namespace dom {
+namespace power {
+
+NS_INTERFACE_MAP_BEGIN(WakeLock)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozWakeLock)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMozWakeLock)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozWakeLock)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WakeLock)
+NS_IMPL_RELEASE(WakeLock)
+
+WakeLock::WakeLock()
+  : mLocked(false)
+  , mHidden(true)
+{
+}
+
+WakeLock::~WakeLock()
+{
+  DoUnlock();
+  DetachEventListener();
+}
+
+nsresult
+WakeLock::Init(const nsAString &aTopic, nsIDOMWindow *aWindow)
+{
+  mTopic.Assign(aTopic);
+
+  mWindow = do_GetWeakReference(aWindow);
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
+
+  /**
+   * Null windows are allowed. A wake lock without associated window
+   * is always considered invisible.
+   */
+  if (window) {
+    nsCOMPtr<nsIDOMDocument> domDoc = window->GetExtantDocument();
+    NS_ENSURE_STATE(domDoc);
+    domDoc->GetMozHidden(&mHidden);
+  }
+
+  AttachEventListener();
+  DoLock();
+
+  return NS_OK;
+}
+
+void
+WakeLock::DoLock()
+{
+  if (!mLocked) {
+    // Change the flag immediately to prevent recursive reentering
+    mLocked = true;
+    hal::ModifyWakeLock(mTopic,
+                        hal::WAKE_LOCK_ADD_ONE,
+                        mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_NO_CHANGE);
+  }
+}
+
+void
+WakeLock::DoUnlock()
+{
+  if (mLocked) {
+    // Change the flag immediately to prevent recursive reentering
+    mLocked = false;
+    hal::ModifyWakeLock(mTopic,
+                        hal::WAKE_LOCK_REMOVE_ONE,
+                        mHidden ? hal::WAKE_LOCK_REMOVE_ONE : hal::WAKE_LOCK_NO_CHANGE);
+  }
+}
+
+void
+WakeLock::AttachEventListener()
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  
+  if (window) {
+    nsCOMPtr<nsIDOMDocument> domDoc = window->GetExtantDocument();
+    if (domDoc) {
+      nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(domDoc);
+      target->AddSystemEventListener(NS_LITERAL_STRING("mozvisibilitychange"),
+                                     this,
+                                     /* useCapture = */ true,
+                                     /* wantsUntrusted = */ false);
+
+      target = do_QueryInterface(window);
+      target->AddSystemEventListener(NS_LITERAL_STRING("pagehide"),
+                                     this,
+                                     /* useCapture = */ true,
+                                     /* wantsUntrusted = */ false);
+      target->AddSystemEventListener(NS_LITERAL_STRING("pageshow"),
+                                     this,
+                                     /* useCapture = */ true,
+                                     /* wantsUntrusted = */ false);
+    }
+  }
+}
+
+void
+WakeLock::DetachEventListener()
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+
+  if (window) {
+    nsCOMPtr<nsIDOMDocument> domDoc = window->GetExtantDocument();
+    if (domDoc) {
+      nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(domDoc);
+      target->RemoveSystemEventListener(NS_LITERAL_STRING("mozvisibilitychange"),
+                                        this,
+                                        /* useCapture = */ true);
+      target = do_QueryInterface(window);
+      target->RemoveSystemEventListener(NS_LITERAL_STRING("pagehide"),
+                                        this,
+                                        /* useCapture = */ true);
+      target->RemoveSystemEventListener(NS_LITERAL_STRING("pageshow"),
+                                        this,
+                                        /* useCapture = */ true);
+    }
+  }
+}
+
+NS_IMETHODIMP
+WakeLock::Unlock()
+{
+  /*
+   * We throw NS_ERROR_DOM_INVALID_STATE_ERR on double unlock.
+   */
+  if (!mLocked) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  DoUnlock();
+  DetachEventListener();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WakeLock::GetTopic(nsAString &aTopic)
+{
+  aTopic.Assign(mTopic);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WakeLock::HandleEvent(nsIDOMEvent *aEvent)
+{
+  nsAutoString type;
+  aEvent->GetType(type);
+
+  if (type.EqualsLiteral("mozvisibilitychange")) {
+    nsCOMPtr<nsIDOMEventTarget> target;
+    aEvent->GetTarget(getter_AddRefs(target));
+    nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(target);
+    NS_ENSURE_STATE(domDoc);
+    domDoc->GetMozHidden(&mHidden);
+
+    if (mLocked) {
+      hal::ModifyWakeLock(mTopic,
+                          hal::WAKE_LOCK_NO_CHANGE,
+                          mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_REMOVE_ONE);
+    }
+
+    return NS_OK;
+  }
+
+  if (type.EqualsLiteral("pagehide")) {
+    DoUnlock();
+    return NS_OK;
+  }
+
+  if (type.EqualsLiteral("pageshow")) {
+    DoLock();
+    return NS_OK;
+  }
+
+  return NS_OK;
+}
+
+} // power
+} // dom
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/dom/power/WakeLock.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_power_WakeLock_h
+#define mozilla_dom_power_WakeLock_h
+
+#include "nsCOMPtr.h"
+#include "nsIDOMWakeLock.h"
+#include "nsIDOMEventListener.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+
+class nsIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+namespace power {
+
+class WakeLock
+  : public nsIDOMMozWakeLock
+  , public nsIDOMEventListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMMOZWAKELOCK
+  NS_DECL_NSIDOMEVENTLISTENER
+
+  WakeLock();
+  virtual ~WakeLock();
+
+  nsresult Init(const nsAString &aTopic, nsIDOMWindow *aWindow);
+
+private:
+  void     DoUnlock();
+  void     DoLock();
+  void     AttachEventListener();
+  void     DetachEventListener();
+
+  bool      mLocked;
+  bool      mHidden;
+  nsString  mTopic;
+
+  // window that this was created for.  Weak reference.
+  nsWeakPtr mWindow;
+};
+
+} // namespace power
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_power_WakeLock_h
--- a/dom/power/nsIDOMPowerManager.idl
+++ b/dom/power/nsIDOMPowerManager.idl
@@ -32,14 +32,45 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(6ec16abc-2fe8-4ab3-99b0-0f08405be81b)]
+interface nsIDOMMozWakeLockListener;
+
+/**
+ * This interface implements navigator.mozPower
+ */
+[scriptable, uuid(abf4b2b1-139d-4eff-998d-8f24616910ae)]
 interface nsIDOMMozPowerManager : nsISupports
 {
-    void powerOff();
-    void reboot();
+    void    powerOff();
+    void    reboot();
+
+    /**
+     * The listeners are notified when a resource changes its lock state to:
+     *  - unlocked
+     *  - locked but not visible
+     *  - locked and visible
+     */
+    void    addWakeLockListener(in nsIDOMMozWakeLockListener aListener);
+    void    removeWakeLockListener(in nsIDOMMozWakeLockListener aListener);
+
+    /**
+     * Query the wake lock state of the topic.
+     *
+     * Possible states are:
+     *
+     *  - "unlocked" - nobody holds the wake lock.
+     *
+     *  - "locked-foreground" - at least one window holds the wake lock,
+     *    and it is visible.
+     *
+     *  - "locked-background" - at least one window holds the wake lock,
+     *    but all of them are hidden.
+     *
+     * @param aTopic The resource name related to the wake lock.
+     */
+    DOMString getWakeLockState(in DOMString aTopic);
 };
new file mode 100644
--- /dev/null
+++ b/dom/power/nsIDOMWakeLock.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(2e61eed1-5983-4562-8f26-fd361ab4a00d)]
+interface nsIDOMMozWakeLock : nsISupports
+{
+    readonly attribute DOMString topic;
+
+    /**
+     * Release the wake lock.
+     *
+     * @throw NS_ERROR_DOM_INVALID_STATE_ERR if already unlocked.
+     */
+    void unlock();
+};
new file mode 100644
--- /dev/null
+++ b/dom/power/nsIDOMWakeLockListener.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, function, uuid(4e258af8-cffb-47bc-b16d-e8241243426e)]
+interface nsIDOMMozWakeLockListener : nsISupports
+{
+  /**
+   * The callback will be called when a lock topic changes its lock
+   * state.
+   *
+   * Possible states are:
+   *
+   *  - "unlocked" - nobody holds the wake lock.
+   *
+   *  - "locked-foreground" - at least one window holds the wake lock,
+   *    and it is visible.
+   *
+   *  - "locked-background" - at least one window holds the wake lock,
+   *    but all of them are hidden.
+   *
+   * @param aTopic The resource name related to the wake lock.
+   * @param aState The wake lock state
+   */
+  void callback(in DOMString aTopic, in DOMString aState);
+};
--- a/dom/power/nsIPowerManagerService.idl
+++ b/dom/power/nsIPowerManagerService.idl
@@ -37,14 +37,31 @@
 
 #include "nsISupports.idl"
 
 %{C++
 #define NS_POWERMANAGERSERVICE_CID { 0x18c2e238, 0x3a0a, 0x4153, {0x89, 0xfc, 0x16, 0x6b, 0x3b, 0x14, 0x65, 0xa1 } }
 #define POWERMANAGERSERVICE_CONTRACTID "@mozilla.org/power/powermanagerservice;1"
 %}
 
-[scriptable, builtinclass, uuid(38919539-4641-4f0b-9f11-6b6294a9386f)]
+interface nsIDOMMozWakeLock;
+interface nsIDOMMozWakeLockListener;
+interface nsIDOMWindow;
+
+/**
+ * For use with non-content code.
+ */
+[scriptable, builtinclass, uuid(235ca1a1-d0c8-41f3-9b4a-dbaa4437d69c)]
 interface nsIPowerManagerService : nsISupports
 {
-    void powerOff();
-    void reboot();
+  void              powerOff();
+  void              reboot();
+  void              addWakeLockListener(in nsIDOMMozWakeLockListener aListener);
+  void              removeWakeLockListener(in nsIDOMMozWakeLockListener aListener);
+  DOMString         getWakeLockState(in DOMString aTopic);
+
+  /**
+   * Return a wake lock object of aTopic associated with aWindow.
+   * A wake lock without associated window, e.g. used in chrome, is
+   * always considered invisible.
+   */
+  nsIDOMMozWakeLock newWakeLock(in DOMString aTopic, [optional] in nsIDOMWindow aWindow);
 };
--- a/dom/power/test/Makefile.in
+++ b/dom/power/test/Makefile.in
@@ -47,13 +47,20 @@ DIRS = \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
   test_power_basics.html \
   $(NULL)
 
+_BROWSER_TEST_FILES = \
+  browser_bug697132.js \
+  $(NULL)
+
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
+libs:: $(_BROWSER_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
+
 #libs:: $(_CHROME_TEST_FILES)
 #	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/power/test/browser_bug697132.js
@@ -0,0 +1,235 @@
+"use strict";
+
+waitForExplicitFinish();
+
+let kPrefNode = "dom.power.whitelist";
+let kPageSource1 = "data:text/html,1";
+let kPageSource2 = "data:text/html,2";
+
+let gOldPref;
+let gWin, gWin1, gWin2;
+let gTab, gTab1, gTab2;
+let gLock, gLock1, gLock2;
+let gCurStepIndex = -1;
+let gSteps = [
+  function basicWakeLock() {
+    gTab = gBrowser.addTab(kPageSource1);
+    gWin = gBrowser.getBrowserForTab(gTab).contentWindow;
+    let browser = gBrowser.getBrowserForTab(gTab);
+
+    browser.addEventListener("load", function onLoad(e) {
+      browser.removeEventListener("load", onLoad, true);
+      let nav = gWin.navigator;
+      let power = nav.mozPower;
+      gLock = nav.requestWakeLock("test");
+
+      ok(gLock != null,
+         "navigator.requestWakeLock should return a wake lock");
+      is(gLock.topic, "test",
+         "wake lock should remember the locked topic");
+      isnot(power.getWakeLockState("test"), "unlocked",
+            "topic is locked");
+
+      gLock.unlock();
+
+      is(gLock.topic, "test",
+         "wake lock should remember the locked topic even after unlock");
+      is(power.getWakeLockState("test"), "unlocked",
+         "topic is unlocked");
+
+      try {
+        gLock.unlock();
+        ok(false, "Should have thrown an error.");
+      } catch (e) {
+        is(e.code, DOMException.INVALID_STATE_ERR, "double unlock should throw InvalidStateError");
+      }
+
+      gBrowser.removeTab(gTab);
+
+      executeSoon(runNextStep);
+    }, true);
+  },
+  function multiWakeLock() {
+    gTab = gBrowser.addTab(kPageSource1);
+    gWin = gBrowser.getBrowserForTab(gTab).contentWindow;
+    let browser = gBrowser.getBrowserForTab(gTab);
+
+    browser.addEventListener("load", function onLoad(e) {
+      browser.removeEventListener("load", onLoad, true);
+      let nav = gWin.navigator;
+      let power = nav.mozPower;
+      let count = 0;
+      power.addWakeLockListener(function onWakeLockEvent(topic, state) {
+        is(topic, "test", "gLock topic is test");
+        ok(state == "unlocked" ||
+           state == "locked-foreground" ||
+           state == "locked-background",
+           "wake lock should be either locked or unlocked");
+        count++;
+        if (state == "locked-foreground" ||
+            state == "locked-background") {
+          is(count, 1,
+             "wake lock should be locked and the listener should only fire once");
+        }
+        if (state == "unlocked") {
+          is(count, 2,
+             "wake lock should be unlocked and the listener should only fire once");
+
+          ok(power.getWakeLockState("test") == "unlocked",
+             "topic is unlocked");
+          power.removeWakeLockListener(onWakeLockEvent);
+          gBrowser.removeTab(gTab);
+          executeSoon(runNextStep);
+        }
+      });
+
+      gLock1 = nav.requestWakeLock("test");
+      isnot(power.getWakeLockState("test"), "unlocked",
+            "topic is locked");
+
+      gLock2 = nav.requestWakeLock("test");
+      isnot(power.getWakeLockState("test"), "unlocked",
+            "topic is locked");
+
+      gLock1.unlock();
+      isnot(power.getWakeLockState("test"), "unlocked",
+            "topic is locked");
+
+      gLock2.unlock();
+    }, true);
+  },
+  function crossTabWakeLock1() {
+    gTab1 = gBrowser.addTab(kPageSource1);
+    gWin1 = gBrowser.getBrowserForTab(gTab1).contentWindow;
+    gTab2 = gBrowser.addTab(kPageSource1);
+    gWin2 = gBrowser.getBrowserForTab(gTab2).contentWindow;
+
+    gBrowser.selectedTab = gTab1;
+    let browser = gBrowser.getBrowserForTab(gTab2);
+
+    browser.addEventListener("load", function onLoad(e) {
+      browser.removeEventListener("load", onLoad, true);
+      gLock2 = gWin2.navigator.requestWakeLock("test");
+      is(gWin2.document.mozHidden, true,
+         "window is background")
+      is(gWin2.navigator.mozPower.getWakeLockState("test"), "locked-background",
+         "wake lock is background");
+      let doc2 = gWin2.document;
+      doc2.addEventListener("mozvisibilitychange", function onVisibilityChange(e) {
+        if (!doc2.mozHidden) {
+          doc2.removeEventListener("mozvisibilitychange", onVisibilityChange);
+          executeSoon(runNextStep);
+        }
+      });
+      gBrowser.selectedTab = gTab2;
+    }, true);
+  },
+  function crossTabWakeLock2() {
+    is(gWin2.document.mozHidden, false,
+       "window is foreground")
+    is(gWin2.navigator.mozPower.getWakeLockState("test"), "locked-foreground",
+      "wake lock is foreground");
+    gWin2.addEventListener("pagehide", function onPageHide(e) {
+      gWin2.removeEventListener("pagehide", onPageHide, true);
+      executeSoon(runNextStep);
+    }, true);
+    gWin2.addEventListener("pageshow", function onPageShow(e) {
+      gWin2.removeEventListener("pageshow", onPageShow, true);
+      executeSoon(runNextStep);
+    }, true);
+    gWin2.location = kPageSource2;
+  },
+  function crossTabWakeLock3() {
+    is(gWin1.navigator.mozPower.getWakeLockState("test"), "unlocked",
+       "wake lock should auto-unlock when page is unloaded");
+    gWin2.back();
+    // runNextStep called in onPageShow
+  },
+  function crossTabWakeLock4() {
+    is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-foreground",
+       "wake lock should auto-reacquire when page is available again");
+    gBrowser.selectedTab = gTab1;
+    executeSoon(runNextStep);
+  },
+  function crossTabWakeLock5() {
+    // Test again in background tab
+    is(gWin2.document.mozHidden, true,
+       "window is background")
+    is(gWin2.navigator.mozPower.getWakeLockState("test"), "locked-background",
+      "wake lock is background");
+    gWin2.addEventListener("pagehide", function onPageHide(e) {
+      gWin2.removeEventListener("pagehide", onPageHide, true);
+      executeSoon(runNextStep);
+    }, true);
+    gWin2.addEventListener("pageshow", function onPageShow(e) {
+      gWin2.removeEventListener("pageshow", onPageShow, true);
+      executeSoon(runNextStep);
+    }, true);
+    gWin2.location = kPageSource2;
+  },
+  function crossTabWakeLock6() {
+    is(gWin1.navigator.mozPower.getWakeLockState("test"), "unlocked",
+       "wake lock should auto-unlock when page is unloaded");
+    gWin2.back();
+    // runNextStep called in onPageShow
+  },
+  function crossTabWakeLock7() {
+    is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-background",
+       "wake lock should auto-reacquire when page is available again");
+    gLock2.unlock();
+    gBrowser.selectedTab = gTab2;
+    executeSoon(runNextStep);
+  },
+  function crossTabWakeLock8() {
+    is(gWin1.document.mozHidden, true,
+       "gWin1 is background");
+    is(gWin2.document.mozHidden, false,
+       "gWin2 is foreground");
+
+    gLock1 = gWin1.navigator.requestWakeLock("test");
+    gLock2 = gWin2.navigator.requestWakeLock("test");
+
+    is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-foreground",
+       "topic is locked-foreground when one page is foreground and one is background");
+
+    gLock2.unlock();
+
+    is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-background",
+       "topic is locked-background when all locks are background");
+
+    gLock2 = gWin2.navigator.requestWakeLock("test");
+
+    is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-foreground",
+       "topic is locked-foreground when one page is foreground and one is background");
+
+    gLock1.unlock();
+
+    is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-foreground",
+       "topic is locked-foreground");
+
+    gBrowser.removeTab(gTab1);
+    gBrowser.removeTab(gTab2);
+    executeSoon(runNextStep);
+  },
+];
+
+function runNextStep() {
+  gCurStepIndex++;
+  if (gCurStepIndex < gSteps.length) {
+    gSteps[gCurStepIndex]();
+  } else {
+    Services.prefs.setCharPref(kPrefNode, gOldPref);
+    finish();
+  }
+}
+
+function test() {
+  try {
+    gOldPref = Services.prefs.getCharPref(kPrefNode);
+  } catch (e) {
+    gOldPref = "";
+  }
+  // data url inherits its parent's principal, which is |about:| here.
+  Services.prefs.setCharPref(kPrefNode, "about:");
+  runNextStep();
+}
--- a/dom/system/b2g/ril_consts.js
+++ b/dom/system/b2g/ril_consts.js
@@ -173,16 +173,33 @@ const UNSOLICITED_RESTRICTED_STATE_CHANG
 const UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE = 1024;
 const UNSOLICITED_CDMA_CALL_WAITING = 1025;
 const UNSOLICITED_CDMA_OTA_PROVISION_STATUS = 1026;
 const UNSOLICITED_CDMA_INFO_REC = 1027;
 const UNSOLICITED_OEM_HOOK_RAW = 1028;
 const UNSOLICITED_RINGBACK_TONE = 1029;
 const UNSOLICITED_RESEND_INCALL_MUTE = 1030;
 
+const ERROR_SUCCESS = 0;
+const ERROR_RADIO_NOT_AVAILABLE = 1;
+const ERROR_GENERIC_FAILURE = 2;
+const ERROR_PASSWORD_INCORRECT = 3;
+const ERROR_SIM_PIN2 = 4;
+const ERROR_SIM_PUK2 = 5;
+const ERROR_REQUEST_NOT_SUPPORTED = 6;
+const ERROR_CANCELLED = 7;
+const ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL = 8;
+const ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW = 9;
+const ERROR_SMS_SEND_FAIL_RETRY = 10;
+const ERROR_SIM_ABSENT = 11;
+const ERROR_SUBSCRIPTION_NOT_AVAILABLE = 12;
+const ERROR_MODE_NOT_SUPPORTED = 13;
+const ERROR_FDN_CHECK_FAILURE = 14;
+const ERROR_ILLEGAL_SIM_OR_ME = 15;
+
 const RADIO_STATE_OFF = 0;
 const RADIO_STATE_UNAVAILABLE = 1;
 const RADIO_STATE_SIM_NOT_READY = 2;
 const RADIO_STATE_SIM_LOCKED_OR_ABSENT = 3;
 const RADIO_STATE_SIM_READY = 4;
 const RADIO_STATE_RUIM_NOT_READY = 5;
 const RADIO_STATE_RUIM_READY = 6;
 const RADIO_STATE_RUIM_LOCKED_OR_ABSENT = 7;
@@ -238,16 +255,90 @@ const TOA_UNKNOWN = 0x81;
 
 const CALL_PRESENTATION_ALLOWED = 0;
 const CALL_PRESENTATION_RESTRICTED = 1;
 const CALL_PRESENTATION_UNKNOWN = 2;
 const CALL_PRESENTATION_PAYPHONE = 3;
 
 const SMS_HANDLED = 0;
 
+// ICC commands, see TS 27.007 +CRSM commands
+const ICC_COMMAND_READ_BINARY = 0xb0;
+const ICC_COMMAND_UPDATE_BINARY = 0xd6;
+const ICC_COMMAND_READ_RECORD = 0xb2;
+const ICC_COMMAND_UPDATE_RECORD = 0xdc;
+const ICC_COMMAND_SEEK = 0xa2;
+const ICC_COMMAND_GET_RESPONSE = 0xc0;
+
+// ICC constants, GSM SIM file ids from TS 51.011
+const ICC_EF_ADN = 0x6F3A;
+const ICC_EF_FDN = 0x6F3B;
+const ICC_EF_SDN = 0x6F49;
+const ICC_EF_EXT1 = 0x6F4A;
+const ICC_EF_EXT2 = 0x6F4B;
+const ICC_EF_EXT3 = 0x6F4C;
+const ICC_EF_EXT6 = 0x6fc8;   // Ext record for EF[MBDN]
+const ICC_EF_MWIS = 0x6FCA;
+const ICC_EF_MBDN = 0x6fc7;
+const ICC_EF_PNN = 0x6fc5;
+const ICC_EF_SPN = 0x6F46;
+const ICC_EF_SMS = 0x6F3C;
+const ICC_EF_ICCID = 0x2fe2;
+const ICC_EF_AD = 0x6FAD;
+const ICC_EF_MBI = 0x6fc9;
+const ICC_EF_MSISDN = 0x6f40;
+const ICC_EF_SPDI = 0x6fcd;
+const ICC_EF_SST = 0x6f38;
+const ICC_EF_CFIS = 0x6FCB;
+const ICC_EF_IMG = 0x4f20;
+
+// Types of files  TS 11.11 9.3
+const TYPE_RFU = 0;
+const TYPE_MF  = 1;
+const TYPE_DF  = 2;
+const TYPE_EF  = 4;
+
+const RESPONSE_DATA_FILE_ID_1 = 4;
+const RESPONSE_DATA_FILE_ID_2 = 5;
+const RESPONSE_DATA_FILE_TYPE = 6;
+const RESPONSE_DATA_RFU_3 = 7;
+const RESPONSE_DATA_ACCESS_CONDITION_1 = 8;
+const RESPONSE_DATA_ACCESS_CONDITION_2 = 9;
+const RESPONSE_DATA_ACCESS_CONDITION_3 = 10;
+const RESPONSE_DATA_FILE_STATUS = 11;
+const RESPONSE_DATA_LENGTH = 12;
+const RESPONSE_DATA_STRUCTURE = 13;
+const RESPONSE_DATA_RECORD_LENGTH = 14;
+
+// Types of files  TS 11.11 9.3
+const EF_TYPE_TRANSPARENT = 0;
+const EF_TYPE_LINEAR_FIXED = 1;
+const EF_TYPE_CYCLIC = 3;
+
+// For retriveing MSISDN
+const FOOTER_SIZE_BYTES = 14;
+const MAX_NUMBER_SIZE_BYTES = 11;
+
+// READ_RECORD mode,  TS 102.221
+const READ_RECORD_ABSOLUTE_MODE = 4;
+
+// GET_RESPONSE mandatory response size for EF, see TS 51.011 clause 9, 
+// 'Response data in case of an EF.'
+const GET_RESPONSE_EF_SIZE_BYTES = 15;
+
+// EF path
+const EF_PATH_MF_SIM = "3f00";
+const EF_PATH_DF_TELECOM = "7f10";
+
+// Status code for ICC I/O, 
+// see GSM11.11 and TS 51.011 clause 9.4.
+const STATUS_NORMAL_ENDING = 0x90;
+const STATUS_NORMAL_ENDING_WITH_EXTRA = 0x91;
+const STATUS_WITH_SIM_DATA = 0x9e;
+const STATUS_WITH_RESPONSE_DATA = 0x9f;
 
 /**
  * GSM PDU constants
  */
 
 // PDU TYPE-OF-ADDRESS
 const PDU_TOA_UNKNOWN       = 0x80; // Unknown. This is used when the user or
                                     // network has no a priori information
--- a/dom/system/b2g/ril_worker.js
+++ b/dom/system/b2g/ril_worker.js
@@ -458,22 +458,24 @@ let Buf = {
 
     let request_type, options;
     if (response_type == RESPONSE_TYPE_SOLICITED) {
       let token = this.readUint32();
       let error = this.readUint32();
 
       options = this.tokenRequestMap[token];
       request_type = options.rilRequestType;
-      if (error) {
-        //TODO
+
+      options.rilRequestError = error;
+      if (error) {   	  
         if (DEBUG) {
           debug("Received error " + error + " for solicited parcel type " +
                 request_type);
         }
+        RIL.handleRequestError(options);
         return;
       }
       if (DEBUG) {
         debug("Solicited response for request type " + request_type +
               ", token " + token);
       }
       delete this.tokenRequestMap[token];
       this.lastSolicitedToken = token;
@@ -504,16 +506,17 @@ let Buf = {
     this.writeUint32(type);
     let token = this.token;
     this.writeUint32(token);
 
     if (!options) {
       options = {};
     }
     options.rilRequestType = type;
+    options.rilRequestError = null;
     this.tokenRequestMap[token] = options;
     this.token++;
     return token;
   },
 
   /**
    * Communicate with the RIL IPC thread.
    */
@@ -904,16 +907,51 @@ let RIL = {
     Buf.writeString(user);
     Buf.writeString(passwd);
     Buf.writeString(chappap.toString());
     Buf.writeString(pdptype);
     Buf.sendParcel();
     return token;
   },
 
+   /**
+   *  Request an ICC I/O operation.
+   * 
+   *  See TS 27.007 "restricted SIM" operation, "AT Command +CRSM".
+   *  The sequence is in the same order as how libril reads this parcel,
+   *  see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h
+   *
+   *  @param command 
+   *         The I/O command, one of the ICC_COMMAND_* constants.
+   *  @param fileid
+   *         The file to operate on, one of the ICC_EF_* constants.
+   *  @param pathid
+   *         String type, check pathid from TS 27.007 +CRSM  
+   *  @param p1, p2, p3
+   *         Arbitrary integer parameters for the command.
+   *  @param data
+   *         String parameter for the command.
+   *  @param pin2 [optional]
+   *         String containing the PIN2.
+   */
+  iccIO: function iccIO (options) {
+    let token = Buf.newParcel(REQUEST_SIM_IO, options);
+    Buf.writeUint32(options.command);
+    Buf.writeUint32(options.fileid);
+    Buf.writeString(options.path);
+    Buf.writeUint32(options.p1);
+    Buf.writeUint32(options.p2);
+    Buf.writeUint32(options.p3);
+    Buf.writeString(options.data);
+    if (request.pin2 != null) {
+      Buf.writeString(pin2);
+    }
+    Buf.sendParcel();
+  },
+  
   /**
    * Deactivate a data call.
    *
    * @param cid
    *        String containing CID.
    * @param reason
    *        One of DATACALL_DEACTIVATE_* constants.
    */
@@ -934,16 +972,24 @@ let RIL = {
   },
 
   /**
    * Get failure casue code for the most recently failed PDP context.
    */
   getFailCauseCode: function getFailCauseCode() {
     Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE);
   },
+  
+  /**
+   * Handle the RIL request errors
+   */ 
+  handleRequestError: function handleRequestError(options) {	  
+	options.type = "error";
+	Phone.sendDOMMessage(options);
+  },   
 
   /**
    * Handle incoming requests from the RIL. We find the method that
    * corresponds to the request type. Incidentally, the request type
    * _is_ the method name, so that's easy.
    */
 
   handleParcel: function handleParcel(request_type, length, options) {
@@ -1109,17 +1155,19 @@ RIL[REQUEST_SEND_SMS] = function REQUEST
   options.errorCode = Buf.readUint32();
   Phone.onSendSMS(options);
 };
 RIL[REQUEST_SEND_SMS_EXPECT_MORE] = null;
 RIL[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL() {
   let [cid, ifname, ipaddr, dns, gw] = Buf.readStringList();
   Phone.onSetupDataCall(Buf.lastSolicitedToken, cid, ifname, ipaddr, dns, gw);
 };
-RIL[REQUEST_SIM_IO] = null;
+RIL[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) {
+  Phone.onICCIO(options);
+};
 RIL[REQUEST_SEND_USSD] = null;
 RIL[REQUEST_CANCEL_USSD] = null;
 RIL[REQUEST_GET_CLIR] = null;
 RIL[REQUEST_SET_CLIR] = null;
 RIL[REQUEST_QUERY_CALL_FORWARD_STATUS] = null;
 RIL[REQUEST_SET_CALL_FORWARD] = null;
 RIL[REQUEST_QUERY_CALL_WAITING] = null;
 RIL[REQUEST_SET_CALL_WAITING] = null;
@@ -1322,16 +1370,17 @@ let Phone = {
 
   /**
    * Strings
    */
   IMEI: null,
   IMEISV: null,
   IMSI: null,
   SMSC: null,
+  MSISDN: null,
 
   registrationState: {},
   gprsRegistrationState: {},
 
   /**
    * List of strings identifying the network operator.
    */
   operator: null,
@@ -1468,16 +1517,17 @@ let Phone = {
     if (newState == RADIO_STATE_SIM_READY  ||
         newState == RADIO_STATE_RUIM_READY ||
         newState == RADIO_STATE_NV_READY) {
       // The ICC has become available. Get all the things.
       RIL.getICCStatus();
       this.requestNetworkInfo();
       RIL.getSignalStrength();
       RIL.getSMSCAddress();
+      this.getMSISDN();
       this.sendDOMMessage({type: "cardstatechange",
                            cardState: GECKO_CARDSTATE_READY});
     }
     if (newState == RADIO_STATE_SIM_LOCKED_OR_ABSENT  ||
         newState == RADIO_STATE_RUIM_LOCKED_OR_ABSENT) {
       RIL.getICCStatus();
       this.sendDOMMessage({type: "cardstatechange",
                            cardState: GECKO_CARDSTATE_UNAVAILABLE});
@@ -1671,16 +1721,62 @@ let Phone = {
   onIMEI: function onIMEI(imei) {
     this.IMEI = imei;
   },
 
   onIMEISV: function onIMEISV(imeiSV) {
     this.IMEISV = imeiSV;
   },
 
+  onICCIO: function onICCIO(options) {
+    switch (options.fileid) {
+      case ICC_EF_MSISDN:
+        this.readMSISDNResponse(options);
+        break;
+    }
+  },
+  
+  readMSISDNResponse: function readMSISDNResponse(options) {
+    let sw1 = Buf.readUint32();
+    let sw2 = Buf.readUint32();
+    // See GSM11.11 section 9.4 for sw1 and sw2
+    if (sw1 != STATUS_NORMAL_ENDING) {
+      // TODO: error 
+      // Wait for fix for Bug 713451 to report error.
+      debug("Error in iccIO");
+    }
+    if (DEBUG) debug("ICC I/O (" + sw1 + "/" + sw2 + ")");
+
+    switch (options.command) {
+      case ICC_COMMAND_GET_RESPONSE:
+        let response = Buf.readString();
+        let recordSize = parseInt(
+            response.substr(RESPONSE_DATA_RECORD_LENGTH * 2, 2), 16) & 0xff;
+        let request = {
+          command: ICC_COMMAND_READ_RECORD,
+          fileid:  ICC_EF_MSISDN,
+          pathid:  EF_PATH_MF_SIM + EF_PATH_DF_TELECOM,
+          p1:      1, // Record number, MSISDN is always in the 1st record
+          p2:      READ_RECORD_ABSOLUTE_MODE,
+          p3:      recordSize,
+          data:    null,
+          pin2:    null,
+        };
+        RIL.iccIO(request);
+        break;
+
+      case ICC_COMMAND_READ_RECORD:
+        // Ignore 2 bytes prefix, which is 4 chars
+        let number = GsmPDUHelper.readStringAsBCD().toString().substr(4); 
+        if (DEBUG) debug("MSISDN: " + number);
+        this.MSISDN = number;
+        break;
+    } 
+  },
+
   onRegistrationState: function onRegistrationState(state) {
     let rs = this.registrationState;
     let stateChanged = false;
 
     let regState = RIL.parseInt(state[0], NETWORK_CREG_STATE_UNKNOWN);
     if (rs.regState != regState) {
       rs.regState = regState;
       stateChanged = true;
@@ -2127,16 +2223,33 @@ let Phone = {
   /**
    * Get failure cause code for the last failed PDP context.
    */
   getFailCauseCode: function getFailCauseCode(options) {
     RIL.getFailCauseCode();
   },
 
   /**
+   *  Get MSISDN
+   */ 
+  getMSISDN: function getMSISDN() {
+    let request = {
+      command: ICC_COMMAND_GET_RESPONSE,
+      fileid:  ICC_EF_MSISDN,
+      pathid:  EF_PATH_MF_SIM + EF_PATH_DF_TELECOM,
+      p1:      0, // For GET_RESPONSE, p1 = 0
+      p2:      0, // For GET_RESPONSE, p2 = 0
+      p3:      GET_RESPONSE_EF_SIZE_BYTES,
+      data:    null,
+      pin2:    null,
+    };
+    RIL.iccIO(request);
+  },
+
+  /**
    * Handle incoming messages from the main UI thread.
    *
    * @param message
    *        Object containing the message. Messages are supposed
    */
   handleDOMMessage: function handleMessage(message) {
     if (DEBUG) debug("Received DOM message " + JSON.stringify(message));
     let method = this[message.type];
@@ -2254,30 +2367,47 @@ let GsmPDUHelper = {
    *        Number of nibble *pairs* to read.
    *
    * @return the decimal as a number.
    */
   readSwappedNibbleBCD: function readSwappedNibbleBCD(length) {
     let number = 0;
     for (let i = 0; i < length; i++) {
       let octet = this.readHexOctet();
+      if (octet == 0xff)
+        continue;
       // If the first nibble is an "F" , only the second nibble is to be taken
       // into account.
       if ((octet & 0xf0) == 0xf0) {
         number *= 10;
         number += octet & 0x0f;
         continue;
       }
       number *= 100;
       number += this.octetToBCD(octet);
     }
     return number;
   },
 
   /**
+   *  Read a string from Buf and convert it to BCD
+   * 
+   *  @return the decimal as a number.
+   */ 
+  readStringAsBCD: function readStringAsBCD() {
+    let length = Buf.readUint32();
+    let bcd = this.readSwappedNibbleBCD(length / 2);
+    let delimiter = Buf.readUint16();
+    if (!(length & 1)) {
+      delimiter |= Buf.readUint16();
+    }
+    return bcd;
+  },
+
+  /**
    * Write numerical data as swapped nibble BCD.
    *
    * @param data
    *        Data to write (as a string or a number)
    */
   writeSwappedNibbleBCD: function writeSwappedNibbleBCD(data) {
     data = data.toString();
     if (data.length % 2) {
@@ -2358,23 +2488,42 @@ let GsmPDUHelper = {
             // character and therefore must not be used for language specific
             // characters."
             ret += " ";
           } else {
             ret += langShiftTable[septet];
           }
         } else if (septet == PDU_NL_EXTENDED_ESCAPE) {
           escapeFound = true;
+
+          // <escape> is not an effective character
+          --length;
         } else {
           ret += langTable[septet];
         }
       }
     } while (byteLength);
 
     if (ret.length != length) {
+      /**
+       * If num of effective characters does not equal to the length of read
+       * string, cut the tail off. This happens when the last octet of user
+       * data has following layout:
+       *
+       * |<-              penultimate octet in user data               ->|
+       * |<-               data septet N               ->|<-   dsN-1   ->|
+       * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+       *
+       * |<-                  last octet in user data                  ->|
+       * |<-                       fill bits                   ->|<-dsN->|
+       * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+       *
+       * The fill bits in the last octet may happen to form a full septet and
+       * be appended at the end of result string.
+       */
       ret = ret.slice(0, length);
     }
     return ret;
   },
 
   writeStringAsSeptets: function writeStringAsSeptets(message, paddingBits, langIndex, langShiftIndex) {
     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
@@ -2845,20 +2994,20 @@ let GsmPDUHelper = {
       if (firstOctet & (PDU_VPF_ABSOLUTE | PDU_VPF_RELATIVE | PDU_VPF_ENHANCED)) {
         msg.validity = this.readHexOctet();
       }
       //TODO: check validity period
     } else {
       // - TP-Service-Center-Time-Stamp -
       let year   = this.readSwappedNibbleBCD(1) + PDU_TIMESTAMP_YEAR_OFFSET;
       let month  = this.readSwappedNibbleBCD(1) - 1;
-      let day    = this.readSwappedNibbleBCD(1) - 1;
-      let hour   = this.readSwappedNibbleBCD(1) - 1;
-      let minute = this.readSwappedNibbleBCD(1) - 1;
-      let second = this.readSwappedNibbleBCD(1) - 1;
+      let day    = this.readSwappedNibbleBCD(1);
+      let hour   = this.readSwappedNibbleBCD(1);
+      let minute = this.readSwappedNibbleBCD(1);
+      let second = this.readSwappedNibbleBCD(1);
       msg.timestamp = Date.UTC(year, month, day, hour, minute, second);
 
       // If the most significant bit of the least significant nibble is 1,
       // the timezone offset is negative (fourth bit from the right => 0x08).
       let tzOctet = this.readHexOctet();
       let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000;
       if (tzOctet & 0x08) {
         msg.timestamp -= tzOffset;
new file mode 100644
--- /dev/null
+++ b/dom/system/b2g/tests/test_ril_worker_sms.js
@@ -0,0 +1,438 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+const ESCAPE = "\uffff";
+const RESCTL = "\ufffe";
+const LF = "\n";
+const CR = "\r";
+const SP = " ";
+const FF = "\u000c";
+
+function run_test() {
+  run_next_test();
+}
+
+/**
+ * Verify validity of the national language tables
+ */
+add_test(function test_nl_locking_shift_tables_validity() {
+  for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) {
+    do_print("Verifying PDU_NL_LOCKING_SHIFT_TABLES[" + lst + "]");
+
+    let table = PDU_NL_LOCKING_SHIFT_TABLES[lst];
+
+    // Make sure table length is 128, or it will break table lookup algorithm.
+    do_check_eq(table.length, 128);
+
+    // Make sure special values are preserved.
+    do_check_eq(table[PDU_NL_EXTENDED_ESCAPE], ESCAPE);
+    do_check_eq(table[PDU_NL_LINE_FEED], LF);
+    do_check_eq(table[PDU_NL_CARRIAGE_RETURN], CR);
+    do_check_eq(table[PDU_NL_SPACE], SP);
+  }
+
+  run_next_test();
+});
+
+add_test(function test_nl_single_shift_tables_validity() {
+  for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) {
+    do_print("Verifying PDU_NL_SINGLE_SHIFT_TABLES[" + sst + "]");
+
+    let table = PDU_NL_SINGLE_SHIFT_TABLES[sst];
+
+    // Make sure table length is 128, or it will break table lookup algorithm.
+    do_check_eq(table.length, 128);
+
+    // Make sure special values are preserved.
+    do_check_eq(table[PDU_NL_EXTENDED_ESCAPE], ESCAPE);
+    do_check_eq(table[PDU_NL_PAGE_BREAK], FF);
+    do_check_eq(table[PDU_NL_RESERVED_CONTROL], RESCTL);
+  }
+
+  run_next_test();
+});
+
+/**
+ * Verify GsmPDUHelper#_calculateLangEncodedLength() and
+ * GsmPDUHelper#writeStringAsSeptets() algorithm match each other.
+ */
+add_test(function test_GsmPDUHelper__calculateLangEncodedLength() {
+  let worker = newWorker({
+    postRILMessage: function fakePostRILMessage(data) {
+      // Do nothing
+    },
+    postMessage: function fakePostMessage(message) {
+      // Do nothing
+    }
+  });
+
+  let helper = worker.GsmPDUHelper;
+  helper.resetOctetWritten = function () {
+    helper.octetsWritten = 0;
+  };
+  helper.writeHexOctet = function () {
+    helper.octetsWritten++;
+  };
+
+  function do_check_calc(str, expectedCalcLen, lst, sst) {
+    do_check_eq(expectedCalcLen,
+                helper._calculateLangEncodedLength(str,
+                                                   PDU_NL_LOCKING_SHIFT_TABLES[lst],
+                                                   PDU_NL_SINGLE_SHIFT_TABLES[sst]));
+
+    helper.resetOctetWritten();
+    helper.writeStringAsSeptets(str, 0, lst, sst);
+    do_check_eq(Math.ceil(expectedCalcLen * 7 / 8), helper.octetsWritten);
+  }
+
+  // Test calculation encoded message length using both locking/single shift tables.
+  for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) {
+    let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst];
+
+    let str = langTable.substring(0, PDU_NL_EXTENDED_ESCAPE)
+              + langTable.substring(PDU_NL_EXTENDED_ESCAPE + 1);
+
+    for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) {
+      let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst];
+
+      // <escape>, <resctrl> should be ignored.
+      do_check_calc(ESCAPE + RESCTL, 0, lst, sst);
+
+      // Characters defined in locking shift table should be encoded directly.
+      do_check_calc(str, str.length, lst, sst);
+
+      let [str1, str2] = ["", ""];
+      for (let i = 0; i < langShiftTable.length; i++) {
+        if ((i == PDU_NL_EXTENDED_ESCAPE) || (i == PDU_NL_RESERVED_CONTROL)) {
+          continue;
+        }
+
+        let c = langShiftTable[i];
+        if (langTable.indexOf(c) >= 0) {
+          str1 += c;
+        } else {
+          str2 += c;
+        }
+      }
+
+      // Characters found in both locking/single shift tables should be
+      // directly encoded.
+      do_check_calc(str1, str1.length, lst, sst);
+
+      // Characters found only in single shift tables should be encoded as
+      // <escape><code>, therefore doubles its original length.
+      do_check_calc(str2, str2.length * 2, lst, sst);
+    }
+  }
+
+  run_next_test();
+});
+
+/**
+ * Verify GsmPDUHelper#calculateUserDataLength handles national language
+ * selection correctly.
+ */
+add_test(function test_GsmPDUHelper_calculateUserDataLength() {
+  let worker = newWorker({
+    postRILMessage: function fakePostRILMessage(data) {
+      // Do nothing
+    },
+    postMessage: function fakePostMessage(message) {
+      // Do nothing
+    }
+  });
+
+  let helper = worker.GsmPDUHelper;
+  let calc = helper.calculateUserDataLength;
+  function test_calc(str, expected, enabledGsmTableTuples) {
+    helper.enabledGsmTableTuples = enabledGsmTableTuples;
+    let options = {body: str};
+    calc.call(helper, options);
+
+    do_check_eq(expected[0], options.dcs);
+    do_check_eq(expected[1], options.encodedBodyLength);
+    do_check_eq(expected[2], options.userDataHeaderLength);
+    do_check_eq(expected[3], options.langIndex);
+    do_check_eq(expected[4], options.langShiftIndex);
+  }
+
+  // Test UCS fallback
+  // - No any default enabled nl tables
+  test_calc("A", [PDU_DCS_MSG_CODING_16BITS_ALPHABET, 2, 0, 0, 0], []);
+  // - Character not defined in enabled nl tables
+  test_calc("A", [PDU_DCS_MSG_CODING_16BITS_ALPHABET, 2, 0, 0, 0], [[2, 2]]);
+
+  // With GSM default nl tables
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 0, 0, 0], [[0, 0]]);
+  // - SP is defined in both locking/single shift tables, should be directly
+  //   encoded.
+  test_calc(SP, [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 0, 0, 0], [[0, 0]]);
+  // - '^' is only defined in single shift table, should be encoded as
+  //   <escape>^.
+  test_calc("^", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 2, 0, 0, 0], [[0, 0]]);
+
+  // Test userDataHeaderLength calculation
+  // - Header contains both IEIs
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 6, 1, 1], [[1, 1]]);
+  // - Header contains only locking shift table IEI
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[1, 0]]);
+  // - Header contains only single shift table IEI
+  test_calc("^", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 2, 3, 0, 1], [[0, 1]]);
+
+  // Test minimum cost nl tables selection
+  // - 'A' is defined in locking shift table
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[1, 0], [2, 0]]);
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[2, 0], [1, 0]]);
+  // - 'A' is defined in single shift table
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 2, 6, 2, 4], [[2, 0], [2, 4]]);
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 2, 6, 2, 4], [[2, 4], [2, 0]]);
+  // - 'A' is defined in locking shift table of one tuple and in single shift
+  //   table of another.
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[1, 0], [2, 4]]);
+  test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[2, 4], [1, 0]]);
+
+  run_next_test();
+});
+
+/**
+ * Verify GsmPDUHelper#writeStringAsSeptets() padding bits handling.
+ */
+add_test(function test_GsmPDUHelper_writeStringAsSeptets() {
+  let worker = newWorker({
+    postRILMessage: function fakePostRILMessage(data) {
+      // Do nothing
+    },
+    postMessage: function fakePostMessage(message) {
+      // Do nothing
+    }
+  });
+
+  let helper = worker.GsmPDUHelper;
+  helper.resetOctetWritten = function () {
+    helper.octetsWritten = 0;
+  };
+  helper.writeHexOctet = function () {
+    helper.octetsWritten++;
+  };
+
+  let base = "AAAAAAAA"; // Base string of 8 characters long
+  for (let len = 0; len < 8; len++) {
+    let str = base.substring(0, len);
+
+    for (let paddingBits = 0; paddingBits < 8; paddingBits++) {
+      do_print("Verifying GsmPDUHelper.writeStringAsSeptets("
+               + str + ", " + paddingBits + ", <default>, <default>)");
+      helper.resetOctetWritten();
+      helper.writeStringAsSeptets(str, paddingBits, PDU_NL_IDENTIFIER_DEFAULT,
+                                  PDU_NL_IDENTIFIER_DEFAULT);
+      do_check_eq(Math.ceil(((len * 7) + paddingBits) / 8),
+                  helper.octetsWritten);
+    }
+  }
+
+  run_next_test();
+});
+
+/**
+ * Verify receiving SMS-DELIVERY messages
+ */
+
+function hexToNibble(nibble) {
+  nibble &= 0x0f;
+  if (nibble < 10) {
+    nibble += 48; // ASCII '0'
+  } else {
+    nibble += 55; // ASCII 'A'
+  }
+  return nibble;
+}
+
+function pduToParcelData(pdu) {
+  let dataLength = 4 + pdu.length * 4 + 4;
+  let data = new Uint8Array(dataLength);
+  let offset = 0;
+
+  // String length
+  data[offset++] = pdu.length & 0xFF;
+  data[offset++] = (pdu.length >> 8) & 0xFF;
+  data[offset++] = (pdu.length >> 16) & 0xFF;
+  data[offset++] = (pdu.length >> 24) & 0xFF;
+
+  // PDU data
+  for (let i = 0; i < pdu.length; i++) {
+    let hi = (pdu[i] >>> 4) & 0x0F;
+    let lo = pdu[i] & 0x0F;
+
+    data[offset++] = hexToNibble(hi);
+    data[offset++] = 0;
+    data[offset++] = hexToNibble(lo);
+    data[offset++] = 0;
+  }
+
+  // String delimitor
+  data[offset++] = 0;
+  data[offset++] = 0;
+  data[offset++] = 0;
+  data[offset++] = 0;
+
+  return data;
+}
+
+function compose7bitPdu(lst, sst, data, septets) {
+  if ((lst == 0) && (sst == 0)) {
+    return [0x00,                              // SMSC
+            PDU_MTI_SMS_DELIVER,               // firstOctet
+            1, 0x00, 0,                        // senderAddress
+            0x00,                              // protocolIdentifier
+            PDU_DCS_MSG_CODING_7BITS_ALPHABET, // dataCodingScheme
+            0, 0, 0, 0, 0, 0, 0,               // y m d h m s tz
+            septets]                           // userDataLength
+           .concat(data);
+  }
+
+  return [0x00,                                            // SMSC
+          PDU_MTI_SMS_DELIVER | PDU_UDHI,                  // firstOctet
+          1, 0x00, 0,                                      // senderAddress
+          0x00,                                            // protocolIdentifier
+          PDU_DCS_MSG_CODING_7BITS_ALPHABET,               // dataCodingScheme
+          0, 0, 0, 0, 0, 0, 0,                             // y m d h m s tz
+          8 + septets,                                     // userDataLength
+          6,                                               // user data header length
+          PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT, 1, lst, // PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT
+          PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT, 1, sst]  // PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT
+         .concat(data);
+}
+
+function composeUcs2Pdu(rawBytes) {
+  return [0x00,                               // SMSC
+          PDU_MTI_SMS_DELIVER,                // firstOctet
+          1, 0x00, 0,                         // senderAddress
+          0x00,                               // protocolIdentifier
+          PDU_DCS_MSG_CODING_16BITS_ALPHABET, // dataCodingScheme
+          0, 0, 0, 0, 0, 0, 0,                // y m d h m s tz
+          rawBytes.length]                    // userDataLength
+         .concat(rawBytes);
+}
+
+function newSmsParcel(pdu) {
+  return newIncomingParcel(-1,
+                           RESPONSE_TYPE_UNSOLICITED,
+                           UNSOLICITED_RESPONSE_NEW_SMS,
+                           pduToParcelData(pdu));
+}
+
+function removeSpecialChar(str, needle) {
+  for (let i = 0; i < needle.length; i++) {
+    let pos;
+    while ((pos = str.indexOf(needle[i])) >= 0) {
+      str = str.substring(0, pos) + str.substring(pos + 1);
+    }
+  }
+  return str;
+}
+
+function newWriteHexOctetAsUint8Worker() {
+  let worker = newWorker({
+    postRILMessage: function fakePostRILMessage(data) {
+      // Do nothing
+    },
+    postMessage: function fakePostMessage(message) {
+      // Do nothing
+    }
+  });
+
+  worker.GsmPDUHelper.writeHexOctet = function (value) {
+    worker.Buf.writeUint8(value);
+  };
+
+  return worker;
+}
+
+function add_test_receiving_sms(expected, pdu) {
+  add_test(function test_receiving_sms() {
+    let worker = newWorker({
+      postRILMessage: function fakePostRILMessage(data) {
+        // Do nothing
+      },
+      postMessage: function fakePostMessage(message) {
+        do_print("body: " + message.body);
+        do_check_eq(expected, message.body)
+      }
+    });
+
+    do_print("expect: " + expected);
+    do_print("pdu: " + pdu);
+    worker.onRILMessage(newSmsParcel(pdu));
+
+    run_next_test();
+  });
+}
+
+function test_receiving_7bit_alphabets(lst, sst) {
+  let worker = newWriteHexOctetAsUint8Worker();
+  let helper = worker.GsmPDUHelper;
+  let buf = worker.Buf;
+
+  function get7bitRawBytes(expected) {
+    buf.outgoingIndex = 0;
+    helper.writeStringAsSeptets(expected, 0, lst, sst);
+
+    let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex);
+    return Array.slice(subArray);
+  }
+
+  let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst];
+  let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst];
+
+  let text = removeSpecialChar(langTable + langShiftTable, ESCAPE + RESCTL);
+  for (let i = 0; i < text.length;) {
+    let len = Math.min(70, text.length - i);
+    let expected = text.substring(i, i + len);
+    let septets = helper._calculateLangEncodedLength(expected, langTable,
+                                                     langShiftTable);
+    let rawBytes = get7bitRawBytes(expected);
+    let pdu = compose7bitPdu(lst, sst, rawBytes, septets);
+    add_test_receiving_sms(expected, pdu);
+
+    i += len;
+  }
+}
+
+function test_receiving_ucs2_alphabets(text) {
+  let worker = newWriteHexOctetAsUint8Worker();
+  let buf = worker.Buf;
+
+  function getUCS2RawBytes(expected) {
+    buf.outgoingIndex = 0;
+    worker.GsmPDUHelper.writeUCS2String(expected);
+
+    let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex);
+    return Array.slice(subArray);
+  }
+
+  for (let i = 0; i < text.length;) {
+    let len = Math.min(70, text.length - i);
+    let expected = text.substring(i, i + len);
+    let rawBytes = getUCS2RawBytes(expected);
+    let pdu = composeUcs2Pdu(rawBytes);
+    add_test_receiving_sms(expected, pdu);
+
+    i += len;
+  }
+}
+
+let ucs2str = "";
+for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) {
+  ucs2str += PDU_NL_LOCKING_SHIFT_TABLES[lst];
+  for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) {
+    test_receiving_7bit_alphabets(lst, sst);
+
+    if (lst == 0) {
+      ucs2str += PDU_NL_SINGLE_SHIFT_TABLES[sst];
+    }
+  }
+}
+test_receiving_ucs2_alphabets(ucs2str);
+
--- a/dom/system/b2g/tests/xpcshell.ini
+++ b/dom/system/b2g/tests/xpcshell.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 head = header_helpers.js
 tail =
 
 [test_ril_worker_buf.js]
+[test_ril_worker_sms.js]
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -80,19 +80,24 @@ using namespace mozilla::xpconnect::memo
 
 // The size of the worker runtime heaps in bytes. May be changed via pref.
 #define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
 
 // The C stack size. We use the same stack size on all platforms for
 // consistency.
 #define WORKER_STACK_SIZE 256 * sizeof(size_t) * 1024
 
-// The stack limit the JS engine will check. Half the size of the
-// actual C stack, to be safe.
+// The stack limit the JS engine will check. 
+#ifdef MOZ_ASAN
+// For ASan, we need more stack space, so we use all that is available
+#define WORKER_CONTEXT_NATIVE_STACK_LIMIT WORKER_STACK_SIZE
+#else
+// Half the size of the actual C stack, to be safe.
 #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
+#endif
 
 // The maximum number of threads to use for workers, overridable via pref.
 #define MAX_WORKERS_PER_DOMAIN 10
 
 PR_STATIC_ASSERT(MAX_WORKERS_PER_DOMAIN >= 1);
 
 // The default number of seconds that close handlers will be allowed to run.
 #define MAX_SCRIPT_RUN_TIME_SEC 10
--- a/dom/workers/test/importScripts_worker.js
+++ b/dom/workers/test/importScripts_worker.js
@@ -15,19 +15,19 @@ importedScriptFunction();
 
 function tryBadScripts() {
   var badScripts = [
     // Has a syntax error
     "importScripts_worker_imported3.js",
     // Throws an exception
     "importScripts_worker_imported4.js",
     // Shouldn't exist!
-    "http://flippety.com/floppety/foo.js",
+    "http://example.com/non-existing/importScripts_worker_foo.js",
     // Not a valid url
-    "http://flippety::foo_js ftw"
+    "http://notadomain::notafile aword"
   ];
 
   for (var i = 0; i < badScripts.length; i++) {
     var caughtException = false;
     var url = badScripts[i];
     try {
       importScripts(url);
     }
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -1815,16 +1815,19 @@ public class GeckoAppShell
             // http://developer.android.com/guide/practices/screens_support.html
             if ((config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE) {
                 return true;
             }
         }
         return false;
     }
 
+    public static void emitGeckoAccessibilityEvent (int eventType, String[] textList, String description, boolean enabled, boolean checked, boolean password) {
+    }
+
     public static double[] getCurrentNetworkInformation() {
         return GeckoNetworkManager.getInstance().getCurrentInformation();
     }
 
     public static void enableNetworkNotifications() {
         GeckoNetworkManager.getInstance().enableNotifications();
     }
 
--- a/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff
+++ b/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff
@@ -9438,130 +9438,132 @@ 38891a44698,44699
 > pronate/DSGN
 > pronator/MS
 38951c44759
 < proprietorship/M
 ---
 > proprietorship/MS
 39039a44848
 > provender/M
-40036a45846
+39564a45374
+> quinoa
+40036a45847
 > recency
-40141a45952
+40141a45953
 > recuse/DGS
-40208a46020
+40208a46021
 > refactor/SMDG
-40244d46055
+40244d46056
 < reflexion/SM
-40829c46640
+40829c46641
 < reverie/M
 ---
 > reverie/MS
-41415a47227
+41415a47228
 > sabre/MS
-41914c47726
+41914c47727
 < schnaps's
 ---
 > schnaps/M
-41949c47761
+41949c47762
 < schrod's
 ---
 > schrod/SM
-41998a47811
+41998a47812
 > scot-free
-42883,42885c48696
+42883,42885c48697
 < shit's
 < shit/S!
 < shite/S!
 ---
 > shit/MS!
-42887,42888c48698,48699
+42887,42888c48699,48700
 < shithead/S!
 < shitload/!
 ---
 > shithead/MS!
 > shitload/MS!
-42891c48702
+42891c48703
 < shitty/RT!
 ---
 > shitty/TR!
-42976a48788
+42976a48789
 > should've
-43008c48820
+43008c48821
 < showtime
 ---
 > showtime/MS
-43724,43726c49536
+43724,43726c49537
 < smoulder's
 < smouldered
 < smoulders
 ---
 > smoulder/GSMD
-44062c49872
+44062c49873
 < sonofabitch
 ---
 > sonofabitch/!
-44371a50182
+44371a50183
 > spick/S!
-44383c50194
+44383c50195
 < spik/S
 ---
 > spik/S!
-46106a51918
+46106a51919
 > syllabi
-46160c51972
+46160c51973
 < synch/GMD
 ---
 > synch/GMDS
-46167d51978
+46167d51979
 < synchs
-46203,46204c52014,52015
+46203,46204c52015,52016
 < sysadmin/S
 < sysop/S
 ---
 > sysadmin/MS
 > sysop/MS
-46752a52564
+46752a52565
 > terabit/MS
-46753a52566,52567
+46753a52567,52568
 > terahertz/M
 > terapixel/MS
-46817a52632
+46817a52633
 > testcase/MS
-46831a52647
+46831a52648
 > testsuite/MS
-46925a52742
+46925a52743
 > theremin/MS
-47755a53573
+47755a53574
 > transfect/DSMG
-47774a53593,53594
+47774a53594,53595
 > transgenderism
 > transgene/MS
-47951c53771
+47951c53772
 < triage/M
 ---
 > triage/MG
-48869a54690
+48869a54691
 > unlikeable
-49211c55032
+49211c55033
 < vagina/M
 ---
 > vagina/MS
-49368,49369c55189
+49368,49369c55190
 < velour's
 < velours's
 ---
 > velour/MS
-49478a55299
+49478a55300
 > vertices
-50148a55970
+50148a55971
 > weaponize/DSG
-50260,50261d56081
+50260,50261d56082
 < werwolf/M
 < werwolves
-50728c56548
+50728c56549
 < women
 ---
 > women/M
-50794c56614
+50794c56615
 < wop/S!
 ---
 > wop/MS!
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-57436
+57437
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -45693,16 +45693,17 @@ quill/SM
 quilt/SMDRZG
 quilter/M
 quilting/M
 quin/S
 quince/SM
 quincentenary
 quine/S
 quinine/M
+quinoa
 quinquennial
 quinsy/M
 quint/SM
 quintessence/MS
 quintessential/Y
 quintet/SM
 quintette/MS
 quintic
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -179,55 +179,72 @@ public:
 
   void RemoveObserver(Observer<InfoType>* aObserver) {
     MOZ_ASSERT(mObservers);
     mObservers->RemoveObserver(aObserver);
 
     if (mObservers->Length() == 0) {
       DisableNotifications();
 
+      OnNotificationsDisabled();
+
       delete mObservers;
       mObservers = 0;
-
-      mHasValidCache = false;
     }
   }
 
+  void BroadcastInformation(const InfoType& aInfo) {
+    MOZ_ASSERT(mObservers);
+    mObservers->Broadcast(aInfo);
+  }
+
+protected:
+  virtual void EnableNotifications() = 0;
+  virtual void DisableNotifications() = 0;
+  virtual void OnNotificationsDisabled() {}
+
+private:
+  mozilla::ObserverList<InfoType>* mObservers;
+};
+
+template <class InfoType>
+class CachingObserversManager : public ObserversManager<InfoType>
+{
+public:
   InfoType GetCurrentInformation() {
     if (mHasValidCache) {
       return mInfo;
     }
 
-    mHasValidCache = true;
     GetCurrentInformationInternal(&mInfo);
     return mInfo;
   }
 
   void CacheInformation(const InfoType& aInfo) {
     mHasValidCache = true;
     mInfo = aInfo;
   }
 
   void BroadcastCachedInformation() {
-    MOZ_ASSERT(mObservers);
-    mObservers->Broadcast(mInfo);
+    this->BroadcastInformation(mInfo);
   }
 
 protected:
-  virtual void EnableNotifications() = 0;
-  virtual void DisableNotifications() = 0;
   virtual void GetCurrentInformationInternal(InfoType*) = 0;
 
+  virtual void OnNotificationsDisabled() {
+    mHasValidCache = false;
+  }
+
 private:
-  mozilla::ObserverList<InfoType>* mObservers;
   InfoType                mInfo;
   bool                    mHasValidCache;
 };
 
-class BatteryObserversManager : public ObserversManager<BatteryInformation>
+class BatteryObserversManager : public CachingObserversManager<BatteryInformation>
 {
 protected:
   void EnableNotifications() {
     PROXY_IF_SANDBOXED(EnableBatteryNotifications());
   }
 
   void DisableNotifications() {
     PROXY_IF_SANDBOXED(DisableBatteryNotifications());
@@ -235,17 +252,17 @@ protected:
 
   void GetCurrentInformationInternal(BatteryInformation* aInfo) {
     PROXY_IF_SANDBOXED(GetCurrentBatteryInformation(aInfo));
   }
 };
 
 static BatteryObserversManager sBatteryObservers;
 
-class NetworkObserversManager : public ObserversManager<NetworkInformation>
+class NetworkObserversManager : public CachingObserversManager<NetworkInformation>
 {
 protected:
   void EnableNotifications() {
     PROXY_IF_SANDBOXED(EnableNetworkNotifications());
   }
 
   void DisableNotifications() {
     PROXY_IF_SANDBOXED(DisableNetworkNotifications());
@@ -253,16 +270,30 @@ protected:
 
   void GetCurrentInformationInternal(NetworkInformation* aInfo) {
     PROXY_IF_SANDBOXED(GetCurrentNetworkInformation(aInfo));
   }
 };
 
 static NetworkObserversManager sNetworkObservers;
 
+class WakeLockObserversManager : public ObserversManager<WakeLockInformation>
+{
+protected:
+  void EnableNotifications() {
+    PROXY_IF_SANDBOXED(EnableWakeLockNotifications());
+  }
+
+  void DisableNotifications() {
+    PROXY_IF_SANDBOXED(DisableWakeLockNotifications());
+  }
+};
+
+static WakeLockObserversManager sWakeLockObservers;
+
 void
 RegisterBatteryObserver(BatteryObserver* aObserver)
 {
   AssertMainThread();
   sBatteryObservers.AddObserver(aObserver);
 }
 
 void
@@ -430,10 +461,47 @@ void Reboot()
 }
 
 void PowerOff()
 {
   AssertMainThread();
   PROXY_IF_SANDBOXED(PowerOff());
 }
 
+void
+RegisterWakeLockObserver(WakeLockObserver* aObserver)
+{
+  AssertMainThread();
+  sWakeLockObservers.AddObserver(aObserver);
+}
+
+void
+UnregisterWakeLockObserver(WakeLockObserver* aObserver)
+{
+  AssertMainThread();
+  sWakeLockObservers.RemoveObserver(aObserver);
+}
+
+void
+ModifyWakeLock(const nsAString &aTopic,
+               hal::WakeLockControl aLockAdjust,
+               hal::WakeLockControl aHiddenAdjust)
+{
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(ModifyWakeLock(aTopic, aLockAdjust, aHiddenAdjust));
+}
+
+void
+GetWakeLockInfo(const nsAString &aTopic, WakeLockInformation *aWakeLockInfo)
+{
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(GetWakeLockInfo(aTopic, aWakeLockInfo));
+}
+
+void
+NotifyWakeLockChange(const WakeLockInformation& aInfo)
+{
+  AssertMainThread();
+  sWakeLockObservers.BroadcastInformation(aInfo);
+}
+
 } // namespace hal
 } // namespace mozilla
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/hal_sandbox/PHal.h"
 #include "base/basictypes.h"
 #include "mozilla/Types.h"
 #include "nsTArray.h"
 #include "prlog.h"
 #include "mozilla/dom/battery/Types.h"
 #include "mozilla/dom/network/Types.h"
+#include "mozilla/dom/power/Types.h"
 #include "mozilla/hal_sandbox/PHal.h"
 
 /*
  * Hal.h contains the public Hal API.
  *
  * By default, this file defines its functions in the hal namespace, but if
  * MOZ_HAL_NAMESPACE is defined, we'll define our functions in that namespace.
  *
@@ -235,16 +236,65 @@ void SetTimezone(const nsCString& aTimez
  */
 void Reboot();
 
 /**
  * Power off the device.
  */
 void PowerOff();
 
+/**
+ * Enable wake lock notifications from the backend.
+ *
+ * This method is only used by WakeLockObserversManager.
+ */
+void EnableWakeLockNotifications();
+
+/**
+ * Disable wake lock notifications from the backend.
+ *
+ * This method is only used by WakeLockObserversManager.
+ */
+void DisableWakeLockNotifications();
+
+/**
+ * Inform the wake lock backend there is a new wake lock observer.
+ * @param aWakeLockObserver The observer that should be added.
+ */
+void RegisterWakeLockObserver(WakeLockObserver* aObserver);
+
+/**
+ * Inform the wake lock backend a wake lock observer unregistered.
+ * @param aWakeLockObserver The observer that should be removed.
+ */
+void UnregisterWakeLockObserver(WakeLockObserver* aObserver);
+
+/**
+ * Adjust the internal wake lock counts.
+ * @param aTopic        lock topic
+ * @param aLockAdjust   to increase or decrease active locks
+ * @param aHiddenAdjust to increase or decrease hidden locks
+ */
+void ModifyWakeLock(const nsAString &aTopic,
+                    hal::WakeLockControl aLockAdjust,
+                    hal::WakeLockControl aHiddenAdjust);
+
+/**
+ * Query the wake lock numbers of aTopic.
+ * @param aTopic        lock topic
+ * @param aWakeLockInfo wake lock numbers
+ */
+void GetWakeLockInfo(const nsAString &aTopic, hal::WakeLockInformation *aWakeLockInfo);
+
+/**
+ * Notify of a change in the wake lock state.
+ * @param aWakeLockInfo The new wake lock information.
+ */
+void NotifyWakeLockChange(const hal::WakeLockInformation& aWakeLockInfo);
+
 } // namespace MOZ_HAL_NAMESPACE
 } // namespace mozilla
 
 #ifdef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_HAL_NAMESPACE
 #endif
 
--- a/hal/HalTypes.h
+++ b/hal/HalTypes.h
@@ -31,19 +31,35 @@ enum LightMode {
     eHalLightMode_User = 0,       // brightness is managed by user setting
     eHalLightMode_Sensor = 1      // brightness is managed by a light sensor
 };
 enum FlashMode {
     eHalLightFlash_None = 0,
     eHalLightFlash_Timed = 1,     // timed flashing.  Use flashOnMS and flashOffMS for timing
     eHalLightFlash_Hardware = 2   // hardware assisted flashing
 };
+
 } // namespace hal
 } // namespace mozilla
 
+namespace mozilla {
+namespace hal {
+
+/**
+ * Used by ModifyWakeLock
+ */
+enum WakeLockControl {
+  WAKE_LOCK_REMOVE_ONE = -1,
+  WAKE_LOCK_NO_CHANGE  = 0,
+  WAKE_LOCK_ADD_ONE    = 1,
+};
+
+}
+}
+
 namespace IPC {
 
 /**
  * Light type serializer.
  */
 template <>
 struct ParamTraits<mozilla::hal::LightType>
   : public EnumSerializer<mozilla::hal::LightType,
@@ -66,11 +82,21 @@ struct ParamTraits<mozilla::hal::LightMo
  */
 template <>
 struct ParamTraits<mozilla::hal::FlashMode>
   : public EnumSerializer<mozilla::hal::FlashMode,
                           mozilla::hal::eHalLightFlash_None,
                           mozilla::hal::eHalLightFlash_Hardware>
 {};
 
+/**
+ * WakeLockControl serializer.
+ */
+template <>
+struct ParamTraits<mozilla::hal::WakeLockControl>
+  : public EnumSerializer<mozilla::hal::WakeLockControl,
+                          mozilla::hal::WAKE_LOCK_REMOVE_ONE,
+                          mozilla::hal::WAKE_LOCK_ADD_ONE>
+{};
+
 } // namespace IPC
 
 #endif // mozilla_hal_Types_h
new file mode 100644
--- /dev/null
+++ b/hal/HalWakeLock.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/Hal.h"
+#include "mozilla/HalWakeLock.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+
+using namespace mozilla::hal;
+
+namespace mozilla {
+namespace hal {
+
+WakeLockState
+ComputeWakeLockState(int aNumLocks, int aNumHidden)
+{
+  if (aNumLocks == 0) {
+    return WAKE_LOCK_STATE_UNLOCKED;
+  } else if (aNumLocks == aNumHidden) {
+    return WAKE_LOCK_STATE_HIDDEN;
+  } else {
+    return WAKE_LOCK_STATE_VISIBLE;
+  }
+}
+
+} // hal
+} // mozilla
+
+namespace mozilla {
+namespace hal_impl {
+
+namespace {
+struct LockCount {
+  PRUint32 numLocks;
+  PRUint32 numHidden;
+};
+}
+
+static int sActiveChildren = 0;
+static nsAutoPtr<nsDataHashtable<nsStringHashKey, LockCount> > sLockTable;
+static bool sInitialized = false;
+
+static void
+Init()
+{
+  sLockTable = new nsDataHashtable<nsStringHashKey, LockCount>();
+  sLockTable->Init();
+  ClearOnShutdown(&sLockTable);
+  sInitialized = true;
+}
+
+void
+EnableWakeLockNotifications()
+{
+  sActiveChildren++;
+}
+
+void
+DisableWakeLockNotifications()
+{
+  sActiveChildren--;
+}
+
+void
+ModifyWakeLock(const nsAString &aTopic,
+               hal::WakeLockControl aLockAdjust,
+               hal::WakeLockControl aHiddenAdjust)
+{
+  if (!sInitialized) {
+    Init();
+  }
+
+  LockCount count;
+  count.numLocks = 0;
+  count.numHidden = 0;
+  sLockTable->Get(aTopic, &count);
+  MOZ_ASSERT(count.numLocks >= count.numHidden);
+  MOZ_ASSERT(aLockAdjust >= 0 || count.numLocks > 0);
+  MOZ_ASSERT(aHiddenAdjust >= 0 || count.numHidden > 0);
+
+  WakeLockState oldState = ComputeWakeLockState(count.numLocks, count.numHidden);
+
+  count.numLocks += aLockAdjust;
+  count.numHidden += aHiddenAdjust;
+  MOZ_ASSERT(count.numLocks >= count.numHidden);
+
+  if (count.numLocks) {
+    sLockTable->Put(aTopic, count);
+  } else {
+    sLockTable->Remove(aTopic);
+  }
+
+  WakeLockState newState = ComputeWakeLockState(count.numLocks, count.numHidden);
+
+  if (sActiveChildren && oldState != newState) {
+    WakeLockInformation info;
+    info.numLocks() = count.numLocks;
+    info.numHidden() = count.numHidden;
+    info.topic() = aTopic;
+    NotifyWakeLockChange(info);
+  }
+}
+
+void
+GetWakeLockInfo(const nsAString &aTopic, WakeLockInformation *aWakeLockInfo)
+{
+  if (!sInitialized) {
+    Init();
+  }
+
+  LockCount count;
+  count.numLocks = 0;
+  count.numHidden = 0;
+  sLockTable->Get(aTopic, &count);
+
+  aWakeLockInfo->numLocks() = count.numLocks;
+  aWakeLockInfo->numHidden() = count.numHidden;
+  aWakeLockInfo->topic() = aTopic;
+}
+
+} // hal_impl
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/hal/HalWakeLock.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef __HAL_WAKELOCK_H_
+#define __HAL_WAKELOCK_H_
+
+namespace mozilla {
+namespace hal {
+
+enum WakeLockState {
+  WAKE_LOCK_STATE_UNLOCKED,
+  WAKE_LOCK_STATE_HIDDEN,
+  WAKE_LOCK_STATE_VISIBLE
+};
+
+/**
+ * Return the wake lock state according to the numbers.
+ */
+WakeLockState ComputeWakeLockState(int aNumLocks, int aNumHidden);
+
+} // hal
+} // mozilla
+
+#endif /* __HAL_WAKELOCK_H_ */
--- a/hal/Makefile.in
+++ b/hal/Makefile.in
@@ -58,22 +58,24 @@ EXPORT_LIBRARY = 1
 
 EXPORTS_NAMESPACES = mozilla
 EXPORTS_mozilla = \
   Hal.h \
   HalImpl.h \
   HalSandbox.h \
   HalSensor.h \
   HalTypes.h \
+  HalWakeLock.h \
   $(NULL)
 
 CPPSRCS = \
   Hal.cpp \
   SandboxHal.cpp \
   WindowIdentifier.cpp \
+  HalWakeLock.cpp \
   $(NULL)
 
 ifeq (android,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
   AndroidHal.cpp \
   AndroidSensor.cpp \
   $(NULL)
 else ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
@@ -108,9 +110,9 @@ ifneq (gonk,$(MOZ_WIDGET_TOOLKIT)) #{
 CPPSRCS += FallbackLights.cpp FallbackTime.cpp
 endif #}
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
 
 CFLAGS          += $(MOZ_DBUS_GLIB_CFLAGS)
-CXXFLAGS        += $(MOZ_DBUS_GLIB_CFLAGS) -DHAVE_PTHREADS
+CXXFLAGS        += $(MOZ_DBUS_GLIB_CFLAGS)
--- a/hal/gonk/GonkSensor.cpp
+++ b/hal/gonk/GonkSensor.cpp
@@ -6,20 +6,23 @@
 #include <pthread.h>
 #include <stdio.h>
 
 #include "Hal.h"
 #include "HalSensor.h"
 #include "hardware/sensors.h"
 #include "mozilla/Util.h"
 #include "SensorDevice.h"
+#include "nsThreadUtils.h"
 
 using namespace mozilla::hal;
 using namespace android;
 
+namespace mozilla {
+
 static SensorType
 HardwareSensorToHalSensor(int type)
 {     
   switch(type) {
     case SENSOR_TYPE_ORIENTATION:
       return SENSOR_ORIENTATION;
     case SENSOR_TYPE_ACCELEROMETER:
       return SENSOR_ACCELERATION;
@@ -58,29 +61,29 @@ SensorseventToSensorData(const sensors_e
   aSensorData->values()[1] = data.data[1];
   aSensorData->values()[2] = data.data[2];
   return true;
 }
 
 static void
 onSensorChanged(const sensors_event_t& data, SensorData* aSensorData)
 {
-  mozilla::DebugOnly<bool> convertedData = SensorseventToSensorData(data, aSensorData);
+  DebugOnly<bool> convertedData = SensorseventToSensorData(data, aSensorData);
   MOZ_ASSERT(convertedData);
   NotifySensorChange(*aSensorData);
 }
 
-namespace mozilla {
 namespace hal_impl {
 
 static pthread_t sThread;
-static bool sInitialized = false;
-static bool sContinue = false;
-static int sActivatedSensors = 0;
+static bool sInitialized;
+static bool sContinue;
+static int sActivatedSensors;
 static SensorData sSensordata[NUM_SENSOR_TYPE];
+static nsCOMPtr<nsIThread> sSwitchThread;
 
 static void*
 UpdateSensorData(void* /*unused*/)
 {
   SensorDevice &device = SensorDevice::getInstance();
   const size_t numEventMax = 16;
   sensors_event_t buffer[numEventMax];
   int count = 0;
@@ -95,52 +98,76 @@ UpdateSensorData(void* /*unused*/)
       onSensorChanged(buffer[i], &sSensordata[HardwareSensorToHalSensor(buffer[i].type)]);
     }
   }
 
   return NULL;
 }
 
 static void 
-InitialResources()
+InitializeResources()
 {
   pthread_create(&sThread, NULL, &UpdateSensorData, NULL);
+  NS_NewThread(getter_AddRefs(sSwitchThread));
   sInitialized = true;
   sContinue = true;
 }
 
 static void 
 ReleaseResources()
 {
   sContinue = false;
   pthread_join(sThread, NULL);
+  sSwitchThread->Shutdown();
   sInitialized = false;
 }
 
+// This class is used as a runnable on the sSwitchThread
+class SensorInfo {
+  public:
+    NS_INLINE_DECL_REFCOUNTING(SensorInfo)
+
+    SensorInfo(bool aActivate, sensor_t aSensor, pthread_t aThreadId) :
+               activate(aActivate), sensor(aSensor), threadId(aThreadId) { }
+
+    void Switch() {
+     SensorDevice& device = SensorDevice::getInstance();
+     device.activate((void*)threadId, sensor.handle, activate);
+    }
+
+  protected:
+    SensorInfo() { };
+    bool      activate;
+    sensor_t  sensor;
+    pthread_t threadId;
+};
+
 static void
 SensorSwitch(SensorType aSensor, bool activate)
 {
   int type = HalSensorToHardwareSensor(aSensor);
   const sensor_t* sensors = NULL;
   SensorDevice& device = SensorDevice::getInstance();
   size_t size = device.getSensorList(&sensors);
-
-  for (size_t i=0; i<size; i++) {
+  for (size_t i = 0; i < size; i++) {
     if (sensors[i].type == type) {
-      device.activate((void*)pthread_self(), sensors[i].handle, activate);
+      // Post an event to the activation thread
+      nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(new SensorInfo(activate, sensors[i], pthread_self()),
+                                                         &SensorInfo::Switch);
+      sSwitchThread->Dispatch(event, NS_DISPATCH_NORMAL);
       break;
     }
   }
 }
 
 void
 EnableSensorNotifications(SensorType aSensor) 
 {
   if (!sInitialized) {
-    InitialResources();
+    InitializeResources();
   }
   
   SensorSwitch(aSensor, true);
   sActivatedSensors++;
 }
 
 void
 DisableSensorNotifications(SensorType aSensor) 
--- a/hal/sandbox/PHal.ipdl
+++ b/hal/sandbox/PHal.ipdl
@@ -43,16 +43,17 @@ include "nspr/prtime.h";
 include "mozilla/HalSensor.h";
 include "mozilla/HalTypes.h";
 
 using PRTime;
 using mozilla::hal::FlashMode;
 using mozilla::hal::LightType;
 using mozilla::hal::LightMode;
 using mozilla::hal::SensorType;
+using mozilla::hal::WakeLockControl;
 
 namespace mozilla {
 
 namespace hal {
   struct BatteryInformation {
     double level;
     bool   charging;
     double remainingTime;
@@ -76,24 +77,33 @@ namespace hal {
 
 namespace hal {
   struct NetworkInformation {
     double bandwidth;
     bool   canBeMetered;
   };
 }
 
+namespace hal {
+  struct WakeLockInformation {
+    uint32_t numLocks;
+    uint32_t numHidden;
+    nsString topic;
+  };
+}
+
 namespace hal_sandbox {
 
 sync protocol PHal {
     manager PContent;
 
 child:
     NotifyBatteryChange(BatteryInformation aBatteryInfo);
     NotifyNetworkChange(NetworkInformation aNetworkInfo);
+    NotifyWakeLockChange(WakeLockInformation aWakeLockInfo);
 
 parent:
     Vibrate(uint32[] pattern, uint64[] id, PBrowser browser);
     CancelVibrate(uint64[] id, PBrowser browser);
 
     EnableBatteryNotifications();
     DisableBatteryNotifications();
     sync GetCurrentBatteryInformation()
@@ -116,16 +126,22 @@ parent:
     sync SetLight(LightType light, LightConfiguration aConfig)
       returns (bool status);
     sync GetLight(LightType light)
       returns (LightConfiguration aConfig, bool status);
 
     Reboot();
     PowerOff();
 
+    ModifyWakeLock(nsString aTopic, WakeLockControl aLockAdjust, WakeLockControl aHiddenAdjust);
+    EnableWakeLockNotifications();
+    DisableWakeLockNotifications();
+    sync GetWakeLockInfo(nsString aTopic)
+      returns (WakeLockInformation aWakeLockInfo);
+
 child:
     NotifySensorChange(SensorData aSensorData);
 
 parent:    
     EnableSensorNotifications(SensorType aSensor);
     DisableSensorNotifications(SensorType aSensor);
 
     __delete__();
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -164,20 +164,45 @@ EnableSensorNotifications(SensorType aSe
   Hal()->SendEnableSensorNotifications(aSensor);
 }
 
 void
 DisableSensorNotifications(SensorType aSensor) {
   Hal()->SendDisableSensorNotifications(aSensor);
 }
 
+void
+EnableWakeLockNotifications()
+{
+  Hal()->SendEnableWakeLockNotifications();
+}
+
+void
+DisableWakeLockNotifications()
+{
+  Hal()->SendDisableWakeLockNotifications();
+}
+
+void
+ModifyWakeLock(const nsAString &aTopic, WakeLockControl aLockAdjust, WakeLockControl aHiddenAdjust)
+{
+  Hal()->SendModifyWakeLock(nsString(aTopic), aLockAdjust, aHiddenAdjust);
+}
+
+void
+GetWakeLockInfo(const nsAString &aTopic, WakeLockInformation *aWakeLockInfo)
+{
+  Hal()->SendGetWakeLockInfo(nsString(aTopic), aWakeLockInfo);
+}
+
 class HalParent : public PHalParent
                 , public BatteryObserver
                 , public NetworkObserver
                 , public ISensorObserver
+                , public WakeLockObserver
 {
 public:
   NS_OVERRIDE virtual bool
   RecvVibrate(const InfallibleTArray<unsigned int>& pattern,
               const InfallibleTArray<uint64> &id,
               PBrowserParent *browserParent)
   {
     // Check whether browserParent is active.  We should have already
@@ -338,16 +363,51 @@ public:
   RecvDisableSensorNotifications(const SensorType &aSensor) {
     hal::UnregisterSensorObserver(aSensor, this);
     return true;
   }
   
   void Notify(const SensorData& aSensorData) {
     unused << SendNotifySensorChange(aSensorData);
   }
+
+  NS_OVERRIDE virtual bool
+  RecvModifyWakeLock(const nsString &aTopic,
+                     const WakeLockControl &aLockAdjust,
+                     const WakeLockControl &aHiddenAdjust)
+  {
+    hal::ModifyWakeLock(aTopic, aLockAdjust, aHiddenAdjust);
+    return true;
+  }
+
+  NS_OVERRIDE virtual bool
+  RecvEnableWakeLockNotifications()
+  {
+    hal::RegisterWakeLockObserver(this);
+    return true;
+  }
+   
+  NS_OVERRIDE virtual bool
+  RecvDisableWakeLockNotifications()
+  {
+    hal::UnregisterWakeLockObserver(this);
+    return true;
+  }
+
+  NS_OVERRIDE virtual bool
+  RecvGetWakeLockInfo(const nsString &aTopic, WakeLockInformation *aWakeLockInfo)
+  {
+    hal::GetWakeLockInfo(aTopic, aWakeLockInfo);
+    return true;
+  }
+  
+  void Notify(const WakeLockInformation& aWakeLockInfo)
+  {
+    unused << SendNotifyWakeLockChange(aWakeLockInfo);
+  }
 };
 
 class HalChild : public PHalChild {
 public:
   NS_OVERRIDE virtual bool
   RecvNotifyBatteryChange(const BatteryInformation& aBatteryInfo) {
     hal::NotifyBatteryChange(aBatteryInfo);
     return true;
@@ -356,16 +416,22 @@ public:
   NS_OVERRIDE virtual bool
   RecvNotifySensorChange(const hal::SensorData &aSensorData);
 
   NS_OVERRIDE virtual bool
   RecvNotifyNetworkChange(const NetworkInformation& aNetworkInfo) {
     hal::NotifyNetworkChange(aNetworkInfo);
     return true;
   }
+
+  NS_OVERRIDE virtual bool
+  RecvNotifyWakeLockChange(const WakeLockInformation& aWakeLockInfo) {
+    hal::NotifyWakeLockChange(aWakeLockInfo);
+    return true;
+  }
 };
 
 bool
 HalChild::RecvNotifySensorChange(const hal::SensorData &aSensorData) {
   hal::NotifySensorChange(aSensorData);
   
   return true;
 }
--- a/ipc/chromium/src/base/histogram.cc
+++ b/ipc/chromium/src/base/histogram.cc
@@ -912,16 +912,62 @@ void BooleanHistogram::AddBoolean(bool v
   Add(value ? 1 : 0);
 }
 
 BooleanHistogram::BooleanHistogram(const std::string& name)
     : LinearHistogram(name, 1, 2, 3) {
 }
 
 //------------------------------------------------------------------------------
+// FlagHistogram:
+//------------------------------------------------------------------------------
+
+Histogram *
+FlagHistogram::FactoryGet(const std::string &name, Flags flags)
+{
+  Histogram *h(nsnull);
+
+  if (!StatisticsRecorder::FindHistogram(name, &h)) {
+    // To avoid racy destruction at shutdown, the following will be leaked.
+    FlagHistogram *fh = new FlagHistogram(name);
+    fh->InitializeBucketRange();
+    fh->SetFlags(flags);
+    size_t zero_index = fh->BucketIndex(0);
+    fh->Histogram::Accumulate(1, 1, zero_index);
+    h = StatisticsRecorder::RegisterOrDeleteDuplicate(fh);
+  }
+
+  return h;
+}
+
+FlagHistogram::FlagHistogram(const std::string &name)
+  : BooleanHistogram(name), mSwitched(false) {
+}
+
+Histogram::ClassType
+FlagHistogram::histogram_type() const
+{
+  return FLAG_HISTOGRAM;
+}
+
+void
+FlagHistogram::Accumulate(Sample value, Count count, size_t index)
+{
+  if (mSwitched) {
+    return;
+  }
+
+  mSwitched = true;
+  DCHECK_EQ(value, 1);
+  Histogram::Accumulate(value, 1, index);
+  size_t zero_index = BucketIndex(0);
+  Histogram::Accumulate(1, -1, zero_index);
+}
+
+//------------------------------------------------------------------------------
 // CustomHistogram:
 //------------------------------------------------------------------------------
 
 Histogram* CustomHistogram::FactoryGet(const std::string& name,
                                        const std::vector<Sample>& custom_ranges,
                                        Flags flags) {
   Histogram* histogram(NULL);
 
--- a/ipc/chromium/src/base/histogram.h
+++ b/ipc/chromium/src/base/histogram.h
@@ -271,16 +271,17 @@ class Histogram {
   typedef std::vector<Sample> Ranges;
 
   // These enums are used to facilitate deserialization of renderer histograms
   // into the browser.
   enum ClassType {
     HISTOGRAM,
     LINEAR_HISTOGRAM,
     BOOLEAN_HISTOGRAM,
+    FLAG_HISTOGRAM,
     CUSTOM_HISTOGRAM,
     NOT_VALID_IN_RENDERER
   };
 
   enum BucketLayout {
     EXPONENTIAL,
     LINEAR,
     CUSTOM
@@ -637,24 +638,43 @@ class LinearHistogram : public Histogram
 class BooleanHistogram : public LinearHistogram {
  public:
   static Histogram* FactoryGet(const std::string& name, Flags flags);
 
   virtual ClassType histogram_type() const;
 
   virtual void AddBoolean(bool value);
 
- private:
+ protected:
   explicit BooleanHistogram(const std::string& name);
 
   DISALLOW_COPY_AND_ASSIGN(BooleanHistogram);
 };
 
 //------------------------------------------------------------------------------
 
+// FlagHistogram is like boolean histogram, but only allows a single off/on value.
+class FlagHistogram : public BooleanHistogram
+{
+public:
+  static Histogram *FactoryGet(const std::string &name, Flags flags);
+
+  virtual ClassType histogram_type() const;
+
+  virtual void Accumulate(Sample value, Count count, size_t index);
+
+private:
+  explicit FlagHistogram(const std::string &name);
+  bool mSwitched;
+
+  DISALLOW_COPY_AND_ASSIGN(FlagHistogram);
+};
+
+//------------------------------------------------------------------------------
+
 // CustomHistogram is a histogram for a set of custom integers.
 class CustomHistogram : public Histogram {
  public:
 
   static Histogram* FactoryGet(const std::string& name,
                                const std::vector<Sample>& custom_ranges,
                                Flags flags);
 
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -1763,25 +1763,57 @@ if test -n "$CLANG_CC"; then
     _WARNINGS_CFLAGS="-Qunused-arguments ${_WARNINGS_CFLAGS}"
     CPPFLAGS="-Qunused-arguments ${CPPFLAGS}"
 fi
 if test -n "$CLANG_CXX"; then
     _WARNINGS_CXXFLAGS="-Qunused-arguments ${_WARNINGS_CXXFLAGS}"
 fi
 
 dnl ========================================================
+dnl = Use Address Sanitizer
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(address-sanitizer,
+[  --enable-address-sanitizer       Enable Address Sanitizer (default=no)],
+    MOZ_ASAN=1,
+    MOZ_ASAN= )
+if test -n "$MOZ_ASAN"; then
+    MOZ_LLVM_HACKS=1
+    AC_DEFINE(MOZ_ASAN)
+fi
+AC_SUBST(MOZ_ASAN)
+
+dnl ========================================================
+dnl = Enable hacks required for LLVM instrumentations
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(llvm-hacks,
+[  --enable-llvm-hacks       Enable workarounds required for several LLVM instrumentations (default=no)],
+    MOZ_LLVM_HACKS=1,
+    MOZ_LLVM_HACKS= )
+if test -n "$MOZ_LLVM_HACKS"; then
+    MOZ_NO_WLZDEFS=1
+    MOZ_CFLAGS_NSS=1
+fi
+AC_SUBST(MOZ_NO_WLZDEFS)
+AC_SUBST(MOZ_CFLAGS_NSS)
+
+dnl ========================================================
 dnl GNU specific defaults
 dnl ========================================================
 if test "$GNU_CC"; then
     MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@'
     MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@'
     DSO_LDOPTS='-shared'
     if test "$GCC_USE_GNU_LD"; then
-        # Don't allow undefined symbols in libraries
-        DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs"
+        # Some tools like ASan use a runtime library that is only
+        # linked against executables, so we must allow undefined
+        # symbols for shared objects in some cases.
+        if test -z "$MOZ_NO_WLZDEFS"; then
+            # Don't allow undefined symbols in libraries
+            DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs"
+        fi
     fi
     WARNINGS_AS_ERRORS='-Werror -Wno-error=uninitialized'
     DSO_CFLAGS=''
     DSO_PIC_CFLAGS='-fPIC'
     ASFLAGS="$ASFLAGS -fPIC"
     _MOZ_RTTI_FLAGS_ON=-frtti
     _MOZ_RTTI_FLAGS_OFF=-fno-rtti
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -4081,17 +4081,21 @@ EmitCatch(JSContext *cx, BytecodeEmitter
      * our PNK_LEXICALSCOPE parent, so the decompiler knows to pop.
      */
     ptrdiff_t off = bce->stackDepth;
     if (NewSrcNote2(cx, bce, SRC_CATCH, off) < 0)
         return false;
     return true;
 }
 
-static bool
+/*
+ * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr12127. See
+ * the comment on EmitSwitch.
+ */
+MOZ_NEVER_INLINE static bool
 EmitTry(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     StmtInfo stmtInfo;
     ptrdiff_t catchJump = -1;
 
     /*
      * Push stmtInfo to track jumps-over-catches and gosubs-to-finally
      * for later fixup.
@@ -4441,17 +4445,21 @@ EmitIf(JSContext *cx, BytecodeEmitter *b
  * backpatching, which is handled by LetNotes.
  *
  * The SRC_DECL offset allows recursive decompilation of 'e'.
  *
  * The SRC_PCBASE allows js_DecompileValueGenerator to walk backwards from
  * JSOP_LEAVEBLOCKEXPR to the beginning of the let and is only needed for
  * let-expressions.
  */
-static bool
+/*
+ * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr12127. See
+ * the comment on EmitSwitch.
+ */
+MOZ_NEVER_INLINE static bool
 EmitLet(JSContext *cx, BytecodeEmitter *bce, ParseNode *pnLet)
 {
     JS_ASSERT(pnLet->isArity(PN_BINARY));
     ParseNode *varList = pnLet->pn_left;
     JS_ASSERT(varList->isArity(PN_LIST));
     ParseNode *letBody = pnLet->pn_right;
     JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE));
     StaticBlockObject &blockObj = letBody->pn_objbox->object->asStaticBlock();
@@ -4585,17 +4593,21 @@ EmitXMLProcessingInstruction(JSContext *
     if (!EmitIndex32(cx, JSOP_QNAMEPART, index, bce))
         return false;
     if (!EmitAtomOp(cx, pi.target(), JSOP_XMLPI, bce))
         return false;
     return true;
 }
 #endif
 
-static bool
+/*
+ * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr12127. See
+ * the comment on EmitSwitch.
+ */
+MOZ_NEVER_INLINE static bool
 EmitLexicalScope(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
     JS_ASSERT(pn->getOp() == JSOP_LEAVEBLOCK);
 
     StmtInfo stmtInfo;
     ObjectBox *objbox = pn->pn_objbox;
     StaticBlockObject &blockObj = objbox->object->asStaticBlock();
@@ -5806,17 +5818,21 @@ EmitIncOrDec(JSContext *cx, BytecodeEmit
                 if (Emit1(cx, bce, op) < 0)
                     return false;
             }
         }
     }
     return true;
 }
 
-static bool
+/*
+ * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr12127. See
+ * the comment on EmitSwitch.
+ */
+MOZ_NEVER_INLINE static bool
 EmitLabel(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     /*
      * Emit a JSOP_LABEL instruction. The argument is the offset to the statement
      * following the labeled statement. This op has either a SRC_LABEL or
      * SRC_LABELBRACE source note for the decompiler.
      */
     JSAtom *atom = pn->pn_atom;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6462,20 +6462,33 @@ JS_ClearPendingException(JSContext *cx)
 {
     AssertNoGC(cx);
     cx->clearPendingException();
 }
 
 JS_PUBLIC_API(JSBool)
 JS_ReportPendingException(JSContext *cx)
 {
-    AssertNoGC(cx);
-    CHECK_REQUEST(cx);
-
-    return js_ReportUncaughtException(cx);
+    JSBool ok;
+    bool save;
+
+    AssertNoGC(cx);
+    CHECK_REQUEST(cx);
+
+    /*
+     * Set cx->generatingError to suppress the standard error-to-exception
+     * conversion done by all {js,JS}_Report* functions except for OOM.  The
+     * cx->generatingError flag was added to suppress recursive divergence
+     * under js_ErrorToException, but it serves for our purposes here too.
+     */
+    save = cx->generatingError;
+    cx->generatingError = JS_TRUE;
+    ok = js_ReportUncaughtException(cx);
+    cx->generatingError = save;
+    return ok;
 }
 
 struct JSExceptionState {
     JSBool throwing;
     jsval  exception;
 };
 
 JS_PUBLIC_API(JSExceptionState *)
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -825,32 +825,35 @@ struct JSContext : js::ContextFriendFiel
 
   private:
     /* See JSContext::findVersion. */
     JSVersion           defaultVersion;      /* script compilation version */
     JSVersion           versionOverride;     /* supercedes defaultVersion when valid */
     bool                hasVersionOverride;
 
     /* Exception state -- the exception member is a GC root by definition. */
-    JSBool              throwing;            /* is there a pending exception? */
-    js::Value           exception;           /* most-recently-thrown exception */
+    JSBool              throwing;           /* is there a pending exception? */
+    js::Value           exception;          /* most-recently-thrown exception */
 
     /* Per-context run options. */
-    unsigned            runOptions;          /* see jsapi.h for JSOPTION_* */
+    unsigned               runOptions;            /* see jsapi.h for JSOPTION_* */
 
   public:
     int32_t             reportGranularity;  /* see jsprobes.h */
 
     /* Locale specific callbacks for string conversion. */
     JSLocaleCallbacks   *localeCallbacks;
 
     js::AutoResolving   *resolvingList;
 
-    /* True if generating an error, to prevent runaway recursion. */
-    bool                generatingError;
+    /*
+     * True if generating an error, to prevent runaway recursion.
+     * NB: generatingError packs with throwing below.
+     */
+    bool        generatingError;
 
     /* GC heap compartment. */
     JSCompartment       *compartment;
 
     inline void setCompartment(JSCompartment *compartment);
 
     /* Current execution stack. */
     js::ContextStack    stack;
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -1079,17 +1079,17 @@ js_ErrorToException(JSContext *cx, const
     JSObject *errProto, *errObject;
     JSString *messageStr, *filenameStr;
 
     /*
      * Tell our caller to report immediately if this report is just a warning.
      */
     JS_ASSERT(reportp);
     if (JSREPORT_IS_WARNING(reportp->flags))
-        return false;
+        return JS_FALSE;
 
     /* Find the exception index associated with this error. */
     errorNumber = (JSErrNum) reportp->errorNumber;
     if (!callback || callback == js_GetErrorMessage)
         errorString = js_GetLocalizedErrorMessage(cx, NULL, NULL, errorNumber);
     else
         errorString = callback(userRef, NULL, errorNumber);
     exn = errorString ? (JSExnType) errorString->exnType : JSEXN_NONE;
@@ -1102,62 +1102,78 @@ js_ErrorToException(JSContext *cx, const
             errortoexnname[errorNumber].exception);
 #endif
 
     /*
      * Return false (no exception raised) if no exception is associated
      * with the given error number.
      */
     if (exn == JSEXN_NONE)
-        return false;
+        return JS_FALSE;
 
-    /* Prevent infinite recursion. */
+    /*
+     * Prevent runaway recursion, via cx->generatingError.  If an out-of-memory
+     * error occurs, no exception object will be created, but we don't assume
+     * that OOM is the only kind of error that subroutines of this function
+     * called below might raise.
+     */
     if (cx->generatingError)
-        return false;
-    AutoScopedAssign<bool> asa(&cx->generatingError, false);
+        return JS_FALSE;
+
+    MUST_FLOW_THROUGH("out");
+    cx->generatingError = JS_TRUE;
 
     /* Protect the newly-created strings below from nesting GCs. */
     PodArrayZero(tv);
     AutoArrayRooter tvr(cx, ArrayLength(tv), tv);
 
     /*
      * Try to get an appropriate prototype by looking up the corresponding
      * exception constructor name in the scope chain of the current context's
      * top stack frame, or in the global object if no frame is active.
      */
     ok = js_GetClassPrototype(cx, NULL, GetExceptionProtoKey(exn), &errProto);
     if (!ok)
-        return false;
+        goto out;
     tv[0] = OBJECT_TO_JSVAL(errProto);
 
     errObject = NewObjectWithGivenProto(cx, &ErrorClass, errProto, NULL);
-    if (!errObject)
-        return false;
+    if (!errObject) {
+        ok = JS_FALSE;
+        goto out;
+    }
     tv[1] = OBJECT_TO_JSVAL(errObject);
 
     messageStr = JS_NewStringCopyZ(cx, message);
-    if (!messageStr)
-        return false;
+    if (!messageStr) {
+        ok = JS_FALSE;
+        goto out;
+    }
     tv[2] = STRING_TO_JSVAL(messageStr);
 
     filenameStr = JS_NewStringCopyZ(cx, reportp->filename);
-    if (!filenameStr)
-        return false;
+    if (!filenameStr) {
+        ok = JS_FALSE;
+        goto out;
+    }
     tv[3] = STRING_TO_JSVAL(filenameStr);
 
     ok = InitExnPrivate(cx, errObject, messageStr, filenameStr,
                         reportp->lineno, reportp, exn);
     if (!ok)
-        return false;
+        goto out;
 
     JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject));
 
     /* Flag the error report passed in to indicate an exception was raised. */
     reportp->flags |= JSREPORT_EXCEPTION;
-    return true;
+
+out:
+    cx->generatingError = JS_FALSE;
+    return ok;
 }
 
 JSBool
 js_ReportUncaughtException(JSContext *cx)
 {
     jsval exn;
     JSObject *exnObject;
     jsval roots[5];
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1208,35 +1208,39 @@ mjit::Compiler::ensureDoubleArguments()
     for (uint32_t i = 0; script->function() && i < script->function()->nargs; i++) {
         uint32_t slot = ArgSlot(i);
         if (a->varTypes[slot].getTypeTag(cx) == JSVAL_TYPE_DOUBLE && analysis->trackSlot(slot))
             frame.ensureDouble(frame.getArg(i));
     }
 }
 
 void
+mjit::Compiler::markUndefinedLocal(uint32_t offset, uint32_t i)
+{
+    uint32_t depth = ssa.getFrame(a->inlineIndex).depth;
+    uint32_t slot = LocalSlot(script, i);
+    Address local(JSFrameReg, sizeof(StackFrame) + (depth + i) * sizeof(Value));
+    if (!cx->typeInferenceEnabled() || !analysis->trackSlot(slot)) {
+        masm.storeValue(UndefinedValue(), local);
+    } else {
+        Lifetime *lifetime = analysis->liveness(slot).live(offset);
+        if (lifetime)
+            masm.storeValue(UndefinedValue(), local);
+    }
+}
+
+void
 mjit::Compiler::markUndefinedLocals()
 {
-    uint32_t depth = ssa.getFrame(a->inlineIndex).depth;
-
     /*
      * Set locals to undefined, as in initCallFrameLatePrologue.
      * Skip locals which aren't closed and are known to be defined before used,
      */
-    for (uint32_t i = 0; i < script->nfixed; i++) {
-        uint32_t slot = LocalSlot(script, i);
-        Address local(JSFrameReg, sizeof(StackFrame) + (depth + i) * sizeof(Value));
-        if (!cx->typeInferenceEnabled() || !analysis->trackSlot(slot)) {
-            masm.storeValue(UndefinedValue(), local);
-        } else {
-            Lifetime *lifetime = analysis->liveness(slot).live(0);
-            if (lifetime)
-                masm.storeValue(UndefinedValue(), local);
-        }
-    }
+    for (uint32_t i = 0; i < script->nfixed; i++)
+        markUndefinedLocal(0, i);
 }
 
 CompileStatus
 mjit::Compiler::generateEpilogue()
 {
     return Compile_Okay;
 }
 
@@ -3045,16 +3049,20 @@ mjit::Compiler::generateMethod()
             INLINE_STUBCALL(stubs::SetConst, REJOIN_FALLTHROUGH);
           }
           END_CASE(JSOP_SETCONST)
 
           BEGIN_CASE(JSOP_DEFLOCALFUN_FC)
           {
             uint32_t slot = GET_SLOTNO(PC);
             JSFunction *fun = script->getFunction(GET_UINT32_INDEX(PC + SLOTNO_LEN));
+
+            /* See JSOP_DEFLOCALFUN. */
+            markUndefinedLocal(PC - script->code, slot);
+
             prepareStubCall(Uses(frame.frameSlots()));
             masm.move(ImmPtr(fun), Registers::ArgReg1);
             INLINE_STUBCALL(stubs::DefLocalFun_FC, REJOIN_DEFLOCALFUN);
             frame.takeReg(Registers::ReturnReg);
             frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg);
             frame.storeLocal(slot, true);
             frame.pop();
             updateVarType();
@@ -3125,16 +3133,26 @@ mjit::Compiler::generateMethod()
             finishBarrier(barrier, REJOIN_GETTER, 0);
           }
           END_CASE(JSOP_CALLFCSLOT)
 
           BEGIN_CASE(JSOP_DEFLOCALFUN)
           {
             uint32_t slot = GET_SLOTNO(PC);
             JSFunction *fun = script->getFunction(GET_UINT32_INDEX(PC + SLOTNO_LEN));
+
+            /*
+             * The liveness analysis will report that the value in |slot| is
+             * defined at the start of this opcode. However, we don't actually
+             * fill it in until the stub returns. This will cause a problem if
+             * we GC inside the stub. So we write a safe value here so that the
+             * GC won't crash.
+             */
+            markUndefinedLocal(PC - script->code, slot);
+
             prepareStubCall(Uses(0));
             masm.move(ImmPtr(fun), Registers::ArgReg1);
             INLINE_STUBCALL(stubs::DefLocalFun, REJOIN_DEFLOCALFUN);
             frame.takeReg(Registers::ReturnReg);
             frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg);
             frame.storeLocal(slot, true);
             frame.pop();
             updateVarType();
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -569,16 +569,17 @@ private:
     void updateArithCounters(jsbytecode *pc, FrameEntry *fe,
                              JSValueType firstUseType, JSValueType secondUseType);
     void updateElemCounters(jsbytecode *pc, FrameEntry *obj, FrameEntry *id);
     void bumpPropCounter(jsbytecode *pc, int counter);
 
     /* Analysis helpers. */
     CompileStatus prepareInferenceTypes(JSScript *script, ActiveFrame *a);
     void ensureDoubleArguments();
+    void markUndefinedLocal(uint32_t offset, uint32_t i);
     void markUndefinedLocals();
     void fixDoubleTypes(jsbytecode *target);
     void watchGlobalReallocation();
     void updateVarType();
     void updateJoinVarTypes();
     void restoreVarType();
     JSValueType knownPushedType(uint32_t pushed);
     bool mayPushUndefined(uint32_t pushed);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2056,17 +2056,22 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     {
         // Unconstrain the runtime's threshold on nominal heap size, to avoid
         // triggering GC too often if operating continuously near an arbitrary
         // finite threshold (0xffffffff is infinity for uint32_t parameters).
         // This leaves the maximum-JS_malloc-bytes threshold still in effect
         // to cause period, and we hope hygienic, last-ditch GCs from within
         // the GC's allocator.
         JS_SetGCParameter(mJSRuntime, JSGC_MAX_BYTES, 0xffffffff);
+#ifdef MOZ_ASAN
+        // ASan requires more stack space due to redzones
+        JS_SetNativeStackQuota(mJSRuntime, 2 * 128 * sizeof(size_t) * 1024);
+#else  
         JS_SetNativeStackQuota(mJSRuntime, 128 * sizeof(size_t) * 1024);
+#endif
         JS_SetContextCallback(mJSRuntime, ContextCallback);
         JS_SetCompartmentCallback(mJSRuntime, CompartmentCallback);
         JS_SetGCCallback(mJSRuntime, GCCallback);
         JS_SetFinalizeCallback(mJSRuntime, FinalizeCallback);
         JS_SetExtraGCRootsTracer(mJSRuntime, TraceBlackJS, this);
         JS_SetGrayGCRootsTracer(mJSRuntime, TraceGrayJS, this);
         JS_SetWrapObjectCallbacks(mJSRuntime,
                                   xpc::WrapperFactory::Rewrap,
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -639,17 +639,17 @@ nsBidiPresUtils::Resolve(nsBlockFrame* a
     }
     if (ch != 0) {
       bpd.PushBidiControl(ch);
     }
   }
   for (nsBlockFrame* block = aBlockFrame; block;
        block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
     block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
-    nsBlockInFlowLineIterator lineIter(block, block->begin_lines(), false);
+    nsBlockInFlowLineIterator lineIter(block, block->begin_lines());
     bpd.mPrevFrame = nsnull;
     bpd.GetSubParagraph()->mPrevFrame = nsnull;
     TraverseFrames(aBlockFrame, &lineIter, block->GetFirstPrincipalChild(), &bpd);
   }
 
   if (ch != 0) {
     bpd.PopBidiControl();
   }
--- a/layout/base/tests/Makefile.in
+++ b/layout/base/tests/Makefile.in
@@ -164,16 +164,17 @@ DEFINES += -D_IMPL_NS_LAYOUT
 		test_bug558663.html \
 		test_bug559499.html \
 		test_bug569520.html \
 		test_bug582181-1.html \
 		test_bug582181-2.html \
 		test_bug588174.html \
 		test_bug607529.html \
 		file_bug607529.html \
+		test_bug667512.html \
 		test_bug677878.html \
 		test_bug696020.html \
 		test_flush_on_paint.html \
 		test_mozPaintCount.html \
 		test_scroll_selection_into_view.html \
 		test_bug583889.html \
 		bug583889_inner1.html \
 		bug583889_inner2.html \
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_bug667512.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=667512
+-->
+<head>
+  <title>Test for Bug 667512</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<table contenteditable="true"><tbody><tr><td id="b"><br id="a"></td></tr></tbody></table>
+<span style="display: list-item;direction: rtl;"></span>
+<script type="application/javascript">
+
+/** Test for Bug 667512 **/
+function appendElements() {
+  window.focus();
+  window.getSelection().collapse(document.documentElement, 0);
+
+  var x=document.getElementById('a');
+  x.parentNode.removeChild(x);
+
+  var x=document.getElementById('b');
+  x.parentNode.removeChild(x);
+
+  synthesizeKey("VK_LEFT", {});
+  synthesizeKey("VK_RIGHT", {});
+
+  ok(true, "Should not crash!");
+  SimpleTest.finish();
+}
+
+addLoadEvent(appendElements);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
\ No newline at end of file
--- a/layout/forms/nsComboboxControlFrame.cpp
+++ b/layout/forms/nsComboboxControlFrame.cpp
@@ -1319,17 +1319,17 @@ nsComboboxControlFrame::DestroyFrom(nsIF
 
   // Cleanup frames in popup child list
   mPopupFrames.DestroyFramesFrom(aDestructRoot);
   nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
   nsContentUtils::DestroyAnonymousContent(&mButtonContent);
   nsBlockFrame::DestroyFrom(aDestructRoot);
 }
 
-nsFrameList
+const nsFrameList&
 nsComboboxControlFrame::GetChildList(ChildListID aListID) const
 {
   if (kSelectPopupList == aListID) {
     return mPopupFrames;
   }
   return nsBlockFrame::GetChildList(aListID);
 }
 
--- a/layout/forms/nsComboboxControlFrame.h
+++ b/layout/forms/nsComboboxControlFrame.h
@@ -136,17 +136,17 @@ public:
   }
 
 #ifdef NS_DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const;
 #endif
   virtual void DestroyFrom(nsIFrame* aDestructRoot);
   NS_IMETHOD SetInitialChildList(ChildListID     aListID,
                                  nsFrameList&    aChildList);
-  virtual nsFrameList GetChildList(ChildListID aListID) const;
+  virtual const nsFrameList& GetChildList(ChildListID aListID) const;
   virtual void GetChildLists(nsTArray<ChildList>* aLists) const;
 
   virtual nsIFrame* GetContentInsertionFrame();
 
   // nsIFormControlFrame
   virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue);
   virtual nsresult GetFormProperty(nsIAtom* aName, nsAString& aValue) const; 
   /**
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -280,16 +280,21 @@ DestroyOverflowLines(void* aPropertyValu
 {
   NS_ERROR("Overflow lines should never be destroyed by the FramePropertyTable");
 }
 
 NS_DECLARE_FRAME_PROPERTY(LineCursorProperty, nsnull)
 NS_DECLARE_FRAME_PROPERTY(OverflowLinesProperty, DestroyOverflowLines)
 NS_DECLARE_FRAME_PROPERTY(OverflowOutOfFlowsProperty,
                           nsContainerFrame::DestroyFrameList)
+NS_DECLARE_FRAME_PROPERTY(PushedFloatProperty,
+                          nsContainerFrame::DestroyFrameList)
+NS_DECLARE_FRAME_PROPERTY(OutsideBulletProperty,
+                          nsContainerFrame::DestroyFrameList)
+NS_DECLARE_FRAME_PROPERTY(InsideBulletProperty, nsnull)
 
 //----------------------------------------------------------------------
 
 nsIFrame*
 NS_NewBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aFlags)
 {
   nsBlockFrame* it = new (aPresShell) nsBlockFrame(aContext);
   if (it) {
@@ -303,40 +308,35 @@ NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame)
 nsBlockFrame::~nsBlockFrame()
 {
 }
 
 void
 nsBlockFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   DestroyAbsoluteFrames(aDestructRoot);
-  // Outside bullets are not in our child-list so check for them here
-  // and delete them when present.
-  if (mBullet && HaveOutsideBullet()) {
-    mBullet->DestroyFrom(aDestructRoot);
-    mBullet = nsnull;
-  }
 
   mFloats.DestroyFramesFrom(aDestructRoot);
 
   nsPresContext* presContext = PresContext();
 
   nsLineBox::DeleteLineList(presContext, mLines, aDestructRoot);
   // Now clear mFrames, since we've destroyed all the frames in it.
   mFrames.Clear();
 
   nsFrameList* pushedFloats = RemovePushedFloats();
   if (pushedFloats) {
     pushedFloats->DestroyFrom(aDestructRoot);
   }
 
   // destroy overflow lines now
-  nsLineList* overflowLines = RemoveOverflowLines();
+  FrameLines* overflowLines = RemoveOverflowLines();
   if (overflowLines) {
-    nsLineBox::DeleteLineList(presContext, *overflowLines, aDestructRoot);
+    nsLineBox::DeleteLineList(presContext, overflowLines->mLines,
+                              aDestructRoot);
     delete overflowLines;
   }
 
   {
     nsAutoOOFFrameList oofs(this);
     oofs.mList.DestroyFramesFrom(aDestructRoot);
     // oofs is now empty and will remove the frame list property
   }
@@ -449,22 +449,22 @@ nsBlockFrame::List(FILE* out, PRInt32 aI
   if (!mLines.empty()) {
     const_line_iterator line = begin_lines(), line_end = end_lines();
     for ( ; line != line_end; ++line) {
       line->List(out, aIndent);
     }
   }
 
   // Output the overflow lines.
-  const nsLineList* overflowLines = GetOverflowLines();
-  if (overflowLines && !overflowLines->empty()) {
+  const FrameLines* overflowLines = GetOverflowLines();
+  if (overflowLines && !overflowLines->mLines.empty()) {
     IndentBy(out, aIndent);
     fputs("Overflow-lines<\n", out);
-    const_line_iterator line = overflowLines->begin(),
-                        line_end = overflowLines->end();
+    const_line_iterator line = overflowLines->mLines.begin(),
+                        line_end = overflowLines->mLines.end();
     for ( ; line != line_end; ++line) {
       line->List(out, aIndent + 1);
     }
     IndentBy(out, aIndent);
     fputs(">\n", out);
   }
 
   // skip the principal list - we printed the lines above
@@ -569,94 +569,119 @@ nsBlockFrame::GetCaretBaseline() const
   return nsLayoutUtils::GetCenteredFontBaseline(fm, nsHTMLReflowState::
       CalcLineHeight(GetStyleContext(), contentRect.height, inflation)) +
     bp.top;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Child frame enumeration
 
-nsFrameList
+const nsFrameList&
 nsBlockFrame::GetChildList(ChildListID aListID) const
 {
   switch (aListID) {
     case kPrincipalList:
       return mFrames;
     case kOverflowList: {
-      // XXXbz once we start using nsFrameList for our overflow list, we
-      // could switch GetChildList to returning a |const nsFrameList&|.
-      nsLineList* overflowLines = GetOverflowLines();
-      return overflowLines ? nsFrameList(overflowLines->front()->mFirstChild,
-                                         overflowLines->back()->LastChild())
-                           : nsFrameList::EmptyList();
+      FrameLines* overflowLines = GetOverflowLines();
+      return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList();
     }
     case kFloatList:
       return mFloats;
     case kOverflowOutOfFlowList: {
       const nsFrameList* list = GetOverflowOutOfFlows();
       return list ? *list : nsFrameList::EmptyList();
     }
     case kPushedFloatsList: {
       const nsFrameList* list = GetPushedFloats();
       return list ? *list : nsFrameList::EmptyList();
     }
-    case kBulletList:
-      return HaveOutsideBullet() ? nsFrameList(mBullet, mBullet)
-                                 : nsFrameList::EmptyList();
+    case kBulletList: {
+      const nsFrameList* list = GetOutsideBulletList();
+      return list ? *list : nsFrameList::EmptyList();
+    }
     default:
       return nsContainerFrame::GetChildList(aListID);
   }
 }
 
 void
 nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const
 {
   nsContainerFrame::GetChildLists(aLists);
-  nsLineList* overflowLines = GetOverflowLines();
-  if (overflowLines && overflowLines->front()->mFirstChild) {
-    nsFrameList overflowList(overflowLines->front()->mFirstChild,
-                             overflowLines->back()->LastChild());
-    overflowList.AppendIfNonempty(aLists, kOverflowList);
+  FrameLines* overflowLines = GetOverflowLines();
+  if (overflowLines) {
+    overflowLines->mFrames.AppendIfNonempty(aLists, kOverflowList);
   }
   const nsFrameList* list = GetOverflowOutOfFlows();
   if (list) {
     list->AppendIfNonempty(aLists, kOverflowOutOfFlowList);
   }
   mFloats.AppendIfNonempty(aLists, kFloatList);
-  if (HaveOutsideBullet()) {
-    nsFrameList bullet(mBullet, mBullet);
-    bullet.AppendIfNonempty(aLists, kBulletList);
+  list = GetOutsideBulletList();
+  if (list) {
+    list->AppendIfNonempty(aLists, kBulletList);
   }
   list = GetPushedFloats();
   if (list) {
     list->AppendIfNonempty(aLists, kPushedFloatsList);
   }
 }
 
 /* virtual */ bool
 nsBlockFrame::IsFloatContainingBlock() const
 {
   return true;
 }
 
-static void ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent,
-                          nsIFrame* aNewParent) {
+static void
+ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent, nsIFrame* aNewParent)
+{
   NS_ASSERTION(aOldParent == aFrame->GetParent(),
                "Parent not consistent with expectations");
 
   aFrame->SetParent(aNewParent);
 
   // When pushing and pulling frames we need to check for whether any
   // views need to be reparented
   nsContainerFrame::ReparentFrameView(aFrame->PresContext(), aFrame,
                                       aOldParent, aNewParent);
 }
  
-//////////////////////////////////////////////////////////////////////
-// Frame structure methods
+static void
+ReparentFrames(nsFrameList& aFrameList, nsIFrame* aOldParent,
+               nsIFrame* aNewParent)
+{
+  for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
+    ReparentFrame(e.get(), aOldParent, aNewParent);
+  }
+}
+ 
+/**
+ * Remove the first line from aFromLines and adjust the associated frame list
+ * aFromFrames accordingly.  The removed line is assigned to *aOutLine and
+ * a frame list with its frames is assigned to *aOutFrames, i.e. the frames
+ * that were extracted from the head of aFromFrames.
+ * aFromLines must contain at least one line, the line may be empty.
+ * @return true if aFromLines becomes empty
+ */
+static bool
+RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames,
+                nsLineBox** aOutLine, nsFrameList* aOutFrames)
+{
+  nsLineList_iterator removedLine = aFromLines.begin();
+  *aOutLine = removedLine;
+  nsLineList_iterator next = aFromLines.erase(removedLine);
+  bool isLastLine = next == aFromLines.end();
+  nsIFrame* lastFrame = isLastLine ? aFromFrames.LastChild()
+                                   : next->mFirstChild->GetPrevSibling();
+  nsFrameList::FrameLinkEnumerator linkToBreak(aFromFrames, lastFrame);
+  *aOutFrames = aFromFrames.ExtractHead(linkToBreak);
+  return isLastLine;
+}
 
 //////////////////////////////////////////////////////////////////////
 // Reflow methods
 
 /* virtual */ void
 nsBlockFrame::MarkIntrinsicWidthsDirty()
 {
   nsBlockFrame* dirtyBlock = static_cast<nsBlockFrame*>(GetFirstContinuation());
@@ -1054,17 +1079,17 @@ nsBlockFrame::Reflow(nsPresContext*     
   // we need to continue, too.
   if (NS_UNCONSTRAINEDSIZE != reflowState->availableHeight &&
       NS_FRAME_IS_COMPLETE(state.mReflowStatus) &&
       state.mFloatManager->ClearContinues(FindTrailingClear())) {
     NS_FRAME_SET_INCOMPLETE(state.mReflowStatus);
   }
 
   if (!NS_FRAME_IS_FULLY_COMPLETE(state.mReflowStatus)) {
-    if (GetOverflowLines() || GetPushedFloats()) {
+    if (HasOverflowLines() || HasPushedFloats()) {
       state.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
     }
 
 #ifdef DEBUG_kipp
     ListTag(stdout); printf(": block is not fully complete\n");
 #endif
   }
 
@@ -1077,42 +1102,43 @@ nsBlockFrame::Reflow(nsPresContext*     
   // participates in the height calculation of the list-item box's
   // first line box.
   //
   // There are exactly two places a bullet can be placed: near the
   // first or second line. It's only placed on the second line in a
   // rare case: an empty first line followed by a second line that
   // contains a block (example: <LI>\n<P>... ). This is where
   // the second case can happen.
-  if (mBullet && HaveOutsideBullet() && !mLines.empty() &&
+  if (HasOutsideBullet() && !mLines.empty() &&
       (mLines.front()->IsBlock() ||
        (0 == mLines.front()->mBounds.height &&
         mLines.front() != mLines.back() &&
         mLines.begin().next()->IsBlock()))) {
     // Reflow the bullet
     nsHTMLReflowMetrics metrics;
     // XXX Use the entire line when we fix bug 25888.
     nsLayoutUtils::LinePosition position;
     bool havePosition = nsLayoutUtils::GetFirstLinePosition(this, &position);
     nscoord lineTop = havePosition ? position.mTop
                                    : reflowState->mComputedBorderPadding.top;
-    ReflowBullet(state, metrics, lineTop);
+    nsIFrame* bullet = GetOutsideBullet();
+    ReflowBullet(bullet, state, metrics, lineTop);
     NS_ASSERTION(!BulletIsEmpty() || metrics.height == 0,
                  "empty bullet took up space");
 
     if (havePosition && !BulletIsEmpty()) {
       // We have some lines to align the bullet with.  
 
       // Doing the alignment using the baseline will also cater for
       // bullets that are placed next to a child block (bug 92896)
     
       // Tall bullets won't look particularly nice here...
-      nsRect bbox = mBullet->GetRect();
+      nsRect bbox = bullet->GetRect();
       bbox.y = position.mBaseline - metrics.ascent;
-      mBullet->SetRect(bbox);
+      bullet->SetRect(bbox);
     }
     // Otherwise just leave the bullet where it is, up against our top padding.
   }
 
   // Compute our final size
   nscoord bottomEdgeOfChildren;
   ComputeFinalSize(*reflowState, state, aMetrics, &bottomEdgeOfChildren);
   nsRect areaBounds = nsRect(0, 0, aMetrics.width, aMetrics.height);
@@ -1458,23 +1484,24 @@ nsBlockFrame::ComputeOverflowAreas(const
   nsOverflowAreas areas(aBounds, aBounds);
   if (!ApplyOverflowClipping(this, aDisplay)) {
     for (line_iterator line = begin_lines(), line_end = end_lines();
          line != line_end;
          ++line) {
       areas.UnionWith(line->GetOverflowAreas());
     }
 
-    // Factor the bullet in; normally the bullet will be factored into
+    // Factor an outside bullet in; normally the bullet will be factored into
     // the line-box's overflow areas. However, if the line is a block
     // line then it won't; if there are no lines, it won't. So just
     // factor it in anyway (it can't hurt if it was already done).
     // XXXldb Can we just fix GetOverflowArea instead?
-    if (mBullet) {
-      areas.UnionAllWith(mBullet->GetRect());
+    nsIFrame* outsideBullet = GetOutsideBullet();
+    if (outsideBullet) {
+      areas.UnionAllWith(outsideBullet->GetRect());
     }
 
     // Factor in the bottom edge of the children.  Child frames will be added
     // to the overflow area as we iterate through the lines, but their margins
     // won't, so we need to account for bottom margins here.
     // REVIEW: For now, we do this for both visual and scrollable area,
     // although when we make scrollable overflow area not be a subset of
     // visual, we can change this.
@@ -2185,17 +2212,17 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
     // We'll place lineIter at the last line of this block, so that 
     // nsBlockInFlowLineIterator::Next() will take us to the first
     // line of my next-in-flow-chain.  (But first, check that I 
     // have any lines -- if I don't, just bail out of this
     // optimization.) 
     line_iterator lineIter = this->end_lines();
     if (lineIter != this->begin_lines()) {
       lineIter--; // I have lines; step back from dummy iterator to last line.
-      nsBlockInFlowLineIterator bifLineIter(this, lineIter, false);
+      nsBlockInFlowLineIterator bifLineIter(this, lineIter);
 
       // Check for next-in-flow-chain's first line.
       // (First, see if there is such a line, and second, see if it's clean)
       if (!bifLineIter.Next() ||                
           !bifLineIter.GetLine()->IsDirty()) {
         skipPull=true;
       }
     }
@@ -2207,102 +2234,69 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
       NS_FRAME_SET_OVERFLOW_INCOMPLETE(aState.mReflowStatus);
     else
       NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
   }
   
   if (!skipPull && aState.mNextInFlow) {
     // Pull data from a next-in-flow if there's still room for more
     // content here.
-    while (keepGoing && (nsnull != aState.mNextInFlow)) {
+    while (keepGoing && aState.mNextInFlow) {
       // Grab first line from our next-in-flow
       nsBlockFrame* nextInFlow = aState.mNextInFlow;
-      line_iterator nifLine = nextInFlow->begin_lines();
-      nsLineBox *toMove;
-      bool toMoveIsOverflowLine;
-      if (nifLine != nextInFlow->end_lines()) {
-        toMove = nifLine;
-        nextInFlow->mLines.erase(nifLine);
-        toMoveIsOverflowLine = false;
+      nsLineBox* pulledLine;
+      nsFrameList pulledFrames;
+      bool isOverflowLine = false;
+      if (!nextInFlow->mLines.empty()) {
+        RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames,
+                        &pulledLine, &pulledFrames);
       } else {
         // Grab an overflow line if there are any
-        nsLineList* overflowLines = nextInFlow->GetOverflowLines();
+        FrameLines* overflowLines = nextInFlow->GetOverflowLines();
         if (!overflowLines) {
           aState.mNextInFlow =
             static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
           continue;
         }
-        nifLine = overflowLines->begin();
-        NS_ASSERTION(nifLine != overflowLines->end(),
-                     "Stored overflow line list should not be empty");
-        toMove = nifLine;
-        nextInFlow->RemoveOverflowLines();
-        nifLine = overflowLines->erase(nifLine);
-        if (nifLine != overflowLines->end()) {
-          // We need to this remove-and-put-back dance because we want
-          // to avoid making the overflow line list empty while it's
-          // stored in the property (because the property has the
-          // invariant that the list is never empty).
-          nextInFlow->SetOverflowLines(overflowLines);
-        } else {
-          delete overflowLines;
+        bool last =
+          RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
+                          &pulledLine, &pulledFrames);
+        if (last) {
+          nextInFlow->DestroyOverflowLines();
         }
-        toMoveIsOverflowLine = true;
+        isOverflowLine = true;
       }
 
-      if (0 == toMove->GetChildCount()) {
+      if (pulledFrames.IsEmpty()) {
         // The line is empty. Try the next one.
-        NS_ASSERTION(nsnull == toMove->mFirstChild, "bad empty line");
-        aState.FreeLineBox(toMove);
+        NS_ASSERTION(pulledLine->GetChildCount() == 0 &&
+                     !pulledLine->mFirstChild, "bad empty line");
+        aState.FreeLineBox(pulledLine);
         continue;
       }
 
-      // XXX move to a subroutine: run-in, overflow, pullframe and this do this
-      // Make the children in the line ours.
-      nsIFrame* frame = toMove->mFirstChild;
-      nsIFrame* lastFrame = nsnull;
-      PRInt32 n = toMove->GetChildCount();
-      while (--n >= 0) {
-        ReparentFrame(frame, nextInFlow, this);
-        lastFrame = frame;
-        frame = frame->GetNextSibling();
-      }
-
-      NS_ASSERTION(lastFrame == toMove->LastChild(), "Unexpected lastFrame");
-
+      ReparentFrames(pulledFrames, nextInFlow, this);
+
+      NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(),
+                   "Unexpected last frame");
       NS_ASSERTION(aState.mPrevChild || mLines.empty(), "should have a prevchild here");
-
       NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
                    "Incorrect aState.mPrevChild before inserting line at end");
 
-      // Shift toMove's frames into our mFrames list.
-      if (toMoveIsOverflowLine) {
-        // Pulling from an overflow list
-        // XXXbz If we switch overflow lines to nsFrameList, we should
-        // change this SetNextSibling call.
-        lastFrame->SetNextSibling(nsnull);
-      } else {
-        // Pulling from nextInFlow->mFrames
-        nsFrameList::FrameLinkEnumerator linkToBreak(nextInFlow->mFrames, lastFrame);
-        nextInFlow->mFrames.ExtractHead(linkToBreak);
-      }
-      nsFrameList newFrames(toMove->mFirstChild, lastFrame);
-      mFrames.AppendFrames(nsnull, newFrames);
+      // Shift pulledLine's frames into our mFrames list.
+      mFrames.AppendFrames(nsnull, pulledFrames);
 
       // Add line to our line list, and set its last child as our new prev-child
-      line = mLines.before_insert(end_lines(), toMove);
-      aState.mPrevChild = lastFrame;
-
-      NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
-                   "Incorrect aState.mPrevChild after inserting line at end");
+      line = mLines.before_insert(end_lines(), pulledLine);
+      aState.mPrevChild = mFrames.LastChild();
 
       // Reparent floats whose placeholders are in the line.
-      ReparentFloats(toMove->mFirstChild, nextInFlow, toMoveIsOverflowLine, true);
-
-      DumpLine(aState, toMove, deltaY, 0);
+      ReparentFloats(pulledLine->mFirstChild, nextInFlow, isOverflowLine, true);
+
+      DumpLine(aState, pulledLine, deltaY, 0);
 #ifdef DEBUG
       AutoNoisyIndenter indent2(gNoisyReflow);
 #endif
 
       if (aState.mPresContext->HasPendingInterrupt()) {
         MarkLineDirtyForInterrupt(line);
       } else {
         // Now reflow it and any lines that it makes during it's reflow
@@ -2345,29 +2339,30 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
     }
 
     if (NS_FRAME_IS_NOT_COMPLETE(aState.mReflowStatus)) {
       aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
     } //XXXfr shouldn't set this flag when nextinflow has no lines
   }
 
   // Handle an odd-ball case: a list-item with no lines
-  if (mBullet && HaveOutsideBullet() && mLines.empty()) {
+  if (HasOutsideBullet() && mLines.empty()) {
     nsHTMLReflowMetrics metrics;
-    ReflowBullet(aState, metrics,
+    nsIFrame* bullet = GetOutsideBullet();
+    ReflowBullet(bullet, aState, metrics,
                  aState.mReflowState.mComputedBorderPadding.top);
     NS_ASSERTION(!BulletIsEmpty() || metrics.height == 0,
                  "empty bullet took up space");
 
     if (!BulletIsEmpty()) {
       // There are no lines so we have to fake up some y motion so that
       // we end up with *some* height.
 
       if (metrics.ascent == nsHTMLReflowMetrics::ASK_FOR_BASELINE &&
-          !nsLayoutUtils::GetFirstLineBaseline(mBullet, &metrics.ascent)) {
+          !nsLayoutUtils::GetFirstLineBaseline(bullet, &metrics.ascent)) {
         metrics.ascent = metrics.height;
       }
 
       nsRefPtr<nsFontMetrics> fm;
       nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
         nsLayoutUtils::FontSizeInflationFor(this, nsLayoutUtils::eInReflow));
       aState.mReflowState.rendContext->SetFont(fm); // FIXME: needed?
 
@@ -2375,17 +2370,17 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
         nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight);
       nscoord minDescent = aState.mMinLineHeight - minAscent;
 
       aState.mY += NS_MAX(minAscent, metrics.ascent) +
                    NS_MAX(minDescent, metrics.height - metrics.ascent);
 
       nscoord offset = minAscent - metrics.ascent;
       if (offset > 0) {
-        mBullet->SetRect(mBullet->GetRect() + nsPoint(0, offset));
+        bullet->SetRect(bullet->GetRect() + nsPoint(0, offset));
       }
     }
   }
 
   if (foundAnyClears) {
     AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
   } else {
     RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
@@ -2589,49 +2584,52 @@ nsBlockFrame::ReflowLine(nsBlockReflowSt
 }
 
 nsIFrame*
 nsBlockFrame::PullFrame(nsBlockReflowState& aState,
                         line_iterator       aLine)
 {
   // First check our remaining lines.
   if (end_lines() != aLine.next()) {
-    return PullFrameFrom(aState, aLine, this, false, aLine.next());
+    return PullFrameFrom(aState, aLine, this, false, mFrames, aLine.next());
   }
 
   NS_ASSERTION(!GetOverflowLines(),
     "Our overflow lines should have been removed at the start of reflow");
 
   // Try each next-in-flow.
   nsBlockFrame* nextInFlow = aState.mNextInFlow;
   while (nextInFlow) {
     // first normal lines, then overflow lines
     if (!nextInFlow->mLines.empty()) {
       return PullFrameFrom(aState, aLine, nextInFlow, false,
+                           nextInFlow->mFrames,
                            nextInFlow->mLines.begin());
     }
 
-    nsLineList* overflowLines = nextInFlow->GetOverflowLines();
+    FrameLines* overflowLines = nextInFlow->GetOverflowLines();
     if (overflowLines) {
       return PullFrameFrom(aState, aLine, nextInFlow, true,
-                           overflowLines->begin());
+                           overflowLines->mFrames,
+                           overflowLines->mLines.begin());
     }
 
     nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
     aState.mNextInFlow = nextInFlow;
   }
 
   return nsnull;
 }
 
 nsIFrame*
 nsBlockFrame::PullFrameFrom(nsBlockReflowState&  aState,
                             nsLineBox*           aLine,
                             nsBlockFrame*        aFromContainer,
                             bool                 aFromOverflowLine,
+                            nsFrameList&         aFromFrameList,
                             nsLineList::iterator aFromLine)
 {
   nsLineBox* fromLine = aFromLine;
   NS_ABORT_IF_FALSE(fromLine, "bad line to pull from");
   NS_ABORT_IF_FALSE(fromLine->GetChildCount(), "empty line");
   NS_ABORT_IF_FALSE(aLine->GetChildCount(), "empty line");
 
   NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->GetStyleDisplay()->IsBlockOutside(),
@@ -2648,26 +2646,22 @@ nsBlockFrame::PullFrameFrom(nsBlockReflo
   nsIFrame* newFirstChild = frame->GetNextSibling();
 
   if (aFromContainer != this) {
     NS_ASSERTION(aState.mPrevChild == aLine->LastChild(),
       "mPrevChild should be the LastChild of the line we are adding to");
     // The frame is being pulled from a next-in-flow; therefore we
     // need to add it to our sibling list.
     if (NS_LIKELY(!aFromOverflowLine)) {
+      NS_ASSERTION(&aFromFrameList == &aFromContainer->mFrames,
+                   "must be normal flow if not overflow line");
       NS_ASSERTION(aFromLine == aFromContainer->mLines.begin(),
                    "should only pull from first line");
-      // Pulling from the next-in-flow's normal line list
-      aFromContainer->mFrames.RemoveFrame(frame);
-    } else {
-      // Pulling from the next-in-flow's overflow list
-      // XXXbz If we switch overflow lines to nsFrameList, we should
-      // change this SetNextSibling call.
-      frame->SetNextSibling(nsnull);
-    }
+    }
+    aFromFrameList.RemoveFrame(frame);
 
     // When pushing and pulling frames we need to check for whether any
     // views need to be reparented
     NS_ASSERTION(frame->GetParent() == aFromContainer, "unexpected parent frame");
 
     ReparentFrame(frame, aFromContainer, this);
     mFrames.InsertFrame(nsnull, aState.mPrevChild, frame);
 
@@ -2687,33 +2681,34 @@ nsBlockFrame::PullFrameFrom(nsBlockReflo
     fromLine->mFirstChild = newFirstChild;
   }
   else {
     // Free up the fromLine now that it's empty
     // Its bounds might need to be redrawn, though.
     // XXX WHY do we invalidate the bounds AND the combined area? doesn't
     // the combined area always enclose the bounds?
     Invalidate(fromLine->mBounds);
-    nsLineList* fromLineList = aFromOverflowLine
-      ? aFromContainer->RemoveOverflowLines()
-      : &aFromContainer->mLines;
+    FrameLines* overflowLines =
+      aFromOverflowLine ? aFromContainer->RemoveOverflowLines() : nsnull;
+    nsLineList* fromLineList =
+      aFromOverflowLine ? &overflowLines->mLines : &aFromContainer->mLines;
     if (aFromLine.next() != fromLineList->end())
       aFromLine.next()->MarkPreviousMarginDirty();
 
     Invalidate(fromLine->GetVisualOverflowArea());
     fromLineList->erase(aFromLine);
     // aFromLine is now invalid
     aState.FreeLineBox(fromLine);
 
     // Put any remaining overflow lines back.
     if (aFromOverflowLine) {
       if (!fromLineList->empty()) {
-        aFromContainer->SetOverflowLines(fromLineList);
+        aFromContainer->SetOverflowLines(overflowLines);
       } else {
-        delete fromLineList;
+        delete overflowLines;
         // Now any iterators into fromLineList are invalid (but
         // aFromLine already was invalidated above)
       }
     }
   }
 
 #ifdef DEBUG
   VerifyLines(true);
@@ -2861,17 +2856,17 @@ nsBlockFrame::IsSelfEmpty()
   const nsStylePadding* padding = GetStylePadding();
   if (border->GetActualBorderWidth(NS_SIDE_TOP) != 0 ||
       border->GetActualBorderWidth(NS_SIDE_BOTTOM) != 0 ||
       !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetTop()) ||
       !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBottom())) {
     return false;
   }
 
-  if (HaveOutsideBullet() && !BulletIsEmpty()) {
+  if (HasOutsideBullet() && !BulletIsEmpty()) {
     return false;
   }
 
   return true;
 }
 
 bool
 nsBlockFrame::CachedIsEmpty()
@@ -3775,25 +3770,25 @@ nsBlockFrame::DoReflowInlineFrames(nsBlo
 #ifdef DEBUG
   if (gNoisyReflow) {
     printf("Line reflow status = %s\n", LineReflowStatusNames[lineReflowStatus]);
   }
 #endif
 
   if (aLineLayout.GetDirtyNextLine()) {
     // aLine may have been pushed to the overflow lines.
-    nsLineList* overflowLines = GetOverflowLines();
+    FrameLines* overflowLines = GetOverflowLines();
     // We can't just compare iterators front() to aLine here, since they may be in
     // different lists.
     bool pushedToOverflowLines = overflowLines &&
-      overflowLines->front() == aLine.get();
+      overflowLines->mLines.front() == aLine.get();
     if (pushedToOverflowLines) {
       // aLine is stale, it's associated with the main line list but it should
       // be associated with the overflow line list now
-      aLine = overflowLines->begin();
+      aLine = overflowLines->mLines.begin();
     }
     nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines);
     if (iter.Next() && iter.GetLine()->IsInline()) {
       iter.GetLine()->MarkDirty();
       if (iter.GetContainer() != this) {
         aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
       }
     }
@@ -4187,27 +4182,28 @@ nsBlockFrame::PlaceLine(nsBlockReflowSta
   // According to the CSS2 spec, section 12.6.1, the "marker" box
   // participates in the height calculation of the list-item box's
   // first line box.
   //
   // There are exactly two places a bullet can be placed: near the
   // first or second line. It's only placed on the second line in a
   // rare case: when the first line is empty.
   bool addedBullet = false;
-  if (mBullet && HaveOutsideBullet() &&
+  if (HasOutsideBullet() &&
       ((aLine == mLines.front() &&
         (!aLineLayout.IsZeroHeight() || (aLine == mLines.back()))) ||
        (mLines.front() != mLines.back() &&
         0 == mLines.front()->mBounds.height &&
         aLine == mLines.begin().next()))) {
     nsHTMLReflowMetrics metrics;
-    ReflowBullet(aState, metrics, aState.mY);
+    nsIFrame* bullet = GetOutsideBullet();
+    ReflowBullet(bullet, aState, metrics, aState.mY);
     NS_ASSERTION(!BulletIsEmpty() || metrics.height == 0,
                  "empty bullet took up space");
-    aLineLayout.AddBulletFrame(mBullet, metrics);
+    aLineLayout.AddBulletFrame(bullet, metrics);
     addedBullet = true;
   }
   aLineLayout.VerticalAlignLine();
 
   // We want to compare to the available space that we would have had in
   // the line's height *before* we placed any floats in the line itself.
   // Floats that are in the line are handled during line reflow (and may
   // result in floats being pushed to below the line or (I HOPE???) in a
@@ -4280,17 +4276,17 @@ nsBlockFrame::PlaceLine(nsBlockReflowSta
 #endif // IBMBIDI
 
   // From here on, pfd->mBounds rectangles are incorrect because bidi
   // might have moved frames around!
   nsOverflowAreas overflowAreas;
   aLineLayout.RelativePositionFrames(overflowAreas);
   aLine->SetOverflowAreas(overflowAreas);
   if (addedBullet) {
-    aLineLayout.RemoveBulletFrame(mBullet);
+    aLineLayout.RemoveBulletFrame(GetOutsideBullet());
   }
 
   // Inline lines do not have margins themselves; however they are
   // impacted by prior block margins. If this line ends up having some
   // height then we zero out the previous bottom margin value that was
   // already applied to the line's starting Y coordinate. Otherwise we
   // leave it be so that the previous blocks bottom margin can be
   // collapsed with a block that follows.
@@ -4381,16 +4377,20 @@ nsBlockFrame::PlaceLine(nsBlockReflowSta
   }
   return true;
 }
 
 void
 nsBlockFrame::PushLines(nsBlockReflowState&  aState,
                         nsLineList::iterator aLineBefore)
 {
+  // NOTE: aLineBefore is always a normal line, not an overflow line.
+  // The following expression will assert otherwise.
+  DebugOnly<bool> check = aLineBefore == mLines.begin();
+
   nsLineList::iterator overBegin(aLineBefore.next());
 
   // PushTruncatedPlaceholderLine sometimes pushes the first line.  Ugh.
   bool firstLine = overBegin == begin_lines();
 
   if (overBegin != end_lines()) {
     // Remove floats in the lines from mFloats
     nsFrameList floats;
@@ -4402,62 +4402,57 @@ nsBlockFrame::PushLines(nsBlockReflowSta
       oofs.mList.InsertFrames(nsnull, nsnull, floats);
     }
 
     // overflow lines can already exist in some cases, in particular,
     // when shrinkwrapping and we discover that the shrinkwap causes
     // the height of some child block to grow which creates additional
     // overflowing content. In such cases we must prepend the new
     // overflow to the existing overflow.
-    nsLineList* overflowLines = RemoveOverflowLines();
+    FrameLines* overflowLines = RemoveOverflowLines();
     if (!overflowLines) {
       // XXXldb use presshell arena!
-      overflowLines = new nsLineList();
+      overflowLines = new FrameLines();
     }
     if (overflowLines) {
-      // First, remove the frames we're pushing from mFrames
-      nsIFrame* oldLastChild = mFrames.LastChild();
+      nsIFrame* lineBeforeLastFrame;
       if (firstLine) {
-        mFrames.Clear();
+        lineBeforeLastFrame = nsnull; // removes all frames
       } else {
         nsIFrame* f = overBegin->mFirstChild;
-        nsIFrame* lineBeforeLastFrame =
-          f ? f->GetPrevSibling() : aLineBefore->LastChild();
+        lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild();
         NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(),
                      "unexpected line frames");
-        mFrames.RemoveFramesAfter(lineBeforeLastFrame);
       }
-      if (!overflowLines->empty()) {
-        // XXXbz If we switch overflow lines to nsFrameList, we should
-        // change this SetNextSibling call.
-        oldLastChild->SetNextSibling(overflowLines->front()->mFirstChild);
-      }
-      overflowLines->splice(overflowLines->begin(), mLines, overBegin,
-                            end_lines());
-      NS_ASSERTION(!overflowLines->empty(), "should not be empty");
+      nsFrameList pushedFrames = mFrames.RemoveFramesAfter(lineBeforeLastFrame);
+      overflowLines->mFrames.InsertFrames(nsnull, nsnull, pushedFrames);
+
+      overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines,
+                                    overBegin, end_lines());
+      NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty");
       // this takes ownership but it won't delete it immediately so we
       // can keep using it.
       SetOverflowLines(overflowLines);
   
       // Mark all the overflow lines dirty so that they get reflowed when
       // they are pulled up by our next-in-flow.
 
       // XXXldb Can this get called O(N) times making the whole thing O(N^2)?
-      for (line_iterator line = overflowLines->begin(),
-             line_end = overflowLines->end();
+      for (line_iterator line = overflowLines->mLines.begin(),
+             line_end = overflowLines->mLines.end();
            line != line_end;
            ++line)
-        {
-          line->MarkDirty();
-          line->MarkPreviousMarginDirty();
-          line->mBounds.SetRect(0, 0, 0, 0);
-          if (line->HasFloats()) {
-            line->FreeFloats(aState.mFloatCacheFreeList);
-          }
+      {
+        line->MarkDirty();
+        line->MarkPreviousMarginDirty();
+        line->mBounds.SetRect(0, 0, 0, 0);
+        if (line->HasFloats()) {
+          line->FreeFloats(aState.mFloatCacheFreeList);
         }
+      }
     }
   }
 
 #ifdef DEBUG
   VerifyOverflowSituation();
 #endif
 }
 
@@ -4466,41 +4461,33 @@ nsBlockFrame::PushLines(nsBlockReflowSta
 // the invariant that the property is never set if the list is empty.
 
 bool
 nsBlockFrame::DrainOverflowLines()
 {
 #ifdef DEBUG
   VerifyOverflowSituation();
 #endif
-  nsLineList* overflowLines = nsnull;
-  nsLineList* ourOverflowLines = nsnull;
+  FrameLines* overflowLines = nsnull;
+  FrameLines* ourOverflowLines = nsnull;
 
   // First grab the prev-in-flows overflow lines
   nsBlockFrame* prevBlock = (nsBlockFrame*) GetPrevInFlow();
   if (prevBlock) {
     overflowLines = prevBlock->RemoveOverflowLines();
     if (overflowLines) {
-      NS_ASSERTION(! overflowLines->empty(),
+      NS_ASSERTION(!overflowLines->mLines.empty(),
                    "overflow lines should never be set and empty");
-      // Make all the frames on the overflow line list mine
-      nsIFrame* frame = overflowLines->front()->mFirstChild;
-      while (nsnull != frame) {
-        ReparentFrame(frame, prevBlock, this);
-
-        // Get the next frame
-        frame = frame->GetNextSibling();
-      }
-
-      // make the overflow out-of-flow frames mine too
+      // Make all the frames on the overflow line list mine.
+      ReparentFrames(overflowLines->mFrames, prevBlock, this);
+
+      // Make the overflow out-of-flow frames mine too.
       nsAutoOOFFrameList oofs(prevBlock);
       if (oofs.mList.NotEmpty()) {
-        for (nsIFrame* f = oofs.mList.FirstChild(); f; f = f->GetNextSibling()) {
-          ReparentFrame(f, prevBlock, this);
-        }
+        ReparentFrames(oofs.mList, prevBlock, this);
         mFloats.InsertFrames(nsnull, nsnull, oofs.mList);
       }
     }
     
     // The lines on the overflow list have already been marked dirty and their
     // previous margins marked dirty also.
   }
 
@@ -4517,45 +4504,37 @@ nsBlockFrame::DrainOverflowLines()
 
   if (!overflowLines && !ourOverflowLines) {
     // nothing to do; always the case for non-constrained-height reflows
     return false;
   }
 
   // Now join the line lists into mLines
   if (overflowLines) {
-    if (!overflowLines->empty()) {
+    if (!overflowLines->mLines.empty()) {
       // Join the line lists
       if (!mLines.empty()) {
           // Remember to recompute the margins on the first line. This will
           // also recompute the correct deltaY if necessary.
           mLines.front()->MarkPreviousMarginDirty();
       }
       
       // Join the sibling lists together
-      nsIFrame* firstFrame = overflowLines->front()->mFirstChild;
-      nsIFrame* lastFrame = overflowLines->back()->LastChild();
-      nsFrameList framesToInsert(firstFrame, lastFrame);
-      mFrames.InsertFrames(nsnull, nsnull, framesToInsert);
+      mFrames.InsertFrames(nsnull, nsnull, overflowLines->mFrames);
 
       // Place overflow lines at the front of our line list
-      mLines.splice(mLines.begin(), *overflowLines);
-      NS_ASSERTION(overflowLines->empty(), "splice should empty list");
+      mLines.splice(mLines.begin(), overflowLines->mLines);
+      NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list");
     }
     delete overflowLines;
   }
   if (ourOverflowLines) {
-    if (!ourOverflowLines->empty()) {
-      nsIFrame* firstFrame = ourOverflowLines->front()->mFirstChild;
-      nsIFrame* lastFrame = ourOverflowLines->back()->LastChild();
-      nsFrameList framesToAppend(firstFrame, lastFrame);
-      mFrames.AppendFrames(nsnull, framesToAppend);
-
-      // append the overflow to mLines
-      mLines.splice(mLines.end(), *ourOverflowLines);
+    if (!ourOverflowLines->mLines.empty()) {
+      mFrames.AppendFrames(nsnull, ourOverflowLines->mFrames);
+      mLines.splice(mLines.end(), ourOverflowLines->mLines);
     }
     delete ourOverflowLines;
   }
 
   return true;
 }
 
 // This function assumes our prev-in-flow has completed reflow and its
@@ -4581,59 +4560,75 @@ nsBlockFrame::DrainPushedFloats(nsBlockR
   if (list) {
     if (list->NotEmpty()) {
       mFloats.InsertFrames(this, nsnull, *list);
     }
     delete list;
   }
 }
 
-nsLineList*
+nsBlockFrame::FrameLines*
 nsBlockFrame::GetOverflowLines() const
 {
-  if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES)) {
+  if (!HasOverflowLines()) {
     return nsnull;
   }
-  nsLineList* lines = static_cast<nsLineList*>
-    (Properties().Get(OverflowLinesProperty()));
-  NS_ASSERTION(lines && !lines->empty(),
+  FrameLines* prop =
+    static_cast<FrameLines*>(Properties().Get(OverflowLinesProperty()));
+  NS_ASSERTION(prop && !prop->mLines.empty() &&
+               prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
                "value should always be stored and non-empty when state set");
-  return lines;
-}
-
-nsLineList*
+  return prop;
+}
+
+nsBlockFrame::FrameLines*
 nsBlockFrame::RemoveOverflowLines()
 {
-  if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES)) {
+  if (!HasOverflowLines()) {
     return nsnull;
   }
-  nsLineList* lines = static_cast<nsLineList*>
-    (Properties().Remove(OverflowLinesProperty()));
-  NS_ASSERTION(lines && !lines->empty(),
+  FrameLines* prop =
+    static_cast<FrameLines*>(Properties().Remove(OverflowLinesProperty()));
+  NS_ASSERTION(prop && !prop->mLines.empty() &&
+               prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
                "value should always be stored and non-empty when state set");
   RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
-  return lines;
+  return prop;
+}
+
+void
+nsBlockFrame::DestroyOverflowLines()
+{
+  NS_ASSERTION(HasOverflowLines(), "huh?");
+  FrameLines* prop =
+    static_cast<FrameLines*>(Properties().Remove(OverflowLinesProperty()));
+  NS_ASSERTION(prop && prop->mLines.empty(),
+               "value should always be stored but empty when destroying");
+  RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
+  delete prop;
 }
 
 // This takes ownership of aOverflowLines.
 // XXX We should allocate overflowLines from presShell arena!
-nsresult
-nsBlockFrame::SetOverflowLines(nsLineList* aOverflowLines)
+void
+nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines)
 {
   NS_ASSERTION(aOverflowLines, "null lines");
-  NS_ASSERTION(!aOverflowLines->empty(), "empty lines");
+  NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines");
+  NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild ==
+               aOverflowLines->mFrames.FirstChild(),
+               "invalid overflow lines / frames");
   NS_ASSERTION(!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES),
                "Overwriting existing overflow lines");
 
   FrameProperties props = Properties();
   // Verify that we won't overwrite an existing overflow list
   NS_ASSERTION(!props.Get(OverflowLinesProperty()), "existing overflow list");
   props.Set(OverflowLinesProperty(), aOverflowLines);
   AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
-  return NS_OK;
 }
 
 nsFrameList*
 nsBlockFrame::GetOverflowOutOfFlows() const
 {
   if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
     return nsnull;
   }
@@ -4670,20 +4665,57 @@ nsBlockFrame::SetOverflowOutOfFlows(cons
   }
   else {
     SetPropTableFrames(PresContext(), new nsFrameList(aList),
                        OverflowOutOfFlowsProperty());
     AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
   }
 }
 
+nsBulletFrame*
+nsBlockFrame::GetInsideBullet() const
+{
+  if (!HasInsideBullet()) {
+    return nsnull;
+  }
+  NS_ASSERTION(!HasOutsideBullet(), "invalid bullet state");
+  nsBulletFrame* frame =
+    static_cast<nsBulletFrame*>(Properties().Get(InsideBulletProperty()));
+  NS_ASSERTION(frame && frame->GetType() == nsGkAtoms::bulletFrame,
+               "bogus inside bullet frame");
+  return frame;
+}
+
+nsBulletFrame*
+nsBlockFrame::GetOutsideBullet() const
+{
+  nsFrameList* list = GetOutsideBulletList();
+  return list ? static_cast<nsBulletFrame*>(list->FirstChild())
+              : nsnull;
+}
+
+nsFrameList*
+nsBlockFrame::GetOutsideBulletList() const
+{
+  if (!HasOutsideBullet()) {
+    return nsnull;
+  }
+  NS_ASSERTION(!HasInsideBullet(), "invalid bullet state");
+  nsFrameList* list =
+    static_cast<nsFrameList*>(Properties().Get(OutsideBulletProperty()));
+  NS_ASSERTION(list && list->GetLength() == 1 &&
+               list->FirstChild()->GetType() == nsGkAtoms::bulletFrame,
+               "bogus outside bullet list");
+  return list;
+}
+
 nsFrameList*
 nsBlockFrame::GetPushedFloats() const
 {
-  if (!(GetStateBits() & NS_BLOCK_HAS_PUSHED_FLOATS)) {
+  if (!HasPushedFloats()) {
     return nsnull;
   }
   nsFrameList* result =
     static_cast<nsFrameList*>(Properties().Get(PushedFloatProperty()));
   NS_ASSERTION(result, "value should always be non-empty when state set");
   return result;
 }
 
@@ -4699,20 +4731,19 @@ nsBlockFrame::EnsurePushedFloats()
   AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
 
   return result;
 }
 
 nsFrameList*
 nsBlockFrame::RemovePushedFloats()
 {
-  if (!(GetStateBits() & NS_BLOCK_HAS_PUSHED_FLOATS)) {
+  if (!HasPushedFloats()) {
     return nsnull;
   }
-
   nsFrameList *result =
     static_cast<nsFrameList*>(Properties().Remove(PushedFloatProperty()));
   RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
   NS_ASSERTION(result, "value should always be non-empty when state set");
   return result;
 }
 
 //////////////////////////////////////////////////////////////////////
@@ -4834,48 +4865,45 @@ nsBlockFrame::AddFrames(nsFrameList& aFr
   ClearLineCursor();
 
   if (aFrameList.IsEmpty()) {
     return NS_OK;
   }
 
   // If we're inserting at the beginning of our list and we have an
   // inside bullet, insert after that bullet.
-  if (!aPrevSibling && mBullet && !HaveOutsideBullet()) {
-    NS_ASSERTION(!aFrameList.ContainsFrame(mBullet),
-                 "Trying to make mBullet prev sibling to itself");
-    aPrevSibling = mBullet;
+  if (!aPrevSibling && HasInsideBullet()) {
+    aPrevSibling = GetInsideBullet();
   }
   
   nsIPresShell *presShell = PresContext()->PresShell();
 
   // Attempt to find the line that contains the previous sibling
-  nsFrameList overflowFrames;
+  FrameLines* overflowLines;
   nsLineList* lineList = &mLines;
   nsLineList::iterator prevSibLine = lineList->end();
   PRInt32 prevSiblingIndex = -1;
   if (aPrevSibling) {
     // XXX_perf This is technically O(N^2) in some cases, but by using
     // RFind instead of Find, we make it O(N) in the most common case,
     // which is appending content.
 
     // Find the line that contains the previous sibling
     if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
                                         prevSibLine, mFrames.LastChild(),
                                         &prevSiblingIndex)) {
       // Not in mLines - try overflow lines.
-      lineList = GetOverflowLines();
-      if (lineList) {
-        prevSibLine = lineList->end();
+      overflowLines = GetOverflowLines();
+      lineList = overflowLines ? &overflowLines->mLines : nsnull;
+      if (overflowLines) {
+        prevSibLine = overflowLines->mLines.end();
         prevSiblingIndex = -1;
-        overflowFrames = nsFrameList(lineList->front()->mFirstChild,
-                                     lineList->back()->LastChild());
         if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
                                             prevSibLine,
-                                            overflowFrames.LastChild(),
+                                            overflowLines->mFrames.LastChild(),
                                             &prevSiblingIndex)) {
           lineList = nsnull;
         }
       }
       if (!lineList) {
         // Note: defensive code! RFindLineContaining must not return
         // false in this case, so if it does...
         NS_NOTREACHED("prev sibling not in line list");
@@ -4909,17 +4937,17 @@ nsBlockFrame::AddFrames(nsFrameList& aFr
       line->MarkDirty();
       line->SetInvalidateTextRuns(true);
     }
   }
   else if (! lineList->empty()) {
     lineList->front()->MarkDirty();
     lineList->front()->SetInvalidateTextRuns(true);
   }
-  nsFrameList& frames = lineList == &mLines ? mFrames : overflowFrames;
+  nsFrameList& frames = lineList == &mLines ? mFrames : overflowLines->mFrames;
   const nsFrameList::Slice& newFrames =
     frames.InsertFrames(nsnull, aPrevSibling, aFrameList);
 
   // Walk through the new frames being added and update the line data
   // structures to fit.
   for (nsFrameList::Enumerator e(newFrames); !e.AtEnd(); e.Next()) {
     nsIFrame* newFrame = e.get();
     NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame,
@@ -5124,38 +5152,48 @@ nsBlockFrame::DoRemoveOutOfFlowFrame(nsI
 
 /**
  * This helps us iterate over the list of all normal + overflow lines
  */
 void
 nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator,
                           nsLineList::iterator* aStartIterator,
                           nsLineList::iterator* aEndIterator,
-                          bool* aInOverflowLines) {
+                          bool* aInOverflowLines,
+                          FrameLines** aOverflowLines)
+{
   if (*aIterator == *aEndIterator) {
     if (!*aInOverflowLines) {
+      // Try the overflow lines
       *aInOverflowLines = true;
-      // Try the overflow lines
-      nsLineList* overflowLines = GetOverflowLines();
-      if (overflowLines) {
-        *aStartIterator = overflowLines->begin();
+      FrameLines* lines = GetOverflowLines();
+      if (lines) {
+        *aStartIterator = lines->mLines.begin();
         *aIterator = *aStartIterator;
-        *aEndIterator = overflowLines->end();
+        *aEndIterator = lines->mLines.end();
+        *aOverflowLines = lines;
       }
     }
   }
 }
 
 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
+    line_iterator aLine)
+  : mFrame(aFrame), mLine(aLine), mInOverflowLines(nsnull)
+{
+  // This will assert if aLine isn't in mLines of aFrame:
+  DebugOnly<bool> check = aLine == mFrame->begin_lines();
+}
+
+nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
     line_iterator aLine, bool aInOverflow)
   : mFrame(aFrame), mLine(aLine), mInOverflowLines(nsnull)
 {
   if (aInOverflow) {
-    mInOverflowLines = aFrame->GetOverflowLines();
-    NS_ASSERTION(mInOverflowLines, "How can we be in overflow if there isn't any?");
+    mInOverflowLines = &aFrame->GetOverflowLines()->mLines;
   }
 }
 
 nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
     bool* aFoundValidLine)
   : mFrame(aFrame), mInOverflowLines(nsnull)
 {
   mLine = aFrame->begin_lines();
@@ -5292,17 +5330,18 @@ nsBlockInFlowLineIterator::Prev()
       if (mLine != mFrame->begin_lines()) {
         --mLine;
         return true;
       }
     } else {
       mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
       if (!mFrame)
         return false;
-      mInOverflowLines = mFrame->GetOverflowLines();
+      nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
+      mInOverflowLines = overflowLines ? &overflowLines->mLines : nsnull;
       if (mInOverflowLines) {
         mLine = mInOverflowLines->end();
         NS_ASSERTION(mLine != mInOverflowLines->begin(), "empty overflow line list?");
         --mLine;
         return true;
       }
     }
     currentlyInOverflowLines = !currentlyInOverflowLines;
@@ -5321,17 +5360,18 @@ nsBlockInFlowLineIterator::FindValidLine
       mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
       if (!mFrame)
         return false;
       mInOverflowLines = nsnull;
       mLine = mFrame->begin_lines();
       if (mLine != mFrame->end_lines())
         return true;
     } else {
-      mInOverflowLines = mFrame->GetOverflowLines();
+      nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
+      mInOverflowLines = overflowLines ? &overflowLines->mLines : nsnull;
       if (mInOverflowLines) {
         mLine = mInOverflowLines->begin();
         NS_ASSERTION(mLine != mInOverflowLines->end(), "empty overflow line list?");
         return true;
       }
     }
     currentlyInOverflowLines = !currentlyInOverflowLines;
   }
@@ -5379,26 +5419,29 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aD
   }
 
   nsIPresShell* presShell = presContext->PresShell();
 
   // Find the line that contains deletedFrame
   nsLineList::iterator line_start = mLines.begin(),
                        line_end = mLines.end();
   nsLineList::iterator line = line_start;
+  FrameLines* overflowLines = nsnull;
   bool searchingOverflowList = false;
   // Make sure we look in the overflow lines even if the normal line
   // list is empty
-  TryAllLines(&line, &line_start, &line_end, &searchingOverflowList);
+  TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
+              &overflowLines);
   while (line != line_end) {
     if (line->Contains(aDeletedFrame)) {
       break;
     }
     ++line;
-    TryAllLines(&line, &line_start, &line_end, &searchingOverflowList);
+    TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
+                &overflowLines);
   }
 
   if (line == line_end) {
     NS_ERROR("can't find deleted frame in lines");
     return NS_ERROR_FAILURE;
   }
   
   if (!(aFlags & FRAMES_ARE_EMPTY)) {
@@ -5407,68 +5450,62 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aD
       line.prev()->SetInvalidateTextRuns(true);
     }
     else if (searchingOverflowList && !mLines.empty()) {
       mLines.back()->MarkDirty();
       mLines.back()->SetInvalidateTextRuns(true);
     }
   }
 
-  while ((line != line_end) && (nsnull != aDeletedFrame)) {
+  while (line != line_end && aDeletedFrame) {
     NS_ASSERTION(this == aDeletedFrame->GetParent(), "messed up delete code");
     NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line");
 
     if (!(aFlags & FRAMES_ARE_EMPTY)) {
       line->MarkDirty();
       line->SetInvalidateTextRuns(true);
     }
 
     // If the frame being deleted is the last one on the line then
     // optimize away the line->Contains(next-in-flow) call below.
     bool isLastFrameOnLine = 1 == line->GetChildCount();
     if (!isLastFrameOnLine) {
       line_iterator next = line.next();
       nsIFrame* lastFrame = next != line_end ?
         next->mFirstChild->GetPrevSibling() :
-        (searchingOverflowList ? line->LastChild() : mFrames.LastChild());
+        (searchingOverflowList ? overflowLines->mFrames.LastChild() : 
+                                 mFrames.LastChild());
       NS_ASSERTION(next == line_end || lastFrame == line->LastChild(),
                    "unexpected line frames");
       isLastFrameOnLine = lastFrame == aDeletedFrame;
     }
 
     // Remove aDeletedFrame from the line
-    nsIFrame* nextFrame = aDeletedFrame->GetNextSibling();
     if (line->mFirstChild == aDeletedFrame) {
       // We should be setting this to null if aDeletedFrame
       // is the only frame on the line. HOWEVER in that case
       // we will be removing the line anyway, see below.
-      line->mFirstChild = nextFrame;
+      line->mFirstChild = aDeletedFrame->GetNextSibling();
     }
 
     // Hmm, this won't do anything if we're removing a frame in the first
     // overflow line... Hopefully doesn't matter
     --line;
     if (line != line_end && !line->IsBlock()) {
       // Since we just removed a frame that follows some inline
       // frames, we need to reflow the previous line.
       line->MarkDirty();
     }
     ++line;
 
     // Take aDeletedFrame out of the sibling list. Note that
     // prevSibling will only be nsnull when we are deleting the very
     // first frame in the main or overflow list.
     if (searchingOverflowList) {
-      nsIFrame* prevSibling = aDeletedFrame->GetPrevSibling();
-      if (prevSibling) {
-        // XXXbz If we switch overflow lines to nsFrameList, we should
-        // change this SetNextSibling call.
-        prevSibling->SetNextSibling(nextFrame);
-      }
-      aDeletedFrame->SetNextSibling(nsnull);
+      overflowLines->mFrames.RemoveFrame(aDeletedFrame);
     } else {
       mFrames.RemoveFrame(aDeletedFrame);
     }
 
     // Update the child count of the line to be accurate
     PRInt32 lineChildCount = line->GetChildCount();
     lineChildCount--;
     line->SetChildCount(lineChildCount);
@@ -5513,22 +5550,23 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aD
         nsRect visOverflow(cur->GetVisualOverflowArea());
 #ifdef NOISY_BLOCK_INVALIDATE
         printf("%p invalidate 10 (%d, %d, %d, %d)\n",
                this, visOverflow.x, visOverflow.y,
                visOverflow.width, visOverflow.height);
 #endif
         Invalidate(visOverflow);
       } else {
-        nsLineList* lineList = RemoveOverflowLines();
-        line = lineList->erase(line);
-        if (!lineList->empty()) {
-          SetOverflowLines(lineList);
+        // XXX update searchingOverflowList directly, remove only when empty
+        FrameLines* overflowLines = RemoveOverflowLines();
+        line = overflowLines->mLines.erase(line);
+        if (!overflowLines->mLines.empty()) {
+          SetOverflowLines(overflowLines);
         } else {
-          delete lineList;
+          delete overflowLines;
           // We just invalidated our iterators.  Since we were in
           // the overflow lines list, which is now empty, set them
           // so we're at the end of the regular line list.
           line_start = mLines.begin();
           line_end = mLines.end();
           line = line_end;
         }
       }
@@ -5572,17 +5610,18 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aD
       if (haveAdvancedToNextLine) {
         if (line != line_end && !searchingOverflowList &&
             !line->Contains(deletedNextContinuation)) {
           // We have advanced to the next *normal* line but the next-in-flow
           // is not there - force a switch to the overflow line list.
           line = line_end;
         }
 
-        TryAllLines(&line, &line_start, &line_end, &searchingOverflowList);
+        TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
+                    &overflowLines);
 #ifdef NOISY_REMOVE_FRAME
         printf("DoRemoveFrame: now on %s line=%p\n",
                searchingOverflowList?"overflow":"normal", line.get());
 #endif
       }
     }
   }
 
@@ -5623,57 +5662,54 @@ nsBlockFrame::StealFrame(nsPresContext* 
     return nsContainerFrame::StealFrame(aPresContext, aChild);
 
   // Find the line and the previous sibling that contains
   // aChild; we also find the pointer to the line.
   nsLineList::iterator line = mLines.begin(),
                        line_start = line,
                        line_end = mLines.end();
   bool searchingOverflowList = false;
+  FrameLines* overflowLines = nsnull;
   nsIFrame* prevSibling = nsnull;
   // Make sure we look in the overflow lines even if the normal line
   // list is empty
-  TryAllLines(&line, &line_start, &line_end, &searchingOverflowList);
+  TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
+              &overflowLines);
   while (line != line_end) {
     nsIFrame* frame = line->mFirstChild;
     PRInt32 n = line->GetChildCount();
     while (--n >= 0) {
       if (frame == aChild) {
-        // Disconnect from sibling list
         if (frame == line->mFirstChild) {
           line->mFirstChild = frame->GetNextSibling();
         }
         if (searchingOverflowList) {
-          // XXXbz If we switch overflow lines to nsFrameList, we should
-          // change this SetNextSibling call.
-          if (prevSibling)
-            prevSibling->SetNextSibling(frame->GetNextSibling());
-          frame->SetNextSibling(nsnull);
+          overflowLines->mFrames.RemoveFrame(frame);
         } else {
           mFrames.RemoveFrame(frame);
         }
 
         // Register removal with the line boxes
         PRInt32 count = line->GetChildCount();
         line->SetChildCount(--count);
         if (count > 0) {
            line->MarkDirty();
         }
         else {
           // Remove the line box
           nsLineBox* lineBox = line;
           if (searchingOverflowList) {
             // Erase line, but avoid making the overflow line list empty
-            nsLineList* lineList = RemoveOverflowLines();
-            line = lineList->erase(line);
-            if (!lineList->empty()) {
-              nsresult rv = SetOverflowLines(lineList);
-              NS_ENSURE_SUCCESS(rv, rv);
+            // XXX update overflowLines directly, remove only when empty
+            RemoveOverflowLines();
+            line = overflowLines->mLines.erase(line);
+            if (!overflowLines->mLines.empty()) {
+              SetOverflowLines(overflowLines);
             } else {
-              delete lineList;
+              delete overflowLines;
               // We just invalidated our iterators.  Since we were in
               // the overflow lines list, which is now empty, set them
               // so we're at the end of the regular line list.
               line_start = mLines.begin();
               line_end = mLines.end();
               line = line_end;
             }
           }
@@ -5689,17 +5725,18 @@ nsBlockFrame::StealFrame(nsPresContext* 
 
         // Ok, we're done
         return NS_OK;
       }
       prevSibling = frame;
       frame = frame->GetNextSibling();
     }
     ++line;
-    TryAllLines(&line, &line_start, &line_end, &searchingOverflowList);
+    TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
+                &overflowLines);
     if (prevSibling && !prevSibling->GetNextSibling()) {
       // We just switched to the overflow list.  Null out prevSibling
       prevSibling = nsnull;
     }
   }
   return NS_ERROR_UNEXPECTED;
 }
 
@@ -6298,19 +6335,20 @@ nsBlockFrame::BuildDisplayList(nsDisplay
       lineCount++;
     }
 
     if (NS_SUCCEEDED(rv) && nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
       SetupLineCursor();
     }
   }
 
-  if (NS_SUCCEEDED(rv) && (nsnull != mBullet) && HaveOutsideBullet()) {
+  if (NS_SUCCEEDED(rv) && HasOutsideBullet()) {
     // Display outside bullets manually
-    rv = BuildDisplayListForChild(aBuilder, mBullet, aDirtyRect, aLists);
+    nsIFrame* bullet = GetOutsideBullet();
+    rv = BuildDisplayListForChild(aBuilder, bullet, aDirtyRect, aLists);
   }
 
 #ifdef DEBUG
   if (gLamePaintMetrics) {
     PRTime end = PR_Now();
 
     PRInt32 numLines = mLines.size();
     if (!numLines) numLines = 1;
@@ -6344,17 +6382,17 @@ nsBlockFrame::CreateAccessible()
   nsPresContext* presContext = PresContext();
 
   // block frame may be for <hr>
   if (mContent->Tag() == nsGkAtoms::hr) {
     return accService->CreateHTMLHRAccessible(mContent,
                                               presContext->PresShell());
   }
 
-  if (!mBullet || !presContext) {
+  if (!HasBullet() || !presContext) {
     if (!mContent->GetParent()) {
       // Don't create accessible objects for the root content node, they are redundant with
       // the nsDocAccessible object created with the document node
       return nsnull;
     }
     
     nsCOMPtr<nsIDOMHTMLDocument> htmlDoc =
       do_QueryInterface(mContent->GetDocument());
@@ -6432,17 +6470,17 @@ nsLineBox* nsBlockFrame::GetFirstLineCon
 
 /* virtual */ void
 nsBlockFrame::ChildIsDirty(nsIFrame* aChild)
 {
   // See if the child is absolutely positioned
   if (aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW &&
       aChild->GetStyleDisplay()->IsAbsolutelyPositioned()) {
     // do nothing
-  } else if (aChild == mBullet && HaveOutsideBullet()) {
+  } else if (aChild == GetOutsideBullet()) {
     // The bullet lives in the first line, unless the first line has
     // height 0 and there is a second line, in which case it lives
     // in the second line.
     line_iterator bulletLine = begin_lines();
     if (bulletLine != end_lines() && bulletLine->mBounds.height == 0 &&
         bulletLine != mLines.back()) {
       bulletLine = bulletLine.next();
     }
@@ -6467,40 +6505,39 @@ nsBlockFrame::ChildIsDirty(nsIFrame* aCh
 }
 
 NS_IMETHODIMP
 nsBlockFrame::Init(nsIContent*      aContent,
                    nsIFrame*        aParent,
                    nsIFrame*        aPrevInFlow)
 {
   if (aPrevInFlow) {
-    // Copy over the block frame type flags
-    nsBlockFrame*  blockFrame = (nsBlockFrame*)aPrevInFlow;
-
-    // Don't copy NS_BLOCK_HAS_FIRST_LETTER_CHILD as that is set on the first
-    // continuation only.
-    SetFlags(blockFrame->mState &
-             (NS_BLOCK_FLAGS_MASK &
-               (~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET &
-                ~NS_BLOCK_HAS_FIRST_LETTER_CHILD)));
+    // Copy over the inherited block frame bits from the prev-in-flow.
+    SetFlags(aPrevInFlow->GetStateBits() &
+             (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK));
   }
 
   nsresult rv = nsBlockFrameSuper::Init(aContent, aParent, aPrevInFlow);
 
   if (!aPrevInFlow ||
       aPrevInFlow->GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
     AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsBlockFrame::SetInitialChildList(ChildListID     aListID,
                                   nsFrameList&    aChildList)
 {
+  NS_ASSERTION(aListID != kPrincipalList ||
+               (GetStateBits() & (NS_BLOCK_FRAME_HAS_INSIDE_BULLET |
+                                  NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET)) == 0,
+               "how can we have a bullet already?");
+
   nsresult rv = NS_OK;
 
   if (kAbsoluteList == aListID) {
     nsContainerFrame::SetInitialChildList(aListID, aChildList);
   }
   else if (kFloatList == aListID) {
     mFloats.SetFrames(aChildList);
   }
@@ -6549,20 +6586,19 @@ nsBlockFrame::SetInitialChildList(ChildL
     nsIFrame* possibleListItem = this;
     while (1) {
       nsIFrame* parent = possibleListItem->GetParent();
       if (parent->GetContent() != GetContent()) {
         break;
       }
       possibleListItem = parent;
     }
-    if ((nsnull == GetPrevInFlow()) &&
-        (NS_STYLE_DISPLAY_LIST_ITEM ==
-           possibleListItem->GetStyleDisplay()->mDisplay) &&
-        (nsnull == mBullet)) {
+    if (NS_STYLE_DISPLAY_LIST_ITEM ==
+          possibleListItem->GetStyleDisplay()->mDisplay &&
+        !GetPrevInFlow()) {
       // Resolve style for the bullet frame
       const nsStyleList* styleList = GetStyleList();
       nsCSSPseudoElements::Type pseudoType;
       switch (styleList->mListStyleType) {
         case NS_STYLE_LIST_STYLE_DISC:
         case NS_STYLE_LIST_STYLE_CIRCLE:
         case NS_STYLE_LIST_STYLE_SQUARE:
           pseudoType = nsCSSPseudoElements::ePseudo_mozListBullet;
@@ -6578,46 +6614,45 @@ nsBlockFrame::SetInitialChildList(ChildL
         CorrectStyleParentFrame(this,
           nsCSSPseudoElements::GetPseudoAtom(pseudoType))->GetStyleContext();
       nsRefPtr<nsStyleContext> kidSC = shell->StyleSet()->
         ResolvePseudoElementStyle(mContent->AsElement(), pseudoType,
                                   parentStyle);
 
       // Create bullet frame
       nsBulletFrame* bullet = new (shell) nsBulletFrame(kidSC);
-      if (nsnull == bullet) {
+      if (!bullet) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
       bullet->Init(mContent, this, nsnull);
 
       // If the list bullet frame should be positioned inside then add
       // it to the flow now.
       if (NS_STYLE_LIST_STYLE_POSITION_INSIDE ==
-          styleList->mListStylePosition) {
+            styleList->mListStylePosition) {
         nsFrameList bulletList(bullet, bullet);
         AddFrames(bulletList, nsnull);
-        mState &= ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
+        Properties().Set(InsideBulletProperty(), bullet);
+        AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_BULLET);
+      } else {
+        nsFrameList* bulletList = new nsFrameList(bullet, bullet);
+        Properties().Set(OutsideBulletProperty(), bulletList);
+        AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET);
       }
-      else {
-        mState |= NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
-      }
-
-      mBullet = bullet;
     }
   }
 
   return NS_OK;
 }
 
 bool
 nsBlockFrame::BulletIsEmpty() const
 {
   NS_ASSERTION(mContent->GetPrimaryFrame()->GetStyleDisplay()->mDisplay ==
-                 NS_STYLE_DISPLAY_LIST_ITEM &&
-               HaveOutsideBullet(),
+                 NS_STYLE_DISPLAY_LIST_ITEM && HasOutsideBullet(),
                "should only care when we have an outside bullet");
   const nsStyleList* list = GetStyleList();
   return list->mListStyleType == NS_STYLE_LIST_STYLE_NONE &&
          !list->GetListStyleImage();
 }
 
 void
 nsBlockFrame::GetBulletText(nsAString& aText) const
@@ -6631,32 +6666,23 @@ nsBlockFrame::GetBulletText(nsAString& a
   }
   else if (myList->mListStyleType == NS_STYLE_LIST_STYLE_CIRCLE) {
     aText.Assign(kCircleCharacter);
   }
   else if (myList->mListStyleType == NS_STYLE_LIST_STYLE_SQUARE) {
     aText.Assign(kSquareCharacter);
   }
   else if (myList->mListStyleType != NS_STYLE_LIST_STYLE_NONE) {
-    nsAutoString text;
-    mBullet->GetListItemText(*myList, text);
-    aText = text;
-  }
-}
-
-bool
-nsBlockFrame::HasBullet() const
-{
-  if (mBullet) {
-    const nsStyleList* styleList = GetStyleList();
-    return styleList->GetListStyleImage() ||
-      styleList->mListStyleType != NS_STYLE_LIST_STYLE_NONE;
-  }
-
-  return false;
+    nsBulletFrame* bullet = GetBullet();
+    if (bullet) {
+      nsAutoString text;
+      bullet->GetListItemText(*myList, text);
+      aText = text;