Merge mc->Maple
authorBenoit Girard <b56girard@gmail.com>
Thu, 08 Mar 2012 10:22:42 -0500
changeset 89308 217568c1b04763eee6cf086d30a8a3a2c9b15cc6
parent 89307 4012267a4f08f2b689da1bb6e1ee426c4fb3805e (current diff)
parent 88516 8219e651919031b7eec9aa82c5c91b5d87db234a (diff)
child 89309 c6dcca94622e4b66c69ef48a03c256b5fde85295
push id22242
push userkgupta@mozilla.com
push dateWed, 14 Mar 2012 15:19:09 +0000
treeherdermozilla-central@936ef50fa498 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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_LIS