Merge m-c to s-c.
authorNick Alexander <nalexander@mozilla.com>
Fri, 19 Jul 2013 11:19:17 -0700
changeset 158578 429dd1e02857848f9d53c1ad5e71a9223f1dea59
parent 158577 1555f551eff6f584fc42224e59e64e1b3818b8c1 (current diff)
parent 151512 0983f1a0961ceb4edf9012650393648d44773cd0 (diff)
child 158579 0608a3b4c9d92738b3bca824af1f4a3ff7672bee
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone25.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 m-c to s-c.
dom/interfaces/html/nsIDOMHTMLProgressElement.idl
dom/interfaces/html/nsIDOMHTMLTableCellElement.idl
dom/interfaces/html/nsIDOMHTMLTableColElement.idl
dom/interfaces/html/nsIDOMHTMLTableRowElement.idl
dom/interfaces/html/nsIDOMHTMLTableSectionElement.idl
dom/interfaces/html/nsIDOMHTMLUnknownElement.idl
mobile/android/base/resources/drawable-hdpi/larry_blue.png
mobile/android/base/resources/drawable-hdpi/larry_green.png
mobile/android/base/resources/drawable-hdpi/site_security_identified.png
mobile/android/base/resources/drawable-hdpi/site_security_verified.png
mobile/android/base/resources/drawable-mdpi/larry_blue.png
mobile/android/base/resources/drawable-mdpi/larry_green.png
mobile/android/base/resources/drawable-mdpi/site_security_identified.png
mobile/android/base/resources/drawable-mdpi/site_security_verified.png
mobile/android/base/resources/drawable-xhdpi/larry_blue.png
mobile/android/base/resources/drawable-xhdpi/larry_green.png
mobile/android/base/resources/drawable-xhdpi/site_security_identified.png
mobile/android/base/resources/drawable-xhdpi/site_security_verified.png
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2301,21 +2301,21 @@ function BrowserOnAboutPageLoad(doc) {
     if (AboutHomeUtils.showKnowYourRights) {
       docElt.setAttribute("showKnowYourRights", "true");
       // Set pref to indicate we've shown the notification.
       let currentVersion = Services.prefs.getIntPref("browser.rights.version");
       Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
     }
     docElt.setAttribute("snippetsVersion", AboutHomeUtils.snippetsVersion);
 
-    function updateSearchEngine() {
+    let updateSearchEngine = function() {
       let engine = AboutHomeUtils.defaultSearchEngine;
       docElt.setAttribute("searchEngineName", engine.name);
       docElt.setAttribute("searchEngineURL", engine.searchURL);
-    }
+    };
     updateSearchEngine();
 
     // Listen for the event that's triggered when the user changes search engine.
     // At this point we simply reload about:home to reflect the change.
     Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false);
 
     // Remove the observer when the page is reloaded or closed.
     doc.defaultView.addEventListener("pagehide", function removeObserver() {
--- a/browser/base/content/test/browser_aboutHome.js
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -106,31 +106,30 @@ let gTests = [
     let doc = gBrowser.contentDocument;
     let engineName = doc.documentElement.getAttribute("searchEngineName");
 
     // We rely on the listener in browser.js being installed and fired before
     // this one. If this ever changes, we should add an executeSoon() or similar.
     doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
       is(e.detail, engineName, "Detail is search engine name");
 
-      gBrowser.stop();
-
       getNumberOfSearches(engineName).then(num => {
         is(num, numSearchesBefore + 1, "One more search recorded.");
         deferred.resolve();
       });
     }, true, true);
 
     // Get the current number of recorded searches.
     getNumberOfSearches(engineName).then(num => {
       numSearchesBefore = num;
 
       info("Perform a search.");
       doc.getElementById("searchText").value = "a search";
       doc.getElementById("searchSubmit").click();
+      gBrowser.stop();
     });
 
     return deferred.promise;
   }
 },
 
 {
   desc: "Check snippets map is cleared if cached version is old",
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -73,22 +73,19 @@ undoCloseTab.label so users will not nee
 <!ENTITY printCmd.commandkey "p">
 
 <!ENTITY goOfflineCmd.label "Work Offline">
 <!ENTITY goOfflineCmd.accesskey "k">
 
 <!ENTITY menubarCmd.label "Menu Bar">
 <!ENTITY menubarCmd.accesskey "M">
 <!ENTITY navbarCmd.label "Navigation Toolbar">
-<!ENTITY navbarCmd.accesskey "N">
 <!ENTITY personalbarCmd.label "Bookmarks Toolbar">
 <!ENTITY personalbarCmd.accesskey "B">
 <!ENTITY bookmarksToolbarItem.label "Bookmarks Toolbar Items">
-<!ENTITY addonBarCmd.label "Add-on Bar">
-<!ENTITY addonBarCmd.accesskey "A">
 
 <!ENTITY pageSourceCmd.label "Page Source">
 <!ENTITY pageSourceCmd.accesskey "o">
 <!ENTITY pageSourceCmd.commandkey "u">
 <!ENTITY pageInfoCmd.label "Page Info">
 <!ENTITY pageInfoCmd.accesskey "I">
 <!ENTITY pageInfoCmd.commandkey "i">
 <!-- LOCALIZATION NOTE (enterFullScreenCmd.label, exitFullScreenCmd.label):
@@ -161,48 +158,40 @@ These should match what Safari and other
 <!ENTITY stopButton.tooltip           "Stop loading this page">
 <!ENTITY goEndCap.tooltip             "Go to the address in the Location Bar">
 <!ENTITY printButton.label            "Print">
 <!ENTITY printButton.tooltip          "Print this page">
 
 <!ENTITY backForwardItem.title        "Back/Forward">
 <!ENTITY locationItem.title           "Location">
 <!ENTITY searchItem.title             "Search">
-<!ENTITY throbberItem.title           "Activity Indicator">
 <!ENTITY bookmarksItem.title          "Bookmarks">
 
 <!-- Toolbar items --> 
-<!ENTITY appMenuButton.label          "Menu">
-<!ENTITY appMenuButton.tooltip        "Open &brandShortName; menu">
 <!ENTITY homeButton.label             "Home">
 
 <!ENTITY tabGroupsButton.label        "Tab Groups">
 <!ENTITY tabGroupsButton.tooltip      "Group your tabs">
 
-<!ENTITY feedButton.label             "Subscribe">
-<!ENTITY feedButton.tooltip           "Subscribe to this page…">
-
 <!ENTITY bookmarksButton.label          "Bookmarks">
-<!ENTITY bookmarksButton.tooltip        "Display your bookmarks">
 <!ENTITY bookmarksCmd.commandkey "b">
 
 <!ENTITY bookmarksMenuButton.label          "Bookmarks">
 <!ENTITY bookmarksMenuButton.tooltip        "Display your bookmarks">
 <!ENTITY bookmarksMenuButton.unsorted.label "Unsorted Bookmarks">
 <!ENTITY viewBookmarksSidebar.label         "Show in Sidebar">
 <!ENTITY viewBookmarksToolbar.label         "View Bookmarks Toolbar">
 
 <!-- LOCALIZATION NOTE (bookmarksSidebarGtkCmd.commandkey): This command
   -  key should not contain the letters A-F, since these are reserved
   -  shortcut keys on Linux. -->
 <!ENTITY bookmarksGtkCmd.commandkey "o">
 <!ENTITY bookmarksWinCmd.commandkey "i">
 
 <!ENTITY historyButton.label            "History">
-<!ENTITY historyButton.tooltip          "Display pages you've viewed recently">
 <!ENTITY historySidebarCmd.commandKey   "h">
 
 <!ENTITY toolsMenu.label              "Tools">
 <!ENTITY toolsMenu.accesskey          "T"> 
 
 <!ENTITY keywordfield.label           "Add a Keyword for this Search…">
 <!ENTITY keywordfield.accesskey       "K">
 <!ENTITY search.label                 "Web Search">
@@ -316,40 +305,29 @@ These should match what Safari and other
 <!ENTITY viewTabGroups.label            "Tab Groups">
 <!ENTITY viewTabGroups.accesskey        "G">
 <!ENTITY viewToolbarsMenu.label       "Toolbars"> 
 <!ENTITY viewToolbarsMenu.accesskey     "T"> 
 <!ENTITY viewSidebarMenu.label "Sidebar">
 <!ENTITY viewSidebarMenu.accesskey "e">
 <!ENTITY viewCustomizeToolbar.label       "Customize…"> 
 <!ENTITY viewCustomizeToolbar.accesskey     "C">
-<!ENTITY viewTabsOnTop.label            "Tabs on Top">
-<!ENTITY viewTabsOnTop.accesskey        "T">
 
 <!ENTITY historyMenu.label "History">
 <!ENTITY historyMenu.accesskey "s">
 <!ENTITY historyUndoMenu.label "Recently Closed Tabs">
 <!-- LOCALIZATION NOTE (historyUndoWindowMenu): see bug 394759 -->
 <!ENTITY historyUndoWindowMenu.label "Recently Closed Windows">
 <!ENTITY historyRestoreLastSession.label "Restore Previous Session">
 
 <!ENTITY historyHomeCmd.label "Home">
 <!ENTITY showAllHistoryCmd2.label "Show All History">
 <!ENTITY showAllHistoryCmd.commandkey "H">
 
-<!ENTITY appMenuEdit.label "Edit">
 <!ENTITY appMenuCustomize.label "Customize">
-<!ENTITY appMenuToolbarLayout.label "Toolbar Layout…">
-<!ENTITY appMenuSidebars.label "Sidebars">
-<!ENTITY appMenuFind.label "Find…">
-<!ENTITY appMenuUnsorted.label "Unsorted Bookmarks">
-<!ENTITY appMenuWebDeveloper.label "Web Developer">
-<!ENTITY appMenuGettingStarted.label "Getting Started">
-<!ENTITY appMenuSafeMode.label "Restart with Add-ons Disabled…">
-<!ENTITY appMenuSafeMode.accesskey "R">
 
 <!ENTITY openCmd.commandkey           "l">
 <!ENTITY urlbar.placeholder2          "Search or enter address">
 <!ENTITY urlbar.accesskey             "d">
 <!ENTITY urlbar.switchToTab.label     "Switch to tab:">
 
 <!-- 
   Comment duplicated from browser-sets.inc:
@@ -544,26 +522,18 @@ you can use these alternative items. Oth
 <!ENTITY fullZoomToggleCmd.accesskey    "T">
 <!ENTITY fullZoom.label                 "Zoom">
 <!ENTITY fullZoom.accesskey             "Z">
 
 <!ENTITY newTabButton.tooltip           "Open a new tab">
 <!ENTITY newWindowButton.tooltip        "Open a new window">
 <!ENTITY sidebarCloseButton.tooltip     "Close sidebar">
 
-<!ENTITY cutButton.tooltip              "Cut">
-<!ENTITY copyButton.tooltip             "Copy">
-<!ENTITY pasteButton.tooltip            "Paste">
-
 <!ENTITY fullScreenButton.tooltip       "Display the window in full screen">
 
-<!ENTITY zoomOutButton.tooltip          "Zoom out">
-<!ENTITY zoomInButton.tooltip           "Zoom in">
-<!ENTITY zoomControls.label             "Zoom Controls">
-
 <!ENTITY quitApplicationCmdWin.label       "Exit"> 
 <!ENTITY quitApplicationCmdWin.accesskey   "x">
 <!ENTITY goBackCmd.commandKey "[">
 <!ENTITY goForwardCmd.commandKey "]">
 <!ENTITY quitApplicationCmd.label       "Quit"> 
 <!ENTITY quitApplicationCmd.accesskey   "Q">
 <!ENTITY quitApplicationCmdMac.label    "Quit &brandShortName;">
 <!-- LOCALIZATION NOTE(quitApplicationCmdUnix.key): This keyboard shortcut is used by both Linux and OSX builds. -->
@@ -641,19 +611,16 @@ just addresses the organization to follo
 <!ENTITY syncBrand.shortName.label    "Sync">
 
 <!ENTITY syncSetup.label              "Set Up &syncBrand.shortName.label;…">
 <!ENTITY syncSetup.accesskey          "Y">
 <!ENTITY syncSyncNowItem.label        "Sync Now">
 <!ENTITY syncSyncNowItem.accesskey    "S">
 <!ENTITY syncToolbarButton.label      "Sync">
 
-<!ENTITY addonBarCloseButton.tooltip  "Close Add-on Bar">
-<!ENTITY toggleAddonBarCmd.key        "/">
-
 <!ENTITY socialToolbar.title        "Social Toolbar Button">
 <!ENTITY social.notLoggedIn.label   "Not logged in">
 
 <!ENTITY social.ok.label       "OK">
 <!ENTITY social.ok.accesskey   "O">
 
 <!ENTITY social.toggleSidebar.label "Show sidebar">
 <!ENTITY social.toggleSidebar.accesskey "s">
@@ -693,8 +660,45 @@ just addresses the organization to follo
 
 <!ENTITY pluginNotification.showAll.label "Show All">
 <!ENTITY pluginNotification.showAll.accesskey "S">
 
 <!-- LOCALIZATION NOTE (pluginActivateNow.label, pluginActivateAlways.label, pluginBlockNow.label): These should be the same as the matching strings in browser.properties -->
 <!ENTITY pluginActivateNow.label "Allow Now">
 <!ENTITY pluginActivateAlways.label "Allow and Remember">
 <!ENTITY pluginBlockNow.label "Block Plugin">
+
+<!-- LOCALIZATION NOTE: the following strings are unused in Australis, they're
+     kept here to avoid warnings from l10n tools like compare-locales on
+     l10n-central. They will be definitely removed when Australis is ready
+     for mozilla-aurora. -->
+
+<!ENTITY navbarCmd.accesskey           "N">
+<!ENTITY addonBarCmd.label             "Add-on Bar">
+<!ENTITY addonBarCmd.accesskey         "A">
+<!ENTITY throbberItem.title            "Activity Indicator">
+<!ENTITY appMenuButton.label           "Menu">
+<!ENTITY appMenuButton.tooltip         "Open &brandShortName; menu">
+<!ENTITY feedButton.label              "Subscribe">
+<!ENTITY feedButton.tooltip            "Subscribe to this page…">
+<!ENTITY bookmarksButton.tooltip       "Display your bookmarks">
+<!ENTITY historyButton.tooltip         "Display pages you've viewed recently">
+<!ENTITY viewTabsOnTop.label           "Tabs on Top">
+<!ENTITY viewTabsOnTop.accesskey       "T">
+<!ENTITY appMenuEdit.label             "Edit">
+<!ENTITY appMenuToolbarLayout.label    "Toolbar Layout…">
+<!ENTITY appMenuSidebars.label         "Sidebars">
+<!ENTITY appMenuFind.label             "Find…">
+<!ENTITY appMenuUnsorted.label         "Unsorted Bookmarks">
+<!ENTITY appMenuWebDeveloper.label     "Web Developer">
+<!ENTITY appMenuGettingStarted.label   "Getting Started">
+<!ENTITY appMenuSafeMode.label         "Restart with Add-ons Disabled…">
+<!ENTITY appMenuSafeMode.accesskey     "R">
+<!ENTITY cutButton.tooltip             "Cut">
+<!ENTITY copyButton.tooltip            "Copy">
+<!ENTITY pasteButton.tooltip           "Paste">
+<!ENTITY zoomOutButton.tooltip         "Zoom out">
+<!ENTITY zoomInButton.tooltip          "Zoom in">
+<!ENTITY zoomControls.label            "Zoom Controls">
+<!ENTITY addonBarCloseButton.tooltip   "Close Add-on Bar">
+<!ENTITY toggleAddonBarCmd.key         "/">
+<!-- end of strings to be removed post-Australis -->
+
--- a/browser/metro/base/content/helperui/ChromeSelectionHandler.js
+++ b/browser/metro/base/content/helperui/ChromeSelectionHandler.js
@@ -267,16 +267,24 @@ var ChromeSelectionHandler = {
     this._contentWindow = null;
     this._targetElement = null;
     this._selectionMoveActive = false;
     this._domWinUtils = null;
     this._targetIsEditable = false;
     this.sendAsync("Content:HandlerShutdown", {});
   },
 
+  get hasSelection() {
+    if (!this._targetElement) {
+      return false;
+    }
+    let selection = this._getSelection();
+    return (selection && !selection.isCollapsed);
+  },
+
   /*************************************************
    * Events
    */
 
   /*
    * Scroll + selection advancement timer when the monocle is
    * outside the bounds of an input control.
    */
--- a/browser/metro/base/content/helperui/SelectionHelperUI.js
+++ b/browser/metro/base/content/helperui/SelectionHelperUI.js
@@ -520,16 +520,17 @@ var SelectionHelperUI = {
     window.addEventListener("MozDeckOffsetChanging", this, true);
     window.addEventListener("MozDeckOffsetChanged", this, true);
 
     Elements.browsers.addEventListener("URLChanged", this, true);
     Elements.browsers.addEventListener("SizeChanged", this, true);
     Elements.browsers.addEventListener("ZoomChanged", this, true);
 
     Elements.navbar.addEventListener("transitionend", this, true);
+    Elements.navbar.addEventListener("MozAppbarDismissing", this, true);
 
     this.overlay.enabled = true;
   },
 
   _shutdown: function _shutdown() {
     messageManager.removeMessageListener("Content:SelectionRange", this);
     messageManager.removeMessageListener("Content:SelectionCopied", this);
     messageManager.removeMessageListener("Content:SelectionFail", this);
@@ -546,16 +547,17 @@ var SelectionHelperUI = {
     window.removeEventListener("MozDeckOffsetChanging", this, true);
     window.removeEventListener("MozDeckOffsetChanged", this, true);
 
     Elements.browsers.removeEventListener("URLChanged", this, true);
     Elements.browsers.removeEventListener("SizeChanged", this, true);
     Elements.browsers.removeEventListener("ZoomChanged", this, true);
 
     Elements.navbar.removeEventListener("transitionend", this, true);
+    Elements.navbar.removeEventListener("MozAppbarDismissing", this, true);
 
     this._shutdownAllMarkers();
 
     this._selectionMarkIds = [];
     this._msgTarget = null;
     this._activeSelectionRect = null;
 
     this.overlay.displayDebugLayer = false;
@@ -839,20 +841,17 @@ var SelectionHelperUI = {
       // another input, we'll get a attachToCaret call soonish on the
       // new input.
       this.closeEditSession(false);
       return;
     }
 
     if (this._hitTestSelection(aEvent) && this._targetIsEditable) {
       // Attach to the newly placed caret position
-      this._sendAsyncMessage("Browser:CaretAttach", {
-        xPos: aEvent.clientX,
-        yPos: aEvent.clientY
-      });
+      this.attachToCaret(this._msgTarget, aEvent.clientX, aEvent.clientY);
       return;
     }
 
     // A tap within an editable but outside active selection, clear the
     // selection and flip back to caret mode.
     if (this.startMark.visible && pointInTargetElement &&
         this._targetIsEditable) {
       this._transitionFromSelectionToCaret(clientCoords.x, clientCoords.y);
@@ -895,19 +894,33 @@ var SelectionHelperUI = {
    * Detects when the nav bar hides or shows, so we can enable
    * selection at the appropriate location once the transition is
    * complete, or shutdown selection down when the nav bar is hidden.
    */
   _onNavBarTransitionEvent: function _onNavBarTransitionEvent(aEvent) {
     if (this.layerMode == kContentLayer) {
       return;
     }
+
     if (aEvent.propertyName == "bottom" && Elements.navbar.isShowing) {
       this._sendAsyncMessage("Browser:SelectionUpdate", {});
+      return;
     }
+    
+    if (aEvent.propertyName == "transform" && Elements.navbar.isShowing) {
+      this._sendAsyncMessage("Browser:SelectionUpdate", {});
+      this._showMonocles(ChromeSelectionHandler.hasSelection);
+    }
+  },
+
+  _onNavBarDismissEvent: function _onNavBarDismissEvent() {
+    if (!this.isActive || this.layerMode == kContentLayer) {
+      return;
+    }
+    this._hideMonocles();
   },
 
   /*
    * Event handlers for message manager
    */
 
   _onDebugRectRequest: function _onDebugRectRequest(aMsg) {
     this.overlay.addDebugRect(aMsg.left, aMsg.top, aMsg.right, aMsg.bottom,
@@ -1049,16 +1062,20 @@ var SelectionHelperUI = {
 
       case "MozDeckOffsetChanged":
         this._onDeckOffsetChanged(aEvent);
         break;
 
       case "transitionend":
         this._onNavBarTransitionEvent(aEvent);
         break;
+
+      case "MozAppbarDismissing":
+        this._onNavBarDismissEvent();
+        break;
     }
   },
 
   receiveMessage: function sh_receiveMessage(aMessage) {
     if (this._debugEvents) Util.dumpLn("SelectionHelperUI:", aMessage.name);
     let json = aMessage.json;
     switch (aMessage.name) {
       case "Content:SelectionFail":
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
@@ -83,16 +83,47 @@ gTests.push({
 
     ok(ContextMenuUI._menuPopup._visible, "is visible");
     let paste = document.getElementById("context-paste");
     ok(!paste.hidden, "paste item is visible");
 
     sendElementTap(window, paste);
     ok(edit.popup.popupOpen, "bug: popup should be showing");
 
+    clearSelection(edit);
+    delete window.r;
+  }
+});
+
+gTests.push({
+  desc: "bug 895284 - tap selection",
+  run: function bug887120_test() {
+    gWindow = window;
+
+    yield showNavBar();
+    let edit = document.getElementById("urlbar-edit");
+    edit.value = "wikipedia.org";
+    edit.select();
+
+    let editCoords = logicalCoordsForElement(edit);
+    SelectionHelperUI.attachEditSession(ChromeSelectionHandler, editCoords.x, editCoords.y);
+
+    ok(SelectionHelperUI.isSelectionUIVisible, "selection enabled");
+
+    let selection = edit.QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
+                        .editor.selection;
+    let rects = selection.getRangeAt(0).getClientRects();
+    let midX = Math.ceil(((rects[0].right - rects[0].left) * .5) + rects[0].left);
+    let midY = Math.ceil(((rects[0].bottom - rects[0].top) * .5) + rects[0].top);
+
+    sendTap(window, midX, midY);
+
+    ok(SelectionHelperUI.isCaretUIVisible, "caret browsing enabled");
+
+    clearSelection(edit);
     delete window.r;
   }
 });
 
 function test() {
   if (!isLandscapeMode()) {
     todo(false, "browser_selection_tests need landscape mode to run.");
     return;
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -24,16 +24,17 @@ want to export this environment variable
 
 
 # TODO Bug 794506 Integrate with the in-tree virtualenv configuration.
 SEARCH_PATHS = [
     'python/mach',
     'python/mozboot',
     'python/mozbuild',
     'python/blessings',
+    'python/configobj',
     'python/psutil',
     'python/which',
     'build/pymake',
     'config',
     'other-licenses/ply',
     'xpcom/idl-parser',
     'testing',
     'testing/xpcshell',
--- a/build/virtualenv/packages.txt
+++ b/build/virtualenv/packages.txt
@@ -1,11 +1,12 @@
 simplejson.pth:python/simplejson-2.1.1
 marionette.pth:testing/marionette/client
 blessings.pth:python/blessings
+configobj.pth:python/configobj
 mach.pth:python/mach
 mozbuild.pth:python/mozbuild
 pymake.pth:build/pymake
 optional:setup.py:python/psutil:build_ext:--inplace
 optional:psutil.pth:python/psutil
 which.pth:python/which
 ply.pth:other-licenses/ply/
 codegen.pth:python/codegen/
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -1252,20 +1252,19 @@ nsContentSink::IsTimeToNotify()
   }
 
   if (WaitForPendingSheets()) {
     mDeferredFlushTags = true;
     return false;
   }
 
   PRTime now = PR_Now();
-  int64_t interval, diff;
 
-  LL_I2L(interval, GetNotificationInterval());
-  diff = now - mLastNotificationTime;
+  int64_t interval = GetNotificationInterval();
+  int64_t diff = now - mLastNotificationTime;
 
   if (diff > interval) {
     mBackoffCount--;
     return true;
   }
 
   return false;
 }
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -99,16 +99,17 @@
 
 #ifdef USE_SKIA_GPU
 #undef free // apparently defined by some windows header, clashing with a free()
             // method in SkTypes.h
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "GLContextSkia.h"
 #include "SurfaceTypes.h"
+#include "nsIGfxInfo.h"
 using mozilla::gl::GLContext;
 using mozilla::gl::GLContextProvider;
 #endif
 
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"
 #endif
 
@@ -854,44 +855,50 @@ CanvasRenderingContext2D::EnsureTarget()
 
     if (ownerDoc) {
       layerManager =
         nsContentUtils::PersistentLayerManagerForDocument(ownerDoc);
     }
 
      if (layerManager) {
 #ifdef USE_SKIA_GPU
-       if (gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas()) {
-         SurfaceCaps caps = SurfaceCaps::ForRGBA();
-         caps.preserve = true;
+      if (gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas()) {
+        SurfaceCaps caps = SurfaceCaps::ForRGBA();
+        caps.preserve = true;
 
 #ifdef MOZ_WIDGET_GONK
-         layers::ShadowLayerForwarder *forwarder = layerManager->AsShadowForwarder();
-         if (forwarder) {
-           caps.surfaceAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
-         }
+        layers::ShadowLayerForwarder *forwarder = layerManager->AsShadowForwarder();
+        if (forwarder) {
+          caps.surfaceAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
+        }
 #endif
 
-         DemoteOldestContextIfNecessary();
-
-         nsRefPtr<GLContext> glContext;
-
-         if (!mForceSoftware) {
-           glContext = GLContextProvider::CreateOffscreen(gfxIntSize(size.width, size.height),
-                                                          caps, GLContext::ContextFlagsNone);
-         }
-
-         if (glContext) {
-           SkAutoTUnref<GrGLInterface> i(CreateGrGLInterfaceFromGLContext(glContext));
-           mTarget = Factory::CreateDrawTargetSkiaWithGLContextAndGrGLInterface(glContext, i, size, format);
-           AddDemotableContext(this);
-         } else {
-           mTarget = layerManager->CreateDrawTarget(size, format);
-         }
-       } else
+        DemoteOldestContextIfNecessary();
+
+        nsRefPtr<GLContext> glContext;
+        nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+        nsString vendor;
+
+        if (!mForceSoftware &&
+            gfxInfo &&
+            NS_SUCCEEDED(gfxInfo->GetAdapterVendorID(vendor)) &&
+            StringBeginsWith(vendor, NS_LITERAL_STRING("NVIDIA")))
+        {
+          glContext = GLContextProvider::CreateOffscreen(gfxIntSize(size.width, size.height),
+                                                         caps, GLContext::ContextFlagsNone);
+        }
+
+        if (glContext) {
+          SkAutoTUnref<GrGLInterface> i(CreateGrGLInterfaceFromGLContext(glContext));
+          mTarget = Factory::CreateDrawTargetSkiaWithGLContextAndGrGLInterface(glContext, i, size, format);
+          AddDemotableContext(this);
+        } else {
+          mTarget = layerManager->CreateDrawTarget(size, format);
+        }
+      } else
 #endif
        mTarget = layerManager->CreateDrawTarget(size, format);
      } else {
        mTarget = gfxPlatform::GetPlatform()->CreateOffscreenDrawTarget(size, format);
      }
   }
 
   if (mTarget) {
@@ -3333,17 +3340,19 @@ CanvasRenderingContext2D::DrawWindow(nsI
 
     RefPtr<SourceSurface> data =
       mTarget->CreateSourceSurfaceFromData(img->Data(),
                                            IntSize(size.width, size.height),
                                            img->Stride(),
                                            FORMAT_B8G8R8A8);
     mgfx::Rect destRect(0, 0, w, h);
     mgfx::Rect sourceRect(0, 0, sw, sh);
-    mTarget->DrawSurface(data, destRect, sourceRect);
+    mTarget->DrawSurface(data, destRect, sourceRect,
+                         DrawSurfaceOptions(mgfx::FILTER_POINT),
+                         DrawOptions(1.0f, OP_SOURCE, AA_NONE));
     mTarget->Flush();
   } else {
     mTarget->SetTransform(matrix);
   }
 
   // note that x and y are coordinates in the document that
   // we're drawing; x and y are drawn to 0,0 in current user
   // space.
--- a/content/canvas/test/reftest/webgl-clear-test.html
+++ b/content/canvas/test/reftest/webgl-clear-test.html
@@ -1,49 +1,49 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<head>
-<meta charset="UTF-8">
-
-<script type="text/javascript" src="webgl-utils.js"></script>
-<script type="text/javascript">
-/* Clear Test
- *
- * Clear the canvas to green to test that we get pixels to the screen.
- */
-
-"use strict";
-
-function renderGL(gl) {
-  gl.clearColor(0.0, 1.0, 0.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-  gl.finish();
-}
-
-function renderBackup(canvas) {
-  var context = canvas.getContext("2d");
-  context.fillStyle = "rgba(0, 255, 0, 1.0)";
-  context.fillRect(0, 0, 256, 256);
-}
-
-function runTest() {
-  var canvas = document.getElementById("canvas");
-  var gl = initGL(canvas);
-
-  if (gl)
-    renderGL(gl);
-  else
-    renderBackup(canvas);
-
-  rAF(testComplete);
-}
-
-function testComplete() {
-  document.documentElement.removeAttribute("class");
-}
-</script>
-</head>
-
-<body onload="rAF(runTest);">
-  <canvas id="canvas" width="256" height="256"></canvas>
-</body>
-
-</html>
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+
+<script type="text/javascript" src="webgl-utils.js"></script>
+<script type="text/javascript">
+/* Clear Test
+ *
+ * Clear the canvas to green to test that we get pixels to the screen.
+ */
+
+"use strict";
+
+function renderGL(gl) {
+  gl.clearColor(0.0, 1.0, 0.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+  gl.finish();
+}
+
+function renderBackup(canvas) {
+  var context = canvas.getContext("2d");
+  context.fillStyle = "rgba(0, 255, 0, 1.0)";
+  context.fillRect(0, 0, 256, 256);
+}
+
+function runTest() {
+  var canvas = document.getElementById("canvas");
+  var gl = initGL(canvas);
+
+  if (gl)
+    renderGL(gl);
+  else
+    renderBackup(canvas);
+
+  rAF(testComplete);
+}
+
+function testComplete() {
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="rAF(runTest);">
+  <canvas id="canvas" width="256" height="256"></canvas>
+</body>
+
+</html>
--- a/content/canvas/test/reftest/webgl-color-alpha-test.html
+++ b/content/canvas/test/reftest/webgl-color-alpha-test.html
@@ -1,101 +1,101 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<head>
-<meta charset="UTF-8">
-
-<script type="text/javascript" src="webgl-utils.js"></script>
-<script type="text/javascript">
-/* Color-Alpha Test
- *
- * Clear the four quadrants of the canvas as follows:
- * +------+------+
- * | red  |green |
- * |      |      |
- * +------+------+
- * | blue |white |
- * |      |      |
- * +------+------+
- * However, unlike the Color test, clear with a given alpha value.
- * What effect this has depends on the context-creation args passed
- * to this page.
- *
- * Here we check that we handle various combinations of alpha and
- * premultipliedAlpha correctly.
- */
-
-"use strict";
-
-function renderGL(gl, value, alpha) {
-  gl.enable(gl.SCISSOR_TEST);
-
-  gl.scissor(0, 128, 128, 128);
-  gl.clearColor(value, 0.0, 0.0, alpha);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.scissor(128, 128, 128, 128);
-  gl.clearColor(0.0, value, 0.0, alpha);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.scissor(0, 0, 128, 128);
-  gl.clearColor(0.0, 0.0, value, alpha);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.scissor(128, 0, 128, 128);
-  gl.clearColor(value, value, value, alpha);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.finish();
-}
-
-function renderBackup(canvas, value, alpha) {
-  if (!arg("alpha"))
-    alpha = 1.0;
-
-  if (arg("alpha") && arg("premult")) {
-    if (alpha == 0.0)
-      value = 1.0;
-    else
-      value /= alpha;
-  }
-
-  var intValue = (value * 255) | 0;
-  var context = canvas.getContext("2d");
-  context.fillStyle = "rgba(" + intValue + ", 0, 0, " + alpha + ")";
-  context.fillRect(0, 0, 128, 128);
-
-  context.fillStyle = "rgba(0, " + intValue + ", 0, " + alpha + ")";
-  context.fillRect(128, 0, 128, 128);
-
-  context.fillStyle = "rgba(0, 0, " + intValue + ", " + alpha + ")";
-  context.fillRect(0, 128, 128, 128);
-
-  context.fillStyle = "rgba(" + intValue + ", " + intValue + ", " + intValue + ", " + alpha + ")";
-  context.fillRect(128, 128, 128, 128);
-}
-
-function runTest() {
-  var canvas = document.getElementById("canvas");
-  var gl = initGL(canvas);
-
-  var value = arg("colorVal");
-  var alpha = arg("alphaVal");
-
-  if (gl)
-    renderGL(gl, value, alpha);
-  else
-    renderBackup(canvas, value, alpha);
-
-  rAF(testComplete);
-}
-
-function testComplete() {
-  document.documentElement.removeAttribute("class");
-}
-</script>
-</head>
-
-<body onload="rAF(runTest);">
-  <canvas id="canvas" width="256" height="256"></canvas>
-</body>
-
-</html>
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+
+<script type="text/javascript" src="webgl-utils.js"></script>
+<script type="text/javascript">
+/* Color-Alpha Test
+ *
+ * Clear the four quadrants of the canvas as follows:
+ * +------+------+
+ * | red  |green |
+ * |      |      |
+ * +------+------+
+ * | blue |white |
+ * |      |      |
+ * +------+------+
+ * However, unlike the Color test, clear with a given alpha value.
+ * What effect this has depends on the context-creation args passed
+ * to this page.
+ *
+ * Here we check that we handle various combinations of alpha and
+ * premultipliedAlpha correctly.
+ */
+
+"use strict";
+
+function renderGL(gl, value, alpha) {
+  gl.enable(gl.SCISSOR_TEST);
+
+  gl.scissor(0, 128, 128, 128);
+  gl.clearColor(value, 0.0, 0.0, alpha);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.scissor(128, 128, 128, 128);
+  gl.clearColor(0.0, value, 0.0, alpha);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.scissor(0, 0, 128, 128);
+  gl.clearColor(0.0, 0.0, value, alpha);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.scissor(128, 0, 128, 128);
+  gl.clearColor(value, value, value, alpha);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.finish();
+}
+
+function renderBackup(canvas, value, alpha) {
+  if (!arg("alpha"))
+    alpha = 1.0;
+
+  if (arg("alpha") && arg("premult")) {
+    if (alpha == 0.0)
+      value = 1.0;
+    else
+      value /= alpha;
+  }
+
+  var intValue = (value * 255) | 0;
+  var context = canvas.getContext("2d");
+  context.fillStyle = "rgba(" + intValue + ", 0, 0, " + alpha + ")";
+  context.fillRect(0, 0, 128, 128);
+
+  context.fillStyle = "rgba(0, " + intValue + ", 0, " + alpha + ")";
+  context.fillRect(128, 0, 128, 128);
+
+  context.fillStyle = "rgba(0, 0, " + intValue + ", " + alpha + ")";
+  context.fillRect(0, 128, 128, 128);
+
+  context.fillStyle = "rgba(" + intValue + ", " + intValue + ", " + intValue + ", " + alpha + ")";
+  context.fillRect(128, 128, 128, 128);
+}
+
+function runTest() {
+  var canvas = document.getElementById("canvas");
+  var gl = initGL(canvas);
+
+  var value = arg("colorVal");
+  var alpha = arg("alphaVal");
+
+  if (gl)
+    renderGL(gl, value, alpha);
+  else
+    renderBackup(canvas, value, alpha);
+
+  rAF(testComplete);
+}
+
+function testComplete() {
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="rAF(runTest);">
+  <canvas id="canvas" width="256" height="256"></canvas>
+</body>
+
+</html>
--- a/content/canvas/test/reftest/webgl-color-test.html
+++ b/content/canvas/test/reftest/webgl-color-test.html
@@ -1,85 +1,85 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<head>
-<meta charset="UTF-8">
-
-<script type="text/javascript" src="webgl-utils.js"></script>
-<script type="text/javascript">
-/* Color Test
- *
- * Clear the four quadrants of the canvas as follows:
- * +------+------+
- * | red  |green |
- * |      |      |
- * +------+------+
- * | blue |white |
- * |      |      |
- * +------+------+
- *
- * This is for checking that we're getting the right colors when
- * we ask for them. This combined with the Orientation test assure
- * that we are getting the correct colors in the correct places.
- */
-
-"use strict";
-
-function renderGL(gl) {
-  gl.enable(gl.SCISSOR_TEST);
-
-  gl.scissor(0, 128, 128, 128);
-  gl.clearColor(1.0, 0.0, 0.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.scissor(128, 128, 128, 128);
-  gl.clearColor(0.0, 1.0, 0.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.scissor(0, 0, 128, 128);
-  gl.clearColor(0.0, 0.0, 1.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.scissor(128, 0, 128, 128);
-  gl.clearColor(1.0, 1.0, 1.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.finish();
-}
-
-function renderBackup(canvas) {
-  var context = canvas.getContext("2d");
-  context.fillStyle = "rgba(255, 0, 0, 1.0)";
-  context.fillRect(0, 0, 128, 128);
-
-  context.fillStyle = "rgba(0, 255, 0, 1.0)";
-  context.fillRect(128, 0, 128, 128);
-
-  context.fillStyle = "rgba(0, 0, 255, 1.0)";
-  context.fillRect(0, 128, 128, 128);
-
-  context.fillStyle = "rgba(255, 255, 255, 1.0)";
-  context.fillRect(128, 128, 128, 128);
-}
-
-function runTest() {
-  var canvas = document.getElementById("canvas");
-  var gl = initGL(canvas);
-
-  if (gl)
-    renderGL(gl);
-  else
-    renderBackup(canvas);
-
-  rAF(testComplete);
-}
-
-function testComplete() {
-  document.documentElement.removeAttribute("class");
-}
-</script>
-</head>
-
-<body onload="rAF(runTest);">
-  <canvas id="canvas" width="256" height="256"></canvas>
-</body>
-
-</html>
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+
+<script type="text/javascript" src="webgl-utils.js"></script>
+<script type="text/javascript">
+/* Color Test
+ *
+ * Clear the four quadrants of the canvas as follows:
+ * +------+------+
+ * | red  |green |
+ * |      |      |
+ * +------+------+
+ * | blue |white |
+ * |      |      |
+ * +------+------+
+ *
+ * This is for checking that we're getting the right colors when
+ * we ask for them. This combined with the Orientation test assure
+ * that we are getting the correct colors in the correct places.
+ */
+
+"use strict";
+
+function renderGL(gl) {
+  gl.enable(gl.SCISSOR_TEST);
+
+  gl.scissor(0, 128, 128, 128);
+  gl.clearColor(1.0, 0.0, 0.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.scissor(128, 128, 128, 128);
+  gl.clearColor(0.0, 1.0, 0.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.scissor(0, 0, 128, 128);
+  gl.clearColor(0.0, 0.0, 1.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.scissor(128, 0, 128, 128);
+  gl.clearColor(1.0, 1.0, 1.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.finish();
+}
+
+function renderBackup(canvas) {
+  var context = canvas.getContext("2d");
+  context.fillStyle = "rgba(255, 0, 0, 1.0)";
+  context.fillRect(0, 0, 128, 128);
+
+  context.fillStyle = "rgba(0, 255, 0, 1.0)";
+  context.fillRect(128, 0, 128, 128);
+
+  context.fillStyle = "rgba(0, 0, 255, 1.0)";
+  context.fillRect(0, 128, 128, 128);
+
+  context.fillStyle = "rgba(255, 255, 255, 1.0)";
+  context.fillRect(128, 128, 128, 128);
+}
+
+function runTest() {
+  var canvas = document.getElementById("canvas");
+  var gl = initGL(canvas);
+
+  if (gl)
+    renderGL(gl);
+  else
+    renderBackup(canvas);
+
+  rAF(testComplete);
+}
+
+function testComplete() {
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="rAF(runTest);">
+  <canvas id="canvas" width="256" height="256"></canvas>
+</body>
+
+</html>
--- a/content/canvas/test/reftest/webgl-hanging-fb-test.html
+++ b/content/canvas/test/reftest/webgl-hanging-fb-test.html
@@ -1,58 +1,58 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<head>
-<meta charset="UTF-8">
-
-<script type="text/javascript" src="webgl-utils.js"></script>
-<script type="text/javascript">
-/* Hanging Framebuffer Test
- *
- * Clear the canvas to green, but create and bind a new framebuffer
- * before returning. This will fail if we blindly read from the bound
- * framebuffer, instead of binding to the screen and reading from that.
- *
- * How failure looks isn't well defined, since this is an empty framebuffer,
- * thus is incomplete, and should cause errors if it's read from.
- */
-
-"use strict";
-
-function renderGL(gl) {
-  gl.clearColor(0.0, 1.0, 0.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  var fb = gl.createFramebuffer();
-  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
-
-  gl.finish();
-}
-
-function renderBackup(canvas) {
-  var context = canvas.getContext("2d");
-  context.fillStyle = "rgba(0, 255, 0, 1.0)";
-  context.fillRect(0, 0, 256, 256);
-}
-
-function runTest() {
-  var canvas = document.getElementById("canvas");
-  var gl = initGL(canvas);
-
-  if (gl)
-    renderGL(gl);
-  else
-    renderBackup(canvas);
-
-  rAF(testComplete);
-}
-
-function testComplete() {
-  document.documentElement.removeAttribute("class");
-}
-</script>
-</head>
-
-<body onload="rAF(runTest);">
-  <canvas id="canvas" width="256" height="256"></canvas>
-</body>
-
-</html>
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+
+<script type="text/javascript" src="webgl-utils.js"></script>
+<script type="text/javascript">
+/* Hanging Framebuffer Test
+ *
+ * Clear the canvas to green, but create and bind a new framebuffer
+ * before returning. This will fail if we blindly read from the bound
+ * framebuffer, instead of binding to the screen and reading from that.
+ *
+ * How failure looks isn't well defined, since this is an empty framebuffer,
+ * thus is incomplete, and should cause errors if it's read from.
+ */
+
+"use strict";
+
+function renderGL(gl) {
+  gl.clearColor(0.0, 1.0, 0.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  var fb = gl.createFramebuffer();
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+
+  gl.finish();
+}
+
+function renderBackup(canvas) {
+  var context = canvas.getContext("2d");
+  context.fillStyle = "rgba(0, 255, 0, 1.0)";
+  context.fillRect(0, 0, 256, 256);
+}
+
+function runTest() {
+  var canvas = document.getElementById("canvas");
+  var gl = initGL(canvas);
+
+  if (gl)
+    renderGL(gl);
+  else
+    renderBackup(canvas);
+
+  rAF(testComplete);
+}
+
+function testComplete() {
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="rAF(runTest);">
+  <canvas id="canvas" width="256" height="256"></canvas>
+</body>
+
+</html>
--- a/content/canvas/test/reftest/webgl-hanging-scissor-test.html
+++ b/content/canvas/test/reftest/webgl-hanging-scissor-test.html
@@ -1,57 +1,57 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<head>
-<meta charset="UTF-8">
-
-<script type="text/javascript" src="webgl-utils.js"></script>
-<script type="text/javascript">
-/* Hanging Scissor Test
- *
- * Clear the canvas to green, but create and enable and set scissor values
- * before returning. This can fail if we blindly blit or read from the screen
- * without disabling scissor-test.
- *
- * Failure should look like only the top-left quadrant is rendered.
- */
-
-"use strict";
-
-function renderGL(gl) {
-  gl.clearColor(0.0, 1.0, 0.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.enable(gl.SCISSOR_TEST);
-  gl.scissor(0, 128, 128, 128);
-
-  gl.finish();
-}
-
-function renderBackup(canvas) {
-  var context = canvas.getContext("2d");
-  context.fillStyle = "rgba(0, 255, 0, 1.0)";
-  context.fillRect(0, 0, 256, 256);
-}
-
-function runTest() {
-  var canvas = document.getElementById("canvas");
-  var gl = initGL(canvas);
-
-  if (gl)
-    renderGL(gl);
-  else
-    renderBackup(canvas);
-
-  rAF(testComplete);
-}
-
-function testComplete() {
-  document.documentElement.removeAttribute("class");
-}
-</script>
-</head>
-
-<body onload="rAF(runTest);">
-  <canvas id="canvas" width="256" height="256"></canvas>
-</body>
-
-</html>
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+
+<script type="text/javascript" src="webgl-utils.js"></script>
+<script type="text/javascript">
+/* Hanging Scissor Test
+ *
+ * Clear the canvas to green, but create and enable and set scissor values
+ * before returning. This can fail if we blindly blit or read from the screen
+ * without disabling scissor-test.
+ *
+ * Failure should look like only the top-left quadrant is rendered.
+ */
+
+"use strict";
+
+function renderGL(gl) {
+  gl.clearColor(0.0, 1.0, 0.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.enable(gl.SCISSOR_TEST);
+  gl.scissor(0, 128, 128, 128);
+
+  gl.finish();
+}
+
+function renderBackup(canvas) {
+  var context = canvas.getContext("2d");
+  context.fillStyle = "rgba(0, 255, 0, 1.0)";
+  context.fillRect(0, 0, 256, 256);
+}
+
+function runTest() {
+  var canvas = document.getElementById("canvas");
+  var gl = initGL(canvas);
+
+  if (gl)
+    renderGL(gl);
+  else
+    renderBackup(canvas);
+
+  rAF(testComplete);
+}
+
+function testComplete() {
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="rAF(runTest);">
+  <canvas id="canvas" width="256" height="256"></canvas>
+</body>
+
+</html>
--- a/content/canvas/test/reftest/webgl-orientation-test.html
+++ b/content/canvas/test/reftest/webgl-orientation-test.html
@@ -1,60 +1,60 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<head>
-<meta charset="UTF-8">
-
-<script type="text/javascript" src="webgl-utils.js"></script>
-<script type="text/javascript">
-/* Orientation Test
- *
- * Clear the canvas to black, and clear the upper-left quadrant
- * to white. If this doesn't pass, but the Clear test does, then
- * likely y-flip is wrong.
- */
-
-"use strict";
-
-function renderGL(gl) {
-  gl.clearColor(0.0, 0.0, 0.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.enable(gl.SCISSOR_TEST);
-  gl.scissor(0, 128, 128, 128);
-  gl.clearColor(1.0, 1.0, 1.0, 1.0);
-  gl.clear(gl.COLOR_BUFFER_BIT);
-
-  gl.finish();
-}
-
-function renderBackup(canvas) {
-  var context = canvas.getContext("2d");
-  context.fillStyle = "rgba(0, 0, 0, 1.0)";
-  context.fillRect(0, 0, 256, 256);
-
-  context.fillStyle = "rgba(255, 255, 255, 1.0)";
-  context.fillRect(0, 0, 128, 128);
-}
-
-function runTest() {
-  var canvas = document.getElementById("canvas");
-  var gl = initGL(canvas);
-
-  if (gl)
-    renderGL(gl);
-  else
-    renderBackup(canvas);
-
-  rAF(testComplete);
-}
-
-function testComplete() {
-  document.documentElement.removeAttribute("class");
-}
-</script>
-</head>
-
-<body onload="rAF(runTest);">
-  <canvas id="canvas" width="256" height="256"></canvas>
-</body>
-
-</html>
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+
+<script type="text/javascript" src="webgl-utils.js"></script>
+<script type="text/javascript">
+/* Orientation Test
+ *
+ * Clear the canvas to black, and clear the upper-left quadrant
+ * to white. If this doesn't pass, but the Clear test does, then
+ * likely y-flip is wrong.
+ */
+
+"use strict";
+
+function renderGL(gl) {
+  gl.clearColor(0.0, 0.0, 0.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.enable(gl.SCISSOR_TEST);
+  gl.scissor(0, 128, 128, 128);
+  gl.clearColor(1.0, 1.0, 1.0, 1.0);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+
+  gl.finish();
+}
+
+function renderBackup(canvas) {
+  var context = canvas.getContext("2d");
+  context.fillStyle = "rgba(0, 0, 0, 1.0)";
+  context.fillRect(0, 0, 256, 256);
+
+  context.fillStyle = "rgba(255, 255, 255, 1.0)";
+  context.fillRect(0, 0, 128, 128);
+}
+
+function runTest() {
+  var canvas = document.getElementById("canvas");
+  var gl = initGL(canvas);
+
+  if (gl)
+    renderGL(gl);
+  else
+    renderBackup(canvas);
+
+  rAF(testComplete);
+}
+
+function testComplete() {
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="rAF(runTest);">
+  <canvas id="canvas" width="256" height="256"></canvas>
+</body>
+
+</html>
--- a/content/canvas/test/test_canvas.html
+++ b/content/canvas/test/test_canvas.html
@@ -2821,24 +2821,24 @@ var canvas = document.getElementById('c1
 var ctx = canvas.getContext('2d');
 
 ctx.fillStyle = '#f00';
 ctx.fillRect(0, 0, 100, 50);
 ctx.drawImage(document.getElementById('rgrg-256x256_2.png'), 0, 0, 256, 256, 0, 0, 100, 50);
 ctx.fillStyle = '#0f0';
 ctx.fillRect(0, 0, 51, 26);
 ctx.fillRect(49, 24, 51, 26);
-isPixel(ctx, 0,0, 0,255,0,255, 2);
-isPixel(ctx, 99,0, 0,255,0,255, 2);
-isPixel(ctx, 0,49, 0,255,0,255, 2);
-isPixel(ctx, 99,49, 0,255,0,255, 2);
-isPixel(ctx, 20,20, 0,255,0,255, 2);
-isPixel(ctx, 80,20, 0,255,0,255, 2);
-isPixel(ctx, 20,30, 0,255,0,255, 2);
-isPixel(ctx, 80,30, 0,255,0,255, 2);
+isPixel(ctx, 0,0, 0,255,0,255, 3);
+isPixel(ctx, 99,0, 0,255,0,255, 3);
+isPixel(ctx, 0,49, 0,255,0,255, 3);
+isPixel(ctx, 99,49, 0,255,0,255, 3);
+isPixel(ctx, 20,20, 0,255,0,255, 3);
+isPixel(ctx, 80,20, 0,255,0,255, 3);
+isPixel(ctx, 20,30, 0,255,0,255, 3);
+isPixel(ctx, 80,30, 0,255,0,255, 3);
 
 
 }
 </script>
 <img src="image_rgrg-256x256.png" id="rgrg-256x256_2.png" class="resource">
 
 <!-- [[[ test_2d.drawImage.alpha.html ]]] -->
 
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -306,21 +306,17 @@ nsSHistory::CalcMaxTotalViewers()
     return 0;
 
   // Conversion from unsigned int64_t to double doesn't work on all platforms.
   // We need to truncate the value at INT64_MAX to make sure we don't
   // overflow.
   if (bytes > INT64_MAX)
     bytes = INT64_MAX;
 
-  uint64_t kbytes;
-  LL_SHR(kbytes, bytes, 10);
-
-  double kBytesD;
-  LL_L2D(kBytesD, (int64_t) kbytes);
+  double kBytesD = (double)(bytes >> 10);
 
   // This is essentially the same calculation as for nsCacheService,
   // except that we divide the final memory calculation by 4, since
   // we assume each ContentViewer takes on average 4MB
   uint32_t viewers = 0;
   double x = std::log(kBytesD)/std::log(2.0) - 14;
   if (x > 0) {
     viewers    = (uint32_t)(x * x - x + 2.001); // add .001 for rounding
--- a/dom/apps/tests/test_app_update.html
+++ b/dom/apps/tests/test_app_update.html
@@ -28,18 +28,30 @@ https://bugzilla.mozilla.org/show_bug.cg
       function() { gGenerator.next() });
   }
 
   function continueTest() {
     try { gGenerator.next(); }
     catch (e) { dump("Got exception: " + e + "\n"); }
   }
 
-  function cbError() {
-    ok(false, "Error callback invoked: " + this.error.name);
+  function mozAppsError() {
+    ok(false, "mozApps error: " + this.error.name);
+    finish();
+  }
+
+  function xhrError(event, url) {
+    var xhr = event.target;
+    ok(false, "XHR error loading " + url + ": " + xhr.status + " - " +
+              xhr.statusText);
+    finish();
+  }
+
+  function xhrAbort(url) {
+    ok(false, "XHR abort loading " + url);
     finish();
   }
 
   function runTest() {
     // Set up.
 
     launchableValue = SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
@@ -53,17 +65,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     checkAppState(null, false, 1, continueTest);
     yield undefined;
 
     // Bump the version and install the app.
     setAppVersion(2, continueTest);
     yield undefined;
 
     var request = navigator.mozApps.install(gHostedManifestURL);
-    request.onerror = cbError;
+    request.onerror = mozAppsError;
     request.onsuccess = continueTest;
     yield undefined;
     var app = request.result;
     ok(app, "App is non-null");
     ok(app.manifest.description == "Updated even faster than Firefox, just to annoy slashdotters.",
        "Manifest is HTML-sanitized");
 
     // Check the app a few times.
@@ -101,92 +113,94 @@ https://bugzilla.mozilla.org/show_bug.cg
     icons = app.manifest.icons;
     var newIcon = icons[Object.keys(icons)[0]];
     var newUpdateTime = app.updateTime;
     isnot(oldIcon, newIcon, 'The icon should be updated');
     isnot(oldUpdateTime, newUpdateTime, 'The update time should be updated');
 
     // Uninstall the app.
     request = navigator.mozApps.mgmt.uninstall(app);
-    request.onerror = cbError;
+    request.onerror = mozAppsError;
     request.onsuccess = continueTest;
     yield undefined;
 
     // Check the uninstalled app.
     checkAppState(app, false, 3, continueTest);
     yield undefined;
 
     // Install the cached app.
     setAppVersion(3, continueTest);
     yield undefined;
     ok(true, "Installing cached app");
     var request = navigator.mozApps.install(gCachedManifestURL);
-    request.onerror = cbError;
+    request.onerror = mozAppsError;
     request.onsuccess = continueTest;
     yield undefined;
     var app = request.result;
     ok(app, "App is non-null");
     if (app.installState == "pending") {
       ok(true, "App is pending. Waiting for progress");
       app.onprogress = function() ok(true, "Got download progress");
       app.ondownloadsuccess = continueTest;
-      app.ondownloaderror = cbError;
+      app.ondownloaderror = mozAppsError;
       yield undefined;
     }
     is(app.installState, "installed", "App is installed");
 
     // Check the cached app.
     checkAppState(app, true, 3, continueTest);
     yield undefined;
 
     // Check for updates. The current infrastructure always returns a new appcache
     // manifest, so there should always be an update.
     var lastCheck = app.lastUpdateCheck;
     ok(true, "Setting callbacks");
     app.ondownloadapplied = function() ok(true, "downloadapplied fired.");
     app.ondownloadavailable = function() ok(false, "downloadavailable fired");
     ok(true, "Checking for updates");
     var request = app.checkForUpdate();
-    request.onerror = cbError;
+    request.onerror = mozAppsError;
     request.onsuccess = continueTest;
     yield undefined;
     todo(app.lastUpdateCheck > lastCheck, "lastUpdateCheck updated appropriately");
 
 
     // Uninstall the app.
     request = navigator.mozApps.mgmt.uninstall(app);
-    request.onerror = cbError;
+    request.onerror = mozAppsError;
     request.onsuccess = continueTest;
     yield undefined;
     ok(true, "Uninstalled app");
 
     // Check the uninstalled app.
     checkAppState(app, false, 3, continueTest);
     yield undefined;
 
     // All done.
     ok(true, "All done");
     finish();
   }
 
   function setAppVersion(version, cb) {
     var xhr = new XMLHttpRequest();
-    xhr.addEventListener("load", function() { is(xhr.responseText, "OK", "setVersion OK"); cb(); });
-    xhr.addEventListener("error", cbError);
-    xhr.addEventListener("abort", cbError);
-    xhr.open('GET', gBaseURL + 'file_app.sjs?setVersion=' + version, true);
+    var url = gBaseURL + 'file_app.sjs?setVersion=' + version;
+    xhr.addEventListener("load", function() { is(xhr.responseText, "OK", "setAppVersion OK"); cb(); });
+    xhr.addEventListener("error", event => xhrError(event, url));
+    xhr.addEventListener("abort", event => xhrAbort(url));
+    xhr.open('GET', url, true);
     xhr.send();
   }
 
   function setAppIcon(icon, cb) {
     var xhr = new XMLHttpRequest();
-    xhr.addEventListener("load", function() { is(xhr.responseText, "OK", "setIcon OK"); cb(); });
-    xhr.addEventListener("error", cbError);
-    xhr.addEventListener("abort", cbError);
-    xhr.open('GET', gBaseURL + 'file_app.sjs?setIcon=' + icon, true);
+    var url = gBaseURL + 'file_app.sjs?setIcon=' + icon;
+    xhr.addEventListener("load", function() { is(xhr.responseText, "OK", "setAppIcon OK"); cb(); });
+    xhr.addEventListener("error", event => xhrError(event, url));
+    xhr.addEventListener("abort", event => xhrAbort(url));
+    xhr.open('GET', url, true);
     xhr.send();
   }
 
   // This function checks the state of an installed app. It does the following:
   //
   // * Check various state on the app object itself.
   // * Launch the app.
   // * Listen for messages from the app, verifying state.
--- a/dom/apps/tests/test_packaged_app_install.html
+++ b/dom/apps/tests/test_packaged_app_install.html
@@ -53,30 +53,43 @@ function go() {
 }
 
 function finish() {
   SpecialPowers.setAllAppsLaunchable(launchableValue);
   SpecialPowers.removePermission("webapps-manage", document);
   SimpleTest.finish();
 }
 
-function cbError(aError) {
-  ok(false, "Error callback invoked " + aError);
+function mozAppsError() {
+  ok(false, "mozApps error: " + this.error.name);
+  finish();
+}
+
+function xhrError(event, url) {
+  var xhr = event.target;
+  ok(false, "XHR error loading " + url + ": " + xhr.status + " - " +
+            xhr.statusText);
+  finish();
+}
+
+function xhrAbort(url) {
+  ok(false, "XHR abort loading " + url);
   finish();
 }
 
 function setAppVersion(aVersion, aCb) {
   var xhr = new XMLHttpRequest();
+  var url = gSJS + "?setVersion=" + aVersion;
   xhr.addEventListener("load", function() {
-    is(xhr.responseText, "OK", "setVersion OK");
+    is(xhr.responseText, "OK", "setAppVersion OK");
     aCb();
   });
-  xhr.addEventListener("error", cbError);
-  xhr.addEventListener("abort", cbError);
-  xhr.open("GET", gSJS + "?setVersion=" + aVersion, true);
+  xhr.addEventListener("error", event => xhrError(event, url));
+  xhr.addEventListener("abort", event => xhrAbort(url));
+  xhr.open("GET", url, true);
   xhr.send();
 }
 
 function checkAppInstallError(aMiniManifestURL, aExpectedError) {
   var req = navigator.mozApps.installPackage(aMiniManifestURL);
   req.onsuccess = function() {
     ok(false, "We are supposed to throw " + aExpectedError);
     finish();
@@ -375,19 +388,17 @@ var steps = [
           size: 0,
           readyToApplyDownload: false,
         };
         checkAppState(gApp, 2, expected, true, false, next);
       };
     };
 
     var request = navigator.mozApps.installPackage(miniManifestURL);
-    request.onerror = function(evt) {
-      cbError(evt.target.error.name);
-    };
+    request.onerror = mozAppsError;
     request.onsuccess = function() {
       ok(true, "Application installed");
     };
   },
   function() {
     ok(true, "all done!\n");
     SimpleTest.finish();
   }
--- a/dom/plugins/base/nsPluginStreamListenerPeer.cpp
+++ b/dom/plugins/base/nsPluginStreamListenerPeer.cpp
@@ -823,17 +823,17 @@ NS_IMETHODIMP nsPluginStreamListenerPeer
   // call OnDataAvailable
   if (mStreamType != NP_ASFILEONLY) {
     // get the absolute offset of the request, if one exists.
     nsCOMPtr<nsIByteRangeRequest> brr = do_QueryInterface(request);
     if (brr) {
       if (!mDataForwardToRequest)
         return NS_ERROR_FAILURE;
       
-      int64_t absoluteOffset64 = LL_ZERO;
+      int64_t absoluteOffset64 = 0;
       brr->GetStartRange(&absoluteOffset64);
       
       // XXX handle 64-bit for real
       int32_t absoluteOffset = (int32_t)int64_t(absoluteOffset64);
       
       // we need to track how much data we have forwarded to the
       // plugin.
       
@@ -904,17 +904,17 @@ NS_IMETHODIMP nsPluginStreamListenerPeer
   
   PLUGIN_LOG(PLUGIN_LOG_NOISY,
              ("nsPluginStreamListenerPeer::OnStopRequest this=%p aStatus=%d request=%p\n",
               this, aStatus, request));
   
   // for ByteRangeRequest we're just updating the mDataForwardToRequest hash and return.
   nsCOMPtr<nsIByteRangeRequest> brr = do_QueryInterface(request);
   if (brr) {
-    int64_t absoluteOffset64 = LL_ZERO;
+    int64_t absoluteOffset64 = 0;
     brr->GetStartRange(&absoluteOffset64);
     // XXX support 64-bit offsets
     int32_t absoluteOffset = (int32_t)int64_t(absoluteOffset64);
     
     nsPRUintKey key(absoluteOffset);
     
     // remove the request from our data forwarding count hash.
     mDataForwardToRequest->Remove(&key);
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -22,22 +22,16 @@
 #include "skia/SkLayerRasterizer.h"
 #include "skia/SkLayerDrawLooper.h"
 #include "skia/SkDashPathEffect.h"
 #include "Logging.h"
 #include "HelpersSkia.h"
 #include "Tools.h"
 #include <algorithm>
 
-#ifdef ANDROID
-# define USE_SOFT_CLIPPING false
-#else
-# define USE_SOFT_CLIPPING true
-#endif
-
 namespace mozilla {
 namespace gfx {
 
 class GradientStopsSkia : public GradientStops
 {
 public:
   GradientStopsSkia(const std::vector<GradientStop>& aStops, uint32_t aNumStops, ExtendMode aExtendMode)
     : mCount(aNumStops)
@@ -78,16 +72,21 @@ public:
   std::vector<SkColor> mColors;
   std::vector<SkScalar> mPositions;
   int mCount;
   ExtendMode mExtendMode;
 };
 
 DrawTargetSkia::DrawTargetSkia()
 {
+#ifdef ANDROID
+  mSoftClipping = false;
+#else
+  mSoftClipping = true;
+#endif
 }
 
 DrawTargetSkia::~DrawTargetSkia()
 {
   MOZ_ASSERT(mSnapshots.size() == 0);
 }
 
 TemporaryRef<SourceSurface>
@@ -645,23 +644,27 @@ DrawTargetSkia::InitWithGLContextAndGrGL
                                                   GrGLInterface* aGrGLInterface,
                                                   const IntSize &aSize,
                                                   SurfaceFormat aFormat)
 {
   mGLContext = aGLContext;
   mSize = aSize;
   mFormat = aFormat;
 
+  // Always use soft clipping when we're using GL
+  mSoftClipping = true;
+
   mGrGLInterface = aGrGLInterface;
   mGrGLInterface->fCallbackData = reinterpret_cast<GrGLInterfaceCallbackData>(this);
 
   GrBackendContext backendContext = reinterpret_cast<GrBackendContext>(aGrGLInterface);
   SkAutoTUnref<GrContext> gr(GrContext::Create(kOpenGL_GrBackend, backendContext));
   mGrContext = gr.get();
 
+  mGrContext->setTextureCacheLimits(128, 1024*1024*16);
 
   GrBackendRenderTargetDesc targetDescriptor;
 
   targetDescriptor.fWidth = mSize.width;
   targetDescriptor.fHeight = mSize.height;
   targetDescriptor.fConfig = GfxFormatToGrConfig(mFormat);
   targetDescriptor.fOrigin = kBottomLeft_GrSurfaceOrigin;
   targetDescriptor.fSampleCnt = 0;
@@ -717,42 +720,42 @@ DrawTargetSkia::CreatePathBuilder(FillRu
 }
 
 void
 DrawTargetSkia::ClearRect(const Rect &aRect)
 {
   MarkChanged();
   SkPaint paint;
   mCanvas->save();
-  mCanvas->clipRect(RectToSkRect(aRect), SkRegion::kIntersect_Op, USE_SOFT_CLIPPING);
+  mCanvas->clipRect(RectToSkRect(aRect), SkRegion::kIntersect_Op, mSoftClipping);
   paint.setColor(SkColorSetARGB(0, 0, 0, 0));
   paint.setXfermodeMode(SkXfermode::kSrc_Mode);
   mCanvas->drawPaint(paint);
   mCanvas->restore();
 }
 
 void
 DrawTargetSkia::PushClip(const Path *aPath)
 {
   if (aPath->GetBackendType() != BACKEND_SKIA) {
     return;
   }
 
   const PathSkia *skiaPath = static_cast<const PathSkia*>(aPath);
   mCanvas->save(SkCanvas::kClip_SaveFlag);
-  mCanvas->clipPath(skiaPath->GetPath(), SkRegion::kIntersect_Op, USE_SOFT_CLIPPING);
+  mCanvas->clipPath(skiaPath->GetPath(), SkRegion::kIntersect_Op, mSoftClipping);
 }
 
 void
 DrawTargetSkia::PushClipRect(const Rect& aRect)
 {
   SkRect rect = RectToSkRect(aRect);
 
   mCanvas->save(SkCanvas::kClip_SaveFlag);
-  mCanvas->clipRect(rect, SkRegion::kIntersect_Op, USE_SOFT_CLIPPING);
+  mCanvas->clipRect(rect, SkRegion::kIntersect_Op, mSoftClipping);
 }
 
 void
 DrawTargetSkia::PopClip()
 {
   mCanvas->restore();
 }
 
--- a/gfx/2d/DrawTargetSkia.h
+++ b/gfx/2d/DrawTargetSkia.h
@@ -129,12 +129,13 @@ private:
   RefPtr<GenericRefCountedBase> mGLContext;
   SkRefPtr<GrGLInterface> mGrGLInterface;
   SkRefPtr<GrContext> mGrContext;
 #endif
 
   IntSize mSize;
   SkRefPtr<SkCanvas> mCanvas;
   std::vector<SourceSurfaceSkia*> mSnapshots;
+  bool mSoftClipping;
 };
 
 }
 }
--- a/gfx/gl/GLContextSkia.cpp
+++ b/gfx/gl/GLContextSkia.cpp
@@ -305,17 +305,17 @@ const GLubyte* glGetString_mozilla(GrGLe
             return reinterpret_cast<const GLubyte*>("OpenGL ES 2.0");
         } else {
             return reinterpret_cast<const GLubyte*>("2.0");
         }
     } else if (name == LOCAL_GL_EXTENSIONS) {
         // Only expose the bare minimum extensions we want to support to ensure a functional Ganesh
         // as GLContext only exposes certain extensions
         static bool extensionsStringBuilt = false;
-        static char extensionsString[120];
+        static char extensionsString[256];
 
         if (!extensionsStringBuilt) {
             if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_texture_format_BGRA8888)) {
                 strcpy(extensionsString, "GL_EXT_texture_format_BGRA8888 ");
             }
 
             if (sGLContext.get()->IsExtensionSupported(GLContext::OES_packed_depth_stencil)) {
                 strcat(extensionsString, "GL_OES_packed_depth_stencil ");
@@ -324,16 +324,24 @@ const GLubyte* glGetString_mozilla(GrGLe
             if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_packed_depth_stencil)) {
                 strcat(extensionsString, "GL_EXT_packed_depth_stencil ");
             }
 
             if (sGLContext.get()->IsExtensionSupported(GLContext::OES_rgb8_rgba8)) {
                 strcat(extensionsString, "GL_OES_rgb8_rgba8 ");
             }
 
+            if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_bgra)) {
+                strcat(extensionsString, "GL_EXT_bgra ");
+            }
+
+            if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_read_format_bgra)) {
+                strcat(extensionsString, "GL_EXT_read_format_bgra ");
+            }
+
             extensionsStringBuilt = true;
         }
 
         return reinterpret_cast<const GLubyte*>(extensionsString);
 
     } else if (name == LOCAL_GL_SHADING_LANGUAGE_VERSION) {
         if (sGLContext.get()->IsGLES2()) {
             return reinterpret_cast<const GLubyte*>("OpenGL ES GLSL ES 1.0");
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -53,16 +53,17 @@ GLScreenBuffer::Create(GLContext* gl,
 #endif
 
     if (!factory)
         factory = new SurfaceFactory_Basic(gl, caps);
 
     SurfaceStream* stream = SurfaceStream::CreateForType(
         SurfaceStream::ChooseGLStreamType(SurfaceStream::MainThread,
                                           caps.preserve),
+        gl,
         nullptr);
 
     return new GLScreenBuffer(gl, caps, factory, stream);
 }
 
 GLScreenBuffer::~GLScreenBuffer()
 {
     delete mFactory;
@@ -359,17 +360,17 @@ GLScreenBuffer::Morph(SurfaceFactory_GL*
     if (newFactory) {
         delete mFactory;
         mFactory = newFactory;
     }
 
     if (mStream->mType == streamType)
         return;
 
-    SurfaceStream* newStream = SurfaceStream::CreateForType(streamType, mStream);
+    SurfaceStream* newStream = SurfaceStream::CreateForType(streamType, mGL, mStream);
     MOZ_ASSERT(newStream);
 
     delete mStream;
     mStream = newStream;
 }
 
 void
 GLScreenBuffer::Attach(SharedSurface* surface, const gfxIntSize& size)
--- a/gfx/gl/SurfaceStream.cpp
+++ b/gfx/gl/SurfaceStream.cpp
@@ -26,30 +26,39 @@ SurfaceStream::ChooseGLStreamType(Surfac
         if (preserveBuffer)
             return SurfaceStreamType::SingleBuffer;
         else
             return SurfaceStreamType::TripleBuffer;
     }
 }
 
 SurfaceStream*
-SurfaceStream::CreateForType(SurfaceStreamType type, SurfaceStream* prevStream)
+SurfaceStream::CreateForType(SurfaceStreamType type, mozilla::gl::GLContext* glContext, SurfaceStream* prevStream)
 {
+    SurfaceStream* result = nullptr;
+
     switch (type) {
         case SurfaceStreamType::SingleBuffer:
-            return new SurfaceStream_SingleBuffer(prevStream);
+            result = new SurfaceStream_SingleBuffer(prevStream);
+            break;
         case SurfaceStreamType::TripleBuffer_Copy:
-            return new SurfaceStream_TripleBuffer_Copy(prevStream);
+            result = new SurfaceStream_TripleBuffer_Copy(prevStream);
+            break;
         case SurfaceStreamType::TripleBuffer_Async:
-            return new SurfaceStream_TripleBuffer_Async(prevStream);
+            result = new SurfaceStream_TripleBuffer_Async(prevStream);
+            break;
         case SurfaceStreamType::TripleBuffer:
-            return new SurfaceStream_TripleBuffer(prevStream);
+            result = new SurfaceStream_TripleBuffer(prevStream);
+            break;
         default:
             MOZ_CRASH("Invalid Type.");
     }
+
+    result->mGLContext = glContext;
+    return result;
 }
 
 void
 SurfaceStream::New(SurfaceFactory* factory, const gfxIntSize& size,
                    SharedSurface*& surf)
 {
     MOZ_ASSERT(!surf);
     surf = factory->NewSharedSurface(size);
--- a/gfx/gl/SurfaceStream.h
+++ b/gfx/gl/SurfaceStream.h
@@ -7,16 +7,17 @@
 #define SURFACESTREAM_H_
 
 #include <stack>
 #include <set>
 #include "mozilla/Monitor.h"
 #include "mozilla/Attributes.h"
 #include "gfxPoint.h"
 #include "SurfaceTypes.h"
+#include "GLContext.h"
 
 namespace mozilla {
 namespace gfx {
 class SharedSurface;
 class SurfaceFactory;
 
 // Owned by: ScreenBuffer
 class SurfaceStream
@@ -26,36 +27,43 @@ public:
         MainThread,
         OffMainThread
     } OMTC;
 
     static SurfaceStreamType ChooseGLStreamType(OMTC omtc,
                                                 bool preserveBuffer);
 
     static SurfaceStream* CreateForType(SurfaceStreamType type,
+                                        mozilla::gl::GLContext* glContext,
                                         SurfaceStream* prevStream = nullptr);
 
     SurfaceStreamHandle GetShareHandle() {
         return reinterpret_cast<SurfaceStreamHandle>(this);
     }
 
     static SurfaceStream* FromHandle(SurfaceStreamHandle handle) {
         return (SurfaceStream*)handle;
     }
 
     const SurfaceStreamType mType;
+
+    mozilla::gl::GLContext* GLContext() const { return mGLContext; }
 protected:
     // |mProd| is owned by us, but can be ripped away when
     // creating a new GLStream from this one.
     SharedSurface* mProducer;
     std::set<SharedSurface*> mSurfaces;
     std::stack<SharedSurface*> mScraps;
     mutable Monitor mMonitor;
     bool mIsAlive;
 
+    // Do not use this. It exists solely so we can ref it in CanvasClientWebGL::Update()
+    // before sent up to the compositor. You have been warned (Bug 894405)
+    mozilla::gl::GLContext* mGLContext;
+
     // |previous| can be null, indicating this is the first one.
     // Otherwise, we pull in |mProd| from |previous| an our initial surface.
     SurfaceStream(SurfaceStreamType type, SurfaceStream* prevStream)
         : mType(type)
         , mProducer(nullptr)
         , mMonitor("SurfaceStream monitor")
         , mIsAlive(true)
     {
--- a/gfx/layers/Compositor.cpp
+++ b/gfx/layers/Compositor.cpp
@@ -1,23 +1,27 @@
 /* -*- Mode: C++; tab-width: 20; 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/layers/Compositor.h"
 #include "mozilla/layers/Effects.h"
+#include "mozilla/layers/CompositorParent.h"
 
 namespace mozilla {
 namespace layers {
 
 /* static */ LayersBackend Compositor::sBackend = LAYERS_NONE;
 /* static */ LayersBackend
 Compositor::GetBackend()
 {
+  MOZ_ASSERT(CompositorParent::CompositorLoop() ==
+             MessageLoop::current(),
+             "Can only call this from the compositor thread!");
   return sBackend;
 }
 
 void
 Compositor::DrawDiagnostics(const gfx::Color& aColor,
                             const gfx::Rect& rect,
                             const gfx::Rect& aClipRect,
                             const gfx::Matrix4x4& aTransform,
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -386,16 +386,18 @@ public:
   virtual nsIWidget* GetWidget() const { return nullptr; }
   virtual const nsIntSize& GetWidgetSize() = 0;
 
   /**
    * We enforce that there can only be one Compositor backend type off the main
    * thread at the same time. The backend type in use can be checked with this
    * static method. We need this for creating texture clients/hosts etc. when we
    * don't have a reference to a Compositor.
+   *
+   * This can only be used from the compositor thread!
    */
   static LayersBackend GetBackend();
 
 protected:
   uint32_t mCompositorID;
   static LayersBackend sBackend;
   bool mDrawColoredBorders;
 };
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -120,15 +120,21 @@ CanvasClientWebGL::Update(gfx::IntSize a
     mDeprecatedTextureClient->SetDescriptor(grallocSurf->GetDescriptor());
 #else
     printf_stderr("isCrossProcess, but not MOZ_WIDGET_GONK! Someone needs to write some code!");
     MOZ_ASSERT(false);
 #endif
   } else {
     SurfaceStreamHandle handle = stream->GetShareHandle();
     mDeprecatedTextureClient->SetDescriptor(SurfaceStreamDescriptor(handle, false));
+
+    // Bug 894405
+    //
+    // Ref this so the SurfaceStream doesn't disappear unexpectedly. The
+    // Compositor will need to unref it when finished.
+    aLayer->mGLContext->AddRef();
   }
 
   aLayer->Painted();
 }
 
 }
 }
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -227,24 +227,34 @@ ClientLayerManager::EndEmptyTransaction(
   if (mWidget) {
     mWidget->PrepareWindowEffects();
   }
   ForwardTransaction();
   MakeSnapshotIfRequired();
   return true;
 }
 
+CompositorChild *
+ClientLayerManager::GetRemoteRenderer()
+{
+  if (!mWidget) {
+    return nullptr;
+  }
+
+  return mWidget->GetRemoteRenderer();
+}
+
 void 
 ClientLayerManager::MakeSnapshotIfRequired()
 {
   if (!mShadowTarget) {
     return;
   }
   if (mWidget) {
-    if (CompositorChild* remoteRenderer = mWidget->GetRemoteRenderer()) {
+    if (CompositorChild* remoteRenderer = GetRemoteRenderer()) {
       nsIntRect bounds;
       mWidget->GetBounds(bounds);
       SurfaceDescriptor inSnapshot, snapshot;
       if (AllocSurfaceDescriptor(bounds.Size(),
                                  gfxASurface::CONTENT_COLOR_ALPHA,
                                  &inSnapshot) &&
           // The compositor will usually reuse |snapshot| and return
           // it through |outSnapshot|, but if it doesn't, it's
@@ -378,17 +388,17 @@ ClientLayerManager::ClearLayer(Layer* aL
        child = child->GetNextSibling()) {
     ClearLayer(child);
   }
 }
 
 void
 ClientLayerManager::GetBackendName(nsAString& aName)
 {
-  switch (Compositor::GetBackend()) {
+  switch (GetCompositorBackendType()) {
     case LAYERS_BASIC: aName.AssignLiteral("Basic"); return;
     case LAYERS_OPENGL: aName.AssignLiteral("OpenGL"); return;
     case LAYERS_D3D9: aName.AssignLiteral("Direct3D 9"); return;
     case LAYERS_D3D10: aName.AssignLiteral("Direct3D 10"); return;
     case LAYERS_D3D11: aName.AssignLiteral("Direct3D 11"); return;
     default: NS_RUNTIMEABORT("Invalid backend");
   }
 }
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -48,16 +48,21 @@ public:
 
   virtual already_AddRefed<ThebesLayer> CreateThebesLayer();
   virtual already_AddRefed<ContainerLayer> CreateContainerLayer();
   virtual already_AddRefed<ImageLayer> CreateImageLayer();
   virtual already_AddRefed<CanvasLayer> CreateCanvasLayer();
   virtual already_AddRefed<ColorLayer> CreateColorLayer();
   virtual already_AddRefed<RefLayer> CreateRefLayer();
 
+  virtual TextureFactoryIdentifier GetTextureFactoryIdentifier() MOZ_OVERRIDE
+  {
+    return mTextureFactoryIdentifier;
+  }
+
   virtual void FlushRendering() MOZ_OVERRIDE;
 
   virtual bool NeedsWidgetInvalidation() MOZ_OVERRIDE { return false; }
 
   ShadowableLayer* Hold(Layer* aLayer);
 
   bool HasShadowManager() const { return ShadowLayerForwarder::HasShadowManager(); }
 
@@ -84,16 +89,18 @@ public:
   bool CompositorMightResample() { return mCompositorMightResample; } 
   
   DrawThebesLayerCallback GetThebesLayerCallback() const
   { return mThebesLayerCallback; }
 
   void* GetThebesLayerCallbackData() const
   { return mThebesLayerCallbackData; }
 
+  CompositorChild *GetRemoteRenderer();
+
   /**
    * Called for each iteration of a progressive tile update. Fills
    * aViewport, aScaleX and aScaleY with the current scale and viewport
    * being used to composite the layers in this manager, to determine what area
    * intersects with the target render rectangle. aDrawingCritical will be
    * true if the current drawing operation is using the critical displayport.
    * Returns true if the update should continue, or false if it should be
    * cancelled.
--- a/gfx/layers/ipc/CompositableForwarder.h
+++ b/gfx/layers/ipc/CompositableForwarder.h
@@ -35,20 +35,16 @@ class BasicTiledLayerBuffer;
 class CompositableForwarder : public ISurfaceAllocator
 {
   friend class AutoOpenSurface;
   friend class DeprecatedTextureClientShmem;
 public:
   typedef gfxASurface::gfxContentType gfxContentType;
 
   CompositableForwarder()
-  : mMaxTextureSize(0)
-  , mCompositorBackend(layers::LAYERS_NONE)
-  , mSupportsTextureBlitting(false)
-  , mSupportsPartialUploads(false)
   {}
 
   /**
    * Setup the IPDL actor for aCompositable to be part of layers
    * transactions.
    */
   virtual void Connect(CompositableClient* aCompositable) = 0;
 
@@ -145,43 +141,40 @@ public:
    */
   virtual void DestroyedThebesBuffer(const SurfaceDescriptor& aBackBufferToDestroy) = 0;
 
   void IdentifyTextureHost(const TextureFactoryIdentifier& aIdentifier);
 
   /**
    * Returns the maximum texture size supported by the compositor.
    */
-  virtual int32_t GetMaxTextureSize() const { return mMaxTextureSize; }
+  virtual int32_t GetMaxTextureSize() const { return mTextureFactoryIdentifier.mMaxTextureSize; }
 
   bool IsOnCompositorSide() const MOZ_OVERRIDE { return false; }
 
   /**
    * Returns the type of backend that is used off the main thread.
    * We only don't allow changing the backend type at runtime so this value can
    * be queried once and will not change until Gecko is restarted.
    */
   LayersBackend GetCompositorBackendType() const
   {
-    return mCompositorBackend;
+    return mTextureFactoryIdentifier.mParentBackend;
   }
 
   bool SupportsTextureBlitting() const
   {
-    return mSupportsTextureBlitting;
+    return mTextureFactoryIdentifier.mSupportsTextureBlitting;
   }
 
   bool SupportsPartialUploads() const
   {
-    return mSupportsPartialUploads;
+    return mTextureFactoryIdentifier.mSupportsPartialUploads;
   }
 
 protected:
-  uint32_t mMaxTextureSize;
-  LayersBackend mCompositorBackend;
-  bool mSupportsTextureBlitting;
-  bool mSupportsPartialUploads;
+  TextureFactoryIdentifier mTextureFactoryIdentifier;
 };
 
 } // namespace
 } // namespace
 
 #endif
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -31,17 +31,16 @@ using namespace mozilla::ipc;
 using namespace std;
 
 namespace mozilla {
 namespace layers {
 
 // FIXME/bug 774386: we're assuming that there's only one
 // CompositorParent, but that's not always true.  This assumption only
 // affects CrossProcessCompositorParent below.
-static CompositorParent* sCurrentCompositor;
 static Thread* sCompositorThread = nullptr;
 // manual reference count of the compositor thread.
 static int sCompositorThreadRefCount = 0;
 static MessageLoop* sMainLoop = nullptr;
 // When ContentParent::StartUp() is called, we use the Thread global.
 // When StartUpWithExistingThread() is used, we have to use the two
 // duplicated globals, because there's no API to make a Thread from an
 // existing thread.
@@ -148,35 +147,29 @@ CompositorParent::CompositorParent(nsIWi
   MOZ_COUNT_CTOR(CompositorParent);
   mCompositorID = 0;
   // FIXME: This holds on the the fact that right now the only thing that
   // can destroy this instance is initialized on the compositor thread after
   // this task has been processed.
   CompositorLoop()->PostTask(FROM_HERE, NewRunnableFunction(&AddCompositor,
                                                           this, &mCompositorID));
 
-  if (!sCurrentCompositor) {
-    sCurrentCompositor = this;
-  }
   ++sCompositorThreadRefCount;
 }
 
 PlatformThreadId
 CompositorParent::CompositorThreadID()
 {
   return sCompositorThread ? sCompositorThread->thread_id() : sCompositorThreadID;
 }
 
 CompositorParent::~CompositorParent()
 {
   MOZ_COUNT_DTOR(CompositorParent);
 
-  if (this == sCurrentCompositor) {
-    sCurrentCompositor = NULL;
-  }
   ReleaseCompositorThread();
 }
 
 void
 CompositorParent::Destroy()
 {
   NS_ABORT_IF_FALSE(ManagedPLayerTransactionParent().Length() == 0,
                     "CompositorParent destroyed before managed PLayerTransactionParent");
@@ -672,16 +665,29 @@ CompositorParent::SetTimeAndSampleAnimat
     it->second->mTestTime = aTime;
     it->second->mCompositionManager->TransformShadowTree(aTime);
   }
 }
 
 typedef map<uint64_t, CompositorParent::LayerTreeState> LayerTreeMap;
 static LayerTreeMap sIndirectLayerTrees;
 
+bool
+CompositorParent::RecvNotifyChildCreated(const uint64_t& child)
+{
+  NotifyChildCreated(child);
+  return true;
+}
+
+void
+CompositorParent::NotifyChildCreated(uint64_t aChild)
+{
+  sIndirectLayerTrees[aChild].mParent = this;
+}
+
 /*static*/ uint64_t
 CompositorParent::AllocateLayerTreeId()
 {
   MOZ_ASSERT(CompositorLoop());
   MOZ_ASSERT(NS_IsMainThread());
   static uint64_t ids;
   return ++ids;
 }
@@ -705,17 +711,17 @@ UpdateControllerForLayersId(uint64_t aLa
                             AsyncPanZoomController* aController)
 {
   // Adopt ref given to us by SetPanZoomControllerForLayerTree()
   sIndirectLayerTrees[aLayersId].mController =
     already_AddRefed<AsyncPanZoomController>(aController);
 
   // Notify the AsyncPanZoomController about the current compositor so that it
   // can request composites off the compositor thread.
-  aController->SetCompositorParent(sCurrentCompositor);
+  aController->SetCompositorParent(sIndirectLayerTrees[aLayersId].mParent);
 }
 
 /*static*/ void
 CompositorParent::SetPanZoomControllerForLayerTree(uint64_t aLayersId,
                                                    AsyncPanZoomController* aController)
 {
   // This ref is adopted by UpdateControllerForLayersId().
   aController->AddRef();
@@ -747,16 +753,17 @@ public:
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   // FIXME/bug 774388: work out what shutdown protocol we need.
   virtual bool RecvWillStop() MOZ_OVERRIDE { return true; }
   virtual bool RecvStop() MOZ_OVERRIDE { return true; }
   virtual bool RecvPause() MOZ_OVERRIDE { return true; }
   virtual bool RecvResume() MOZ_OVERRIDE { return true; }
+  virtual bool RecvNotifyChildCreated(const uint64_t& child) MOZ_OVERRIDE;
   virtual bool RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
                                 SurfaceDescriptor* aOutSnapshot)
   { return true; }
   virtual bool RecvFlushRendering() MOZ_OVERRIDE { return true; }
 
   virtual PLayerTransactionParent*
     AllocPLayerTransactionParent(const LayersBackend& aBackendType,
                                  const uint64_t& aId,
@@ -846,45 +853,57 @@ CrossProcessCompositorParent::ActorDestr
 
 PLayerTransactionParent*
 CrossProcessCompositorParent::AllocPLayerTransactionParent(const LayersBackend& aBackendType,
                                                            const uint64_t& aId,
                                                            TextureFactoryIdentifier* aTextureFactoryIdentifier)
 {
   MOZ_ASSERT(aId != 0);
 
-  nsRefPtr<LayerManager> lm = sCurrentCompositor->GetLayerManager();
-  *aTextureFactoryIdentifier = lm->GetTextureFactoryIdentifier();
-  return new LayerTransactionParent(lm->AsLayerManagerComposite(), this, aId);
+  if (sIndirectLayerTrees[aId].mParent) {
+    nsRefPtr<LayerManager> lm = sIndirectLayerTrees[aId].mParent->GetLayerManager();
+    *aTextureFactoryIdentifier = lm->GetTextureFactoryIdentifier();
+    return new LayerTransactionParent(lm->AsLayerManagerComposite(), this, aId);
+  }
+
+  NS_WARNING("Created child without a matching parent?");
+  return new LayerTransactionParent(nullptr, this, aId);
 }
 
 bool
 CrossProcessCompositorParent::DeallocPLayerTransactionParent(PLayerTransactionParent* aLayers)
 {
   LayerTransactionParent* slp = static_cast<LayerTransactionParent*>(aLayers);
   RemoveIndirectTree(slp->GetId());
   delete aLayers;
   return true;
 }
 
+bool
+CrossProcessCompositorParent::RecvNotifyChildCreated(const uint64_t& child)
+{
+  sIndirectLayerTrees[child].mParent->NotifyChildCreated(child);
+  return true;
+}
+
 void
 CrossProcessCompositorParent::ShadowLayersUpdated(
   LayerTransactionParent* aLayerTree,
   const TargetConfig& aTargetConfig,
   bool isFirstPaint)
 {
   uint64_t id = aLayerTree->GetId();
   MOZ_ASSERT(id != 0);
   Layer* shadowRoot = aLayerTree->GetRoot();
   if (shadowRoot) {
     SetShadowProperties(shadowRoot);
   }
   UpdateIndirectTree(id, shadowRoot, aTargetConfig, isFirstPaint);
 
-  sCurrentCompositor->NotifyShadowTreeTransaction();
+  sIndirectLayerTrees[id].mParent->NotifyShadowTreeTransaction();
 }
 
 void
 CrossProcessCompositorParent::DeferredDestroy()
 {
   mSelfRef = nullptr;
   // |this| was just destroyed, hands off
 }
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -48,16 +48,17 @@ public:
                    int aSurfaceWidth = -1, int aSurfaceHeight = -1);
 
   virtual ~CompositorParent();
 
   virtual bool RecvWillStop() MOZ_OVERRIDE;
   virtual bool RecvStop() MOZ_OVERRIDE;
   virtual bool RecvPause() MOZ_OVERRIDE;
   virtual bool RecvResume() MOZ_OVERRIDE;
+  virtual bool RecvNotifyChildCreated(const uint64_t& child) MOZ_OVERRIDE;
   virtual bool RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
                                 SurfaceDescriptor* aOutSnapshot);
   virtual bool RecvFlushRendering() MOZ_OVERRIDE;
 
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   virtual void ShadowLayersUpdated(LayerTransactionParent* aLayerTree,
                                    const TargetConfig& aTargetConfig,
@@ -69,16 +70,18 @@ public:
    * The information refresh happens because the compositor will call
    * SetFirstPaintViewport on the next frame of composition.
    */
   void ForceIsFirstPaint();
   void Destroy();
 
   LayerManagerComposite* GetLayerManager() { return mLayerManager; }
 
+  void NotifyChildCreated(uint64_t aChild);
+
   void AsyncRender();
 
   // Can be called from any thread
   void ScheduleRenderOnCompositorThread();
   void SchedulePauseOnCompositorThread();
   /**
    * Returns true if a surface was obtained and the resume succeeded; false
    * otherwise.
@@ -147,16 +150,17 @@ public:
    * extra synchronization and call ::Composite() right from toolkit::Paint event
    */
   static void StartUpWithExistingThread(MessageLoop* aMsgLoop,
                                         PlatformThreadId aThreadID);
 
   struct LayerTreeState {
     nsRefPtr<Layer> mRoot;
     nsRefPtr<AsyncPanZoomController> mController;
+    CompositorParent *mParent;
     TargetConfig mTargetConfig;
   };
 
   /**
    * Lookup the indirect shadow tree for |aId| and return it if it
    * exists.  Otherwise null is returned.  This must only be called on
    * the compositor thread.
    */
--- a/gfx/layers/ipc/PCompositor.ipdl
+++ b/gfx/layers/ipc/PCompositor.ipdl
@@ -38,16 +38,18 @@ parent:
   // Clean up in preparation for own destruction.
   sync Stop();
 
   // Pause/resume the compositor. These are intended to be used on mobile, when
   // the compositor needs to pause/resume in lockstep with the application.
   sync Pause();
   sync Resume();
 
+  async NotifyChildCreated(uint64_t id);
+
   // Make a snapshot of the content that would have been drawn to our
   // render target at the time this message is received.  If the size
   // or format of |inSnapshot| doesn't match our render target,
   // results are undefined.
   //
   // NB: this message will result in animations, transforms, effects,
   // and so forth being interpolated.  That's what we want to happen.
   sync MakeSnapshot(SurfaceDescriptor inSnapshot)
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -155,20 +155,17 @@ struct AutoTxnEnd {
   AutoTxnEnd(Transaction* aTxn) : mTxn(aTxn) {}
   ~AutoTxnEnd() { mTxn->End(); }
   Transaction* mTxn;
 };
 
 void
 CompositableForwarder::IdentifyTextureHost(const TextureFactoryIdentifier& aIdentifier)
 {
-  mMaxTextureSize = aIdentifier.mMaxTextureSize;
-  mCompositorBackend = aIdentifier.mParentBackend;
-  mSupportsTextureBlitting = aIdentifier.mSupportsTextureBlitting;
-  mSupportsPartialUploads = aIdentifier.mSupportsPartialUploads;
+  mTextureFactoryIdentifier = aIdentifier;
 }
 
 ShadowLayerForwarder::ShadowLayerForwarder()
  : mShadowManager(NULL)
  , mIsFirstPaint(false)
  , mDrawColoredBorders(false)
  , mWindowOverlayChanged(false)
 {
--- a/gfx/layers/opengl/CanvasLayerOGL.cpp
+++ b/gfx/layers/opengl/CanvasLayerOGL.cpp
@@ -325,16 +325,17 @@ CanvasLayerOGL::RenderLayer(int aPreviou
   program->Activate();
   if (mLayerProgram == RGBARectLayerProgramType ||
       mLayerProgram == RGBXRectLayerProgramType) {
     // This is used by IOSurface that use 0,0...w,h coordinate rather then 0,0..1,1.
     program->SetTexCoordMultiplier(mBounds.width, mBounds.height);
   }
   program->SetLayerQuadRect(drawRect);
   program->SetLayerTransform(GetEffectiveTransform());
+  program->SetTextureTransform(gfx3DMatrix());
   program->SetLayerOpacity(GetEffectiveOpacity());
   program->SetRenderOffset(aOffset);
   program->SetTextureUnit(0);
   program->LoadMask(GetMaskLayer());
 
   if (gl()->CanUploadNonPowerOfTwo()) {
     mOGLManager->BindAndDrawQuad(program, mNeedsYFlip ? true : false);
   } else {
--- a/gfx/layers/opengl/ColorLayerOGL.cpp
+++ b/gfx/layers/opengl/ColorLayerOGL.cpp
@@ -32,16 +32,17 @@ RenderColorLayer(ColorLayer* aLayer, Lay
   color.b *= opacity;
   color.a = opacity;
 
   ShaderProgramOGL *program = aManager->GetProgram(ColorLayerProgramType,
                                                    aLayer->GetMaskLayer());
   program->Activate();
   program->SetLayerQuadRect(aLayer->GetBounds());
   program->SetLayerTransform(aLayer->GetEffectiveTransform());
+  program->SetTextureTransform(gfx3DMatrix());
   program->SetRenderOffset(aOffset);
   program->SetRenderColor(color);
   program->LoadMask(aLayer->GetMaskLayer());
 
   aManager->BindAndDrawQuad(program);
 }
 
 void
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -1033,16 +1033,17 @@ CompositorOGL::DrawQuad(const Rect& aRec
     TexturedEffect* texturedEffect =
         static_cast<TexturedEffect*>(aEffectChain.mPrimaryEffect.get());
     TextureSourceOGL* source = texturedEffect->mTexture->AsSourceOGL();
     // This is used by IOSurface that use 0,0...w,h coordinate rather then 0,0..1,1.
     program->SetTexCoordMultiplier(source->GetSize().width, source->GetSize().height);
   }
   program->SetLayerQuadRect(aRect);
   program->SetLayerTransform(aTransform);
+  program->SetTextureTransform(gfx3DMatrix());
   program->SetRenderOffset(aOffset.x, aOffset.y);
 
   switch (aEffectChain.mPrimaryEffect->mType) {
     case EFFECT_SOLID_COLOR: {
       EffectSolidColor* effectSolidColor =
         static_cast<EffectSolidColor*>(aEffectChain.mPrimaryEffect.get());
 
       Color color = effectSolidColor->mColor;
@@ -1080,19 +1081,18 @@ CompositorOGL::DrawQuad(const Rect& aRec
       TextureSource *source = texturedEffect->mTexture;
 
       if (!texturedEffect->mPremultiplied) {
         mGLContext->fBlendFuncSeparate(LOCAL_GL_SRC_ALPHA, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
                                        LOCAL_GL_ONE, LOCAL_GL_ONE);
       }
 
       AutoBindTexture bindSource(source->AsSourceOGL(), LOCAL_GL_TEXTURE0);
-      if (programType == RGBALayerExternalProgramType) {
-        program->SetTextureTransform(source->AsSourceOGL()->GetTextureTransform());
-      }
+  
+      program->SetTextureTransform(source->AsSourceOGL()->GetTextureTransform());
 
       mGLContext->ApplyFilterToBoundTexture(source->AsSourceOGL()->GetTextureTarget(),
                                             ThebesFilter(texturedEffect->mFilter));
 
       program->SetTextureUnit(0);
       program->SetLayerOpacity(aOpacity);
 
       AutoBindTexture bindMask;
@@ -1211,16 +1211,17 @@ CompositorOGL::DrawQuad(const Rect& aRec
         AutoBindTexture bindSourceOnBlack(sourceOnBlack, LOCAL_GL_TEXTURE0);
         AutoBindTexture bindSourceOnWhite(sourceOnWhite, LOCAL_GL_TEXTURE1);
 
         program->Activate();
         program->SetBlackTextureUnit(0);
         program->SetWhiteTextureUnit(1);
         program->SetLayerOpacity(aOpacity);
         program->SetLayerTransform(aTransform);
+        program->SetTextureTransform(gfx3DMatrix());
         program->SetRenderOffset(aOffset.x, aOffset.y);
         program->SetLayerQuadRect(aRect);
         AutoBindTexture bindMask;
         if (maskType != MaskNone) {
           bindMask.Bind(sourceMask, LOCAL_GL_TEXTURE2);
           program->SetMaskTextureUnit(2);
           program->SetMaskLayerTransform(maskQuadTransform);
         }
--- a/gfx/layers/opengl/ContainerLayerOGL.cpp
+++ b/gfx/layers/opengl/ContainerLayerOGL.cpp
@@ -317,16 +317,17 @@ ContainerRender(Container* aContainer,
         }
       }
       ShaderProgramOGL *rgb =
         aManager->GetFBOLayerProgram(maskType);
 
       rgb->Activate();
       rgb->SetLayerQuadRect(visibleRect);
       rgb->SetLayerTransform(transform);
+      rgb->SetTextureTransform(gfx3DMatrix());
       rgb->SetLayerOpacity(opacity);
       rgb->SetRenderOffset(aOffset);
       rgb->SetTextureUnit(0);
       rgb->LoadMask(aContainer->GetMaskLayer());
 
       if (rgb->GetTexCoordMultiplierUniformLocation() != -1) {
         // 2DRect case, get the multiplier right for a sampler2DRect
         rgb->SetTexCoordMultiplier(visibleRect.width, visibleRect.height);
--- a/gfx/layers/opengl/ImageLayerOGL.cpp
+++ b/gfx/layers/opengl/ImageLayerOGL.cpp
@@ -234,16 +234,17 @@ ImageLayerOGL::RenderLayer(int,
     ShaderProgramOGL *program = mOGLManager->GetProgram(YCbCrLayerProgramType,
                                                         GetMaskLayer());
 
     program->Activate();
     program->SetLayerQuadRect(nsIntRect(0, 0,
                                         yuvImage->GetSize().width,
                                         yuvImage->GetSize().height));
     program->SetLayerTransform(GetEffectiveTransform());
+    program->SetTextureTransform(gfx3DMatrix());
     program->SetLayerOpacity(GetEffectiveOpacity());
     program->SetRenderOffset(aOffset);
     program->SetYCbCrTextureUnits(0, 1, 2);
     program->LoadMask(GetMaskLayer());
 
     mOGLManager->BindAndDrawQuadWithTextureRect(program,
                                                 yuvImage->GetData()->GetPictureRect(),
                                                 nsIntSize(yuvImage->GetData()->mYSize.width,
@@ -292,16 +293,17 @@ ImageLayerOGL::RenderLayer(int,
 
     gl()->ApplyFilterToBoundTexture(mFilter);
 
     program->Activate();
     program->SetLayerQuadRect(nsIntRect(0, 0, 
                                         cairoImage->GetSize().width, 
                                         cairoImage->GetSize().height));
     program->SetLayerTransform(GetEffectiveTransform());
+    program->SetTextureTransform(gfx3DMatrix());
     program->SetLayerOpacity(GetEffectiveOpacity());
     program->SetRenderOffset(aOffset);
     program->SetTextureUnit(0);
     program->LoadMask(GetMaskLayer());
 
     mOGLManager->BindAndDrawQuad(program);
   } else if (image->GetFormat() == SHARED_TEXTURE) {
     SharedTextureImage* texImage =
@@ -319,16 +321,17 @@ ImageLayerOGL::RenderLayer(int,
     ShaderProgramOGL* program = mOGLManager->GetProgram(programType, GetMaskLayer());
 
     program->Activate();
     if (programType == RGBARectLayerProgramType) {
       // 2DRect case, get the multiplier right for a sampler2DRect
       program->SetTexCoordMultiplier(data->mSize.width, data->mSize.height);
     }
     program->SetLayerTransform(GetEffectiveTransform());
+    program->SetTextureTransform(gfx3DMatrix());
     program->SetLayerOpacity(GetEffectiveOpacity());
     program->SetRenderOffset(aOffset);
     program->SetTextureUnit(0);
     program->SetTextureTransform(handleDetails.mTextureTransform);
     program->LoadMask(GetMaskLayer());
 
     if (!texImage->GetBackendData(LAYERS_OPENGL)) {
       AllocateTextureSharedTexture(texImage, gl(), handleDetails.mTarget);
--- a/gfx/layers/opengl/LayerManagerOGLProgram.cpp
+++ b/gfx/layers/opengl/LayerManagerOGLProgram.cpp
@@ -17,16 +17,17 @@ namespace layers {
 typedef ProgramProfileOGL::Argument Argument;
 
 // helper methods for GetProfileFor
 void
 AddCommonArgs(ProgramProfileOGL& aProfile)
 {
   aProfile.mUniforms.AppendElement(Argument("uLayerTransform"));
   aProfile.mUniforms.AppendElement(Argument("uLayerQuadTransform"));
+  aProfile.mUniforms.AppendElement(Argument("uTextureTransform"));
   aProfile.mUniforms.AppendElement(Argument("uMatrixProj"));
   aProfile.mHasMatrixProj = true;
   aProfile.mUniforms.AppendElement(Argument("uRenderTargetOffset"));
   aProfile.mAttributes.AppendElement(Argument("aVertexCoord"));
 }
 void
 AddCommonTextureArgs(ProgramProfileOGL& aProfile)
 {
@@ -53,33 +54,16 @@ ProgramProfileOGL::GetProfileFor(ShaderP
     } else {
       result.mVertexShaderString = sLayerVS;
       result.mFragmentShaderString = sRGBATextureLayerFS;
     }
     AddCommonArgs(result);
     AddCommonTextureArgs(result);
     result.mTextureCount = 1;
     break;
-  case RGBALayerExternalProgramType:
-    if (aMask == Mask3d) {
-      result.mVertexShaderString = sLayerMask3DVS;
-      result.mFragmentShaderString = sRGBATextureLayerExternalMask3DFS;
-    } else if (aMask == Mask2d) {
-      result.mVertexShaderString = sLayerMaskVS;
-      result.mFragmentShaderString = sRGBATextureLayerExternalMaskFS;
-    } else {
-      result.mVertexShaderString = sLayerVS;
-      result.mFragmentShaderString = sRGBATextureLayerExternalFS;
-    }
-    AddCommonArgs(result);
-    AddCommonTextureArgs(result);
-    result.mUniforms.AppendElement(Argument("uTextureTransform"));
-    result.mHasTextureTransform = true;
-    result.mTextureCount = 1;
-    break;
   case BGRALayerProgramType:
     if (aMask == Mask2d) {
       result.mVertexShaderString = sLayerMaskVS;
       result.mFragmentShaderString = sBGRATextureLayerMaskFS;
     } else {
       result.mVertexShaderString = sLayerVS;
       result.mFragmentShaderString = sBGRATextureLayerFS;
     }
--- a/gfx/layers/opengl/LayerManagerOGLProgram.h
+++ b/gfx/layers/opengl/LayerManagerOGLProgram.h
@@ -27,17 +27,16 @@ namespace gl {
 class GLContext;
 }
 namespace layers {
 
 class Layer;
 
 enum ShaderProgramType {
   RGBALayerProgramType,
-  RGBALayerExternalProgramType,
   BGRALayerProgramType,
   RGBXLayerProgramType,
   BGRXLayerProgramType,
   RGBARectLayerProgramType,
   RGBXRectLayerProgramType,
   BGRARectLayerProgramType,
   RGBAExternalLayerProgramType,
   ColorLayerProgramType,
@@ -75,17 +74,17 @@ ShaderProgramFromSurfaceFormat(gfx::Surf
 
 static inline ShaderProgramType
 ShaderProgramFromTargetAndFormat(GLenum aTarget,
                                  gfx::SurfaceFormat aFormat)
 {
   switch(aTarget) {
     case LOCAL_GL_TEXTURE_EXTERNAL:
       MOZ_ASSERT(aFormat == gfx::FORMAT_R8G8B8A8);
-      return RGBALayerExternalProgramType;
+      return RGBAExternalLayerProgramType;
     case LOCAL_GL_TEXTURE_RECTANGLE_ARB:
       MOZ_ASSERT(aFormat == gfx::FORMAT_R8G8B8A8 ||
                  aFormat == gfx::FORMAT_R8G8B8X8);
       if (aFormat == gfx::FORMAT_R8G8B8A8)
         return RGBARectLayerProgramType;
       else
         return RGBXRectLayerProgramType;
     default:
@@ -184,22 +183,20 @@ struct ProgramProfileOGL
   // the source code for the program's shaders
   const char *mVertexShaderString;
   const char *mFragmentShaderString;
 
   nsTArray<Argument> mUniforms;
   nsTArray<Argument> mAttributes;
   uint32_t mTextureCount;
   bool mHasMatrixProj;
-  bool mHasTextureTransform;
 private:
   ProgramProfileOGL() :
     mTextureCount(0),
-    mHasMatrixProj(false),
-    mHasTextureTransform(false) {}
+    mHasMatrixProj(false) {}
 };
 
 
 #if defined(DEBUG)
 #define CHECK_CURRENT_PROGRAM 1
 #define ASSERT_THIS_PROGRAM                                             \
   do {                                                                  \
     NS_ASSERTION(mGL->GetUserData(&sCurrentProgramKey) == this, \
@@ -310,23 +307,21 @@ public:
 
   void SetProjectionMatrix(const gfx3DMatrix& aMatrix) {
     SetMatrixUniform(mProfile.LookupUniformLocation("uMatrixProj"), aMatrix);
     mIsProjectionMatrixStale = false;
   }
 
   // sets this program's texture transform, if it uses one
   void SetTextureTransform(const gfx3DMatrix& aMatrix) {
-    if (mProfile.mHasTextureTransform)
-      SetMatrixUniform(mProfile.LookupUniformLocation("uTextureTransform"), aMatrix);
+    SetMatrixUniform(mProfile.LookupUniformLocation("uTextureTransform"), aMatrix);
   }
 
   void SetTextureTransform(const gfx::Matrix4x4& aMatrix) {
-    if (mProfile.mHasTextureTransform)
-      SetMatrixUniform(mProfile.LookupUniformLocation("uTextureTransform"), aMatrix);
+    SetMatrixUniform(mProfile.LookupUniformLocation("uTextureTransform"), aMatrix);
   }
 
   void SetRenderOffset(const nsIntPoint& aOffset) {
     float vals[4] = { float(aOffset.x), float(aOffset.y), 0.0f, 0.0f };
     SetUniform(mProfile.LookupUniformLocation("uRenderTargetOffset"), 4, vals);
   }
 
   void SetRenderOffset(float aX, float aY) {
--- a/gfx/layers/opengl/LayerManagerOGLShaders.h
+++ b/gfx/layers/opengl/LayerManagerOGLShaders.h
@@ -1,16 +1,17 @@
 /* AUTOMATICALLY GENERATED from LayerManagerOGLShaders.txt */
 /* DO NOT EDIT! */
 
 static const char sLayerVS[] = "/* sLayerVS */\n\
 /* Vertex Shader */\n\
 uniform mat4 uMatrixProj;\n\
 uniform mat4 uLayerQuadTransform;\n\
 uniform mat4 uLayerTransform;\n\
+uniform mat4 uTextureTransform;\n\
 uniform vec4 uRenderTargetOffset;\n\
 attribute vec4 aVertexCoord;\n\
 attribute vec2 aTexCoord;\n\
 #ifdef GL_ES // for tiling, texcoord can be greater than the lowfp range\n\
 varying mediump vec2 vTexCoord;\n\
 #else\n\
 varying vec2 vTexCoord;\n\
 #endif\n\
@@ -21,26 +22,27 @@ vec4 finalPosition = aVertexCoord;\n\
 finalPosition = uLayerQuadTransform * finalPosition;\n\
 finalPosition = uLayerTransform * finalPosition;\n\
 finalPosition.xyz /= finalPosition.w;\n\
 \n\
 \n\
 finalPosition = finalPosition - uRenderTargetOffset;\n\
 finalPosition.xyz *= finalPosition.w;\n\
 finalPosition = uMatrixProj * finalPosition;\n\
-vTexCoord = aTexCoord;\n\
+vTexCoord = (uTextureTransform * vec4(aTexCoord.x, aTexCoord.y, 0.0, 1.0)).xy;\n\
 gl_Position = finalPosition;\n\
 }\n\
 ";
 
 static const char sLayerMaskVS[] = "/* sLayerMaskVS */\n\
 /* Vertex Shader */\n\
 uniform mat4 uMatrixProj;\n\
 uniform mat4 uLayerQuadTransform;\n\
 uniform mat4 uLayerTransform;\n\
+uniform mat4 uTextureTransform;\n\
 uniform vec4 uRenderTargetOffset;\n\
 attribute vec4 aVertexCoord;\n\
 attribute vec2 aTexCoord;\n\
 #ifdef GL_ES // for tiling, texcoord can be greater than the lowfp range\n\
 varying mediump vec2 vTexCoord;\n\
 #else\n\
 varying vec2 vTexCoord;\n\
 #endif\n\
@@ -54,26 +56,27 @@ vec4 finalPosition = aVertexCoord;\n\
 finalPosition = uLayerQuadTransform * finalPosition;\n\
 finalPosition = uLayerTransform * finalPosition;\n\
 finalPosition.xyz /= finalPosition.w;\n\
 vMaskCoord = (uMaskQuadTransform * finalPosition).xy;\n\
 \n\
 finalPosition = finalPosition - uRenderTargetOffset;\n\
 finalPosition.xyz *= finalPosition.w;\n\
 finalPosition = uMatrixProj * finalPosition;\n\
-vTexCoord = aTexCoord;\n\
+vTexCoord = (uTextureTransform * vec4(aTexCoord.x, aTexCoord.y, 0.0, 1.0)).xy;\n\
 gl_Position = finalPosition;\n\
 }\n\
 ";
 
 static const char sLayerMask3DVS[] = "/* sLayerMask3DVS */\n\
 /* Vertex Shader */\n\
 uniform mat4 uMatrixProj;\n\
 uniform mat4 uLayerQuadTransform;\n\
 uniform mat4 uLayerTransform;\n\
+uniform mat4 uTextureTransform;\n\
 uniform vec4 uRenderTargetOffset;\n\
 attribute vec4 aVertexCoord;\n\
 attribute vec2 aTexCoord;\n\
 #ifdef GL_ES // for tiling, texcoord can be greater than the lowfp range\n\
 varying mediump vec2 vTexCoord;\n\
 #else\n\
 varying vec2 vTexCoord;\n\
 #endif\n\
@@ -90,17 +93,17 @@ finalPosition.xyz /= finalPosition.w;\n\
 vMaskCoord.xy = (uMaskQuadTransform * vec4(finalPosition.xyz, 1.0)).xy;\n\
 // correct for perspective correct interpolation, see comment in D3D10 shader\n\
 vMaskCoord.z = 1.0;\n\
 vMaskCoord *= finalPosition.w;\n\
 \n\
 finalPosition = finalPosition - uRenderTargetOffset;\n\
 finalPosition.xyz *= finalPosition.w;\n\
 finalPosition = uMatrixProj * finalPosition;\n\
-vTexCoord = aTexCoord;\n\
+vTexCoord = (uTextureTransform * vec4(aTexCoord.x, aTexCoord.y, 0.0, 1.0)).xy;\n\
 gl_Position = finalPosition;\n\
 }\n\
 ";
 
 static const char sSolidColorLayerFS[] = "/* sSolidColorLayerFS */\n\
 #define NO_LAYER_OPACITY 1\n\
 /* Fragment Shader */\n\
 #ifdef GL_ES\n\
@@ -247,116 +250,16 @@ void main()\n\
 {\n\
 vec2 maskCoords = vMaskCoord.xy / vMaskCoord.z;\n\
 float mask = texture2D(uMaskTexture, maskCoords).r;\n\
 \n\
 gl_FragColor = texture2D(uTexture, vTexCoord) * uLayerOpacity * mask;\n\
 }\n\
 ";
 
-static const char sRGBATextureLayerExternalFS[] = "/* sRGBATextureLayerExternalFS */\n\
-#extension GL_OES_EGL_image_external : require\n\
-#define MEDIUMP_SHADER 1\n\
-/* Fragment Shader */\n\
-#ifdef GL_ES\n\
-#ifdef MEDIUMP_SHADER\n\
-precision mediump float;\n\
-#else\n\
-precision lowp float;\n\
-#endif\n\
-#endif\n\
-\n\
-#ifndef NO_LAYER_OPACITY\n\
-uniform float uLayerOpacity;\n\
-#endif\n\
-#ifdef GL_ES // for tiling, texcoord can be greater than the lowfp range\n\
-varying mediump vec2 vTexCoord;\n\
-#else\n\
-varying vec2 vTexCoord;\n\
-#endif\n\
-\n\
-uniform samplerExternalOES uTexture;\n\
-uniform mat4 uTextureTransform;\n\
-void main()\n\
-{\n\
-float mask = 1.0;\n\
-\n\
-gl_FragColor = texture2D(uTexture, (uTextureTransform * vec4(vTexCoord.x, vTexCoord.y, 0.0, 1.0)).xy) * uLayerOpacity * mask;\n\
-}\n\
-";
-
-static const char sRGBATextureLayerExternalMaskFS[] = "/* sRGBATextureLayerExternalMaskFS */\n\
-#extension GL_OES_EGL_image_external : require\n\
-#define MEDIUMP_SHADER 1\n\
-/* Fragment Shader */\n\
-#ifdef GL_ES\n\
-#ifdef MEDIUMP_SHADER\n\
-precision mediump float;\n\
-#else\n\
-precision lowp float;\n\
-#endif\n\
-#endif\n\
-\n\
-#ifndef NO_LAYER_OPACITY\n\
-uniform float uLayerOpacity;\n\
-#endif\n\
-#ifdef GL_ES // for tiling, texcoord can be greater than the lowfp range\n\
-varying mediump vec2 vTexCoord;\n\
-#else\n\
-varying vec2 vTexCoord;\n\
-#endif\n\
-\n\
-varying vec2 vMaskCoord;\n\
-uniform sampler2D uMaskTexture;\n\
-\n\
-uniform samplerExternalOES uTexture;\n\
-uniform mat4 uTextureTransform;\n\
-void main()\n\
-{\n\
-float mask = texture2D(uMaskTexture, vMaskCoord).r;\n\
-\n\
-gl_FragColor = texture2D(uTexture, (uTextureTransform * vec4(vTexCoord.x, vTexCoord.y, 0.0, 1.0)).xy) * uLayerOpacity * mask;\n\
-}\n\
-";
-
-static const char sRGBATextureLayerExternalMask3DFS[] = "/* sRGBATextureLayerExternalMask3DFS */\n\
-#extension GL_OES_EGL_image_external : require\n\
-#define MEDIUMP_SHADER 1\n\
-/* Fragment Shader */\n\
-#ifdef GL_ES\n\
-#ifdef MEDIUMP_SHADER\n\
-precision mediump float;\n\
-#else\n\
-precision lowp float;\n\
-#endif\n\
-#endif\n\
-\n\
-#ifndef NO_LAYER_OPACITY\n\
-uniform float uLayerOpacity;\n\
-#endif\n\
-#ifdef GL_ES // for tiling, texcoord can be greater than the lowfp range\n\
-varying mediump vec2 vTexCoord;\n\
-#else\n\
-varying vec2 vTexCoord;\n\
-#endif\n\
-\n\
-varying vec3 vMaskCoord;\n\
-uniform sampler2D uMaskTexture;\n\
-\n\
-uniform samplerExternalOES uTexture;\n\
-uniform mat4 uTextureTransform;\n\
-void main()\n\
-{\n\
-vec2 maskCoords = vMaskCoord.xy / vMaskCoord.z;\n\
-float mask = texture2D(uMaskTexture, maskCoords).r;\n\
-\n\
-gl_FragColor = texture2D(uTexture, (uTextureTransform * vec4(vTexCoord.x, vTexCoord.y, 0.0, 1.0)).xy) * uLayerOpacity * mask;\n\
-}\n\
-";
-
 static const char sRGBARectTextureLayerFS[] = "/* sRGBARectTextureLayerFS */\n\
 #extension GL_ARB_texture_rectangle : enable\n\
 /* Fragment Shader */\n\
 #ifdef GL_ES\n\
 #ifdef MEDIUMP_SHADER\n\
 precision mediump float;\n\
 #else\n\
 precision lowp float;\n\
--- a/gfx/layers/opengl/LayerManagerOGLShaders.txt
+++ b/gfx/layers/opengl/LayerManagerOGLShaders.txt
@@ -57,16 +57,17 @@
 // 
 
 @define VERTEX_SHADER_HEADER<>
 /* Vertex Shader */
 
 uniform mat4 uMatrixProj;
 uniform mat4 uLayerQuadTransform;
 uniform mat4 uLayerTransform;
+uniform mat4 uTextureTransform;
 uniform vec4 uRenderTargetOffset;
 
 attribute vec4 aVertexCoord;
 attribute vec2 aTexCoord;
 
 #ifdef GL_ES // for tiling, texcoord can be greater than the lowfp range
 varying mediump vec2 vTexCoord;
 #else
@@ -118,17 +119,17 @@ void main()
   finalPosition.xyz /= finalPosition.w;
 
 $VERTEX_MASK_STUFF<mask>$
 
   finalPosition = finalPosition - uRenderTargetOffset;
   finalPosition.xyz *= finalPosition.w;
   finalPosition = uMatrixProj * finalPosition;
 
-  vTexCoord = aTexCoord;
+  vTexCoord = (uTextureTransform * vec4(aTexCoord.x, aTexCoord.y, 0.0, 1.0)).xy;
   gl_Position = finalPosition;
 }
 @end
 
 
 /*
  * Fragment shaders
  */
@@ -210,34 +211,16 @@ uniform sampler2D uTexture;
 
 void main()
 {
 $FRAGMENT_CALC_MASK<mask>$
   gl_FragColor = texture2D(uTexture, vTexCoord) * uLayerOpacity * mask;
 }
 @end
 
-// Single texture in RGBA format for use with GL_TEXTURE_EXTERNAL_OES
-@shader sRGBATextureLayerExternal<mask:,Mask,Mask3D>FS
-#extension GL_OES_EGL_image_external : require
-
-#define MEDIUMP_SHADER 1
-$LAYER_FRAGMENT<mask>$
-
-uniform samplerExternalOES uTexture;
-uniform mat4 uTextureTransform;
-
-void main()
-{
-$FRAGMENT_CALC_MASK<mask>$
-  gl_FragColor = texture2D(uTexture, (uTextureTransform * vec4(vTexCoord.x, vTexCoord.y, 0.0, 1.0)).xy) * uLayerOpacity * mask;
-}
-@end
-
-
 // Single texture in RGBA format, but with a Rect texture.
 // Container layer needs this to render a FBO group.
 @shader sRGBARectTextureLayer<mask:,Mask,Mask3D>FS
 #extension GL_ARB_texture_rectangle : enable
 
 $LAYER_FRAGMENT<mask>$
 
 /* This should not be used on GL ES */
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -349,16 +349,32 @@ SurfaceStreamHostOGL::DeleteTextures()
 }
 
 void
 SurfaceStreamHostOGL::SwapTexturesImpl(const SurfaceDescriptor& aImage,
                                        nsIntRegion* aRegion)
 {
   MOZ_ASSERT(aImage.type() == SurfaceDescriptor::TSurfaceStreamDescriptor,
              "Invalid descriptor");
+
+  mStreamGL = nullptr;
+
+  if (aImage.type() == SurfaceDescriptor::TSurfaceStreamDescriptor) {
+    // Bug 894405
+    //
+    // The SurfaceStream's GLContext was refed before being passed up to us, so
+    // we need to ensure it gets unrefed when we are finished.
+    const SurfaceStreamDescriptor& streamDesc =
+        aImage.get_SurfaceStreamDescriptor();
+
+    SurfaceStream* surfStream = SurfaceStream::FromHandle(streamDesc.handle());
+    if (surfStream) {
+      mStreamGL = dont_AddRef(surfStream->GLContext());
+    }
+  } 
 }
 
 void
 SurfaceStreamHostOGL::Unlock()
 {
   // We don't know what this is unless we're locked
   mFormat = gfx::FORMAT_UNKNOWN;
 }
--- a/gfx/layers/opengl/TextureHostOGL.h
+++ b/gfx/layers/opengl/TextureHostOGL.h
@@ -501,16 +501,17 @@ protected:
   void DeleteTextures();
 
   gfx::IntSize mSize;
   nsRefPtr<GLContext> mGL;
   GLuint mTextureHandle;
   GLenum mTextureTarget;
   GLuint mUploadTexture;
   GLenum mWrapMode;
+  nsRefPtr<GLContext> mStreamGL;
 };
 
 class TiledDeprecatedTextureHostOGL : public DeprecatedTextureHost
                           , public TextureSourceOGL
 {
 public:
   TiledDeprecatedTextureHostOGL()
     : mTextureHandle(0)
@@ -613,16 +614,21 @@ public:
     return mFormat;
   }
 
   GLenum GetWrapMode() const MOZ_OVERRIDE
   {
     return LOCAL_GL_CLAMP_TO_EDGE;
   }
 
+  virtual GLenum GetTextureTarget() const MOZ_OVERRIDE
+  {
+    return mTextureTarget;
+  }
+
   bool IsValid() const MOZ_OVERRIDE;
 
   virtual already_AddRefed<gfxImageSurface> GetAsSurface() MOZ_OVERRIDE;
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   virtual const char* Name() { return "GrallocDeprecatedTextureHostOGL"; }
 #endif
 
--- a/gfx/layers/opengl/ThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/ThebesLayerOGL.cpp
@@ -176,16 +176,17 @@ ThebesLayerBufferOGL::RenderTo(const nsI
 
       basicProgram->Activate();
       basicProgram->SetTextureUnit(0);
       program = basicProgram;
     }
 
     program->SetLayerOpacity(mLayer->GetEffectiveOpacity());
     program->SetLayerTransform(mLayer->GetEffectiveTransform());
+    program->SetTextureTransform(gfx3DMatrix());
     program->SetRenderOffset(aOffset);
     program->LoadMask(mLayer->GetMaskLayer());
 
     const nsIntRegion& visibleRegion = mLayer->GetEffectiveVisibleRegion();
     nsIntRegion tmpRegion;
     const nsIntRegion* renderRegion;
     if (aFlags & PAINT_WILL_RESAMPLE) {
       // If we're resampling, then the texture image will contain exactly the
new file mode 100644
--- /dev/null
+++ b/gfx/skia/patches/0001-Bug-895086-Remove-unused-find_from_uniqueID-function.patch
@@ -0,0 +1,38 @@
+From 58861c38751adf1f4ef3f67f8e85f5c36f1c43a5 Mon Sep 17 00:00:00 2001
+From: George Wright <gw@gwright.org.uk>
+Date: Wed, 17 Jul 2013 16:28:07 -0400
+Subject: [PATCH] Bug 895086 - Remove unused find_from_uniqueID() function from
+ SkFontHost_linux
+
+---
+ gfx/skia/src/ports/SkFontHost_linux.cpp | 14 --------------
+ 1 file changed, 14 deletions(-)
+
+diff --git a/gfx/skia/src/ports/SkFontHost_linux.cpp b/gfx/skia/src/ports/SkFontHost_linux.cpp
+index df21014..05b73dc 100644
+--- a/gfx/skia/src/ports/SkFontHost_linux.cpp
++++ b/gfx/skia/src/ports/SkFontHost_linux.cpp
+@@ -117,20 +117,6 @@ static FamilyRec* find_family(const SkTypeface* member) {
+     return NULL;
+ }
+ 
+-static SkTypeface* find_from_uniqueID(uint32_t uniqueID) {
+-    FamilyRec* curr = gFamilyHead;
+-    while (curr != NULL) {
+-        for (int i = 0; i < 4; i++) {
+-            SkTypeface* face = curr->fFaces[i];
+-            if (face != NULL && face->uniqueID() == uniqueID) {
+-                return face;
+-            }
+-        }
+-        curr = curr->fNext;
+-    }
+-    return NULL;
+-}
+-
+ /*  Remove reference to this face from its family. If the resulting family
+  is empty (has no faces), return that family, otherwise return NULL
+  */
+-- 
+1.8.3.1
+
new file mode 100644
--- /dev/null
+++ b/gfx/skia/patches/0024-Bug-887318-fix-bgra-readback.patch
@@ -0,0 +1,217 @@
+diff --git a/gfx/gl/GLContextSkia.cpp b/gfx/gl/GLContextSkia.cpp
+--- a/gfx/gl/GLContextSkia.cpp
++++ b/gfx/gl/GLContextSkia.cpp
+@@ -303,39 +303,47 @@ const GLubyte* glGetString_mozilla(GrGLe
+     if (name == LOCAL_GL_VERSION) {
+         if (sGLContext.get()->IsGLES2()) {
+             return reinterpret_cast<const GLubyte*>("OpenGL ES 2.0");
+         } else {
+             return reinterpret_cast<const GLubyte*>("2.0");
+         }
+     } else if (name == LOCAL_GL_EXTENSIONS) {
+         // Only expose the bare minimum extensions we want to support to ensure a functional Ganesh
+         // as GLContext only exposes certain extensions
+         static bool extensionsStringBuilt = false;
+-        static char extensionsString[120];
++        static char extensionsString[256];
+ 
+         if (!extensionsStringBuilt) {
+             if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_texture_format_BGRA8888)) {
+                 strcpy(extensionsString, "GL_EXT_texture_format_BGRA8888 ");
+             }
+ 
+             if (sGLContext.get()->IsExtensionSupported(GLContext::OES_packed_depth_stencil)) {
+                 strcat(extensionsString, "GL_OES_packed_depth_stencil ");
+             }
+ 
+             if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_packed_depth_stencil)) {
+                 strcat(extensionsString, "GL_EXT_packed_depth_stencil ");
+             }
+ 
+             if (sGLContext.get()->IsExtensionSupported(GLContext::OES_rgb8_rgba8)) {
+                 strcat(extensionsString, "GL_OES_rgb8_rgba8 ");
+             }
+ 
++            if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_bgra)) {
++                strcat(extensionsString, "GL_EXT_bgra ");
++            }
++
++            if (sGLContext.get()->IsExtensionSupported(GLContext::EXT_read_format_bgra)) {
++                strcat(extensionsString, "GL_EXT_read_format_bgra ");
++            }
++
+             extensionsStringBuilt = true;
+         }
+ 
+         return reinterpret_cast<const GLubyte*>(extensionsString);
+ 
+     } else if (name == LOCAL_GL_SHADING_LANGUAGE_VERSION) {
+         if (sGLContext.get()->IsGLES2()) {
+             return reinterpret_cast<const GLubyte*>("OpenGL ES GLSL ES 1.0");
+         } else {
+             return reinterpret_cast<const GLubyte*>("1.10");
+diff --git a/gfx/skia/src/gpu/gl/GrGpuGL.cpp b/gfx/skia/src/gpu/gl/GrGpuGL.cpp
+--- a/gfx/skia/src/gpu/gl/GrGpuGL.cpp
++++ b/gfx/skia/src/gpu/gl/GrGpuGL.cpp
+@@ -1,18 +1,18 @@
+ /*
+  * Copyright 2011 Google Inc.
+  *
+  * Use of this source code is governed by a BSD-style license that can be
+  * found in the LICENSE file.
+  */
+ 
+-
++#include <algorithm>
+ #include "GrGpuGL.h"
+ #include "GrGLStencilBuffer.h"
+ #include "GrGLPath.h"
+ #include "GrGLShaderBuilder.h"
+ #include "GrTemplates.h"
+ #include "GrTypes.h"
+ #include "SkTemplates.h"
+ 
+ static const GrGLuint GR_MAX_GLUINT = ~0U;
+ static const GrGLint  GR_INVAL_GLINT = ~0;
+@@ -1381,29 +1381,67 @@ bool GrGpuGL::readPixelsWillPayForYFlip(
+     // Note the rowBytes might be tight to the passed in data, but if data
+     // gets clipped in x to the target the rowBytes will no longer be tight.
+     if (left >= 0 && (left + width) < renderTarget->width()) {
+            return 0 == rowBytes ||
+                   GrBytesPerPixel(config) * width == rowBytes;
+     } else {
+         return false;
+     }
+ }
+ 
++static void swizzleRow(void* buffer, int byteLen) {
++    uint8_t* src = (uint8_t*)buffer;
++    uint8_t* end = src + byteLen;
++
++    GrAssert((end - src) % 4 == 0);
++
++    for (; src != end; src += 4) {
++        std::swap(src[0], src[2]);
++    }
++}
++
++bool GrGpuGL::canReadBGRA() const
++{
++    if (kDesktop_GrGLBinding == this->glBinding() ||
++        this->hasExtension("GL_EXT_bgra"))
++        return true;
++
++    if (this->hasExtension("GL_EXT_read_format_bgra")) {
++        GrGLint readFormat = 0;
++        GrGLint readType = 0;
++
++        GL_CALL(GetIntegerv(GR_GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat));
++        GL_CALL(GetIntegerv(GR_GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType));
++
++        return readFormat == GR_GL_BGRA && readType == GR_GL_UNSIGNED_BYTE;
++    }
++
++    return false;
++}
++
+ bool GrGpuGL::onReadPixels(GrRenderTarget* target,
+                            int left, int top,
+                            int width, int height,
+                            GrPixelConfig config,
+                            void* buffer,
+                            size_t rowBytes) {
+     GrGLenum format;
+     GrGLenum type;
+     bool flipY = kBottomLeft_GrSurfaceOrigin == target->origin();
++    bool needSwizzle = false;
++
++    if (kBGRA_8888_GrPixelConfig == config && !this->canReadBGRA()) {
++        // Read RGBA and swizzle after
++        config = kRGBA_8888_GrPixelConfig;
++        needSwizzle = true;
++    }
++
+     if (!this->configToGLFormats(config, false, NULL, &format, &type)) {
+         return false;
+     }
+     size_t bpp = GrBytesPerPixel(config);
+     if (!adjust_pixel_ops_params(target->width(), target->height(), bpp,
+                                  &left, &top, &width, &height,
+                                  const_cast<const void**>(&buffer),
+                                  &rowBytes)) {
+         return false;
+     }
+@@ -1478,35 +1516,46 @@ bool GrGpuGL::onReadPixels(GrRenderTarge
+             scratch.reset(tightRowBytes);
+             void* tmpRow = scratch.get();
+             // flip y in-place by rows
+             const int halfY = height >> 1;
+             char* top = reinterpret_cast<char*>(buffer);
+             char* bottom = top + (height - 1) * rowBytes;
+             for (int y = 0; y < halfY; y++) {
+                 memcpy(tmpRow, top, tightRowBytes);
+                 memcpy(top, bottom, tightRowBytes);
+                 memcpy(bottom, tmpRow, tightRowBytes);
++
++                if (needSwizzle) {
++                    swizzleRow(top, tightRowBytes);
++                    swizzleRow(bottom, tightRowBytes);
++                }
++
+                 top += rowBytes;
+                 bottom -= rowBytes;
+             }
+         }
+     } else {
+-        GrAssert(readDst != buffer);        GrAssert(rowBytes != tightRowBytes);
++        GrAssert(readDst != buffer);
++        GrAssert(rowBytes != tightRowBytes);
+         // copy from readDst to buffer while flipping y
+         // const int halfY = height >> 1;
+         const char* src = reinterpret_cast<const char*>(readDst);
+         char* dst = reinterpret_cast<char*>(buffer);
+         if (flipY) {
+             dst += (height-1) * rowBytes;
+         }
+         for (int y = 0; y < height; y++) {
+             memcpy(dst, src, tightRowBytes);
++            if (needSwizzle) {
++                swizzleRow(dst, tightRowBytes);
++            }
++
+             src += readDstRowBytes;
+             if (!flipY) {
+                 dst += rowBytes;
+             } else {
+                 dst -= rowBytes;
+             }
+         }
+     }
+     return true;
+ }
+diff --git a/gfx/skia/src/gpu/gl/GrGpuGL.h b/gfx/skia/src/gpu/gl/GrGpuGL.h
+--- a/gfx/skia/src/gpu/gl/GrGpuGL.h
++++ b/gfx/skia/src/gpu/gl/GrGpuGL.h
+@@ -243,20 +243,22 @@ private:
+                        GrPixelConfig dataConfig,
+                        const void* data,
+                        size_t rowBytes);
+ 
+     bool createRenderTargetObjects(int width, int height,
+                                    GrGLuint texID,
+                                    GrGLRenderTarget::Desc* desc);
+ 
+     void fillInConfigRenderableTable();
+ 
++    bool canReadBGRA() const;
++
+     GrGLContext fGLContext;
+ 
+     // GL program-related state
+     ProgramCache*               fProgramCache;
+     SkAutoTUnref<GrGLProgram>   fCurrentProgram;
+ 
+     ///////////////////////////////////////////////////////////////////////////
+     ///@name Caching of GL State
+     ///@{
+     int                         fHWActiveTextureUnitIdx;
--- a/gfx/skia/src/gpu/SkGpuDevice.cpp
+++ b/gfx/skia/src/gpu/SkGpuDevice.cpp
@@ -18,16 +18,17 @@
 #include "SkColorFilter.h"
 #include "SkDeviceImageFilterProxy.h"
 #include "SkDrawProcs.h"
 #include "SkGlyphCache.h"
 #include "SkImageFilter.h"
 #include "SkPathEffect.h"
 #include "SkStroke.h"
 #include "SkUtils.h"
+#include "SkRasterizer.h"
 
 #define CACHE_COMPATIBLE_DEVICE_TEXTURES 1
 
 #if 0
     extern bool (*gShouldDrawProc)();
     #define CHECK_SHOULD_DRAW(draw, forceI)                     \
         do {                                                    \
             if (gShouldDrawProc && !gShouldDrawProc()) return;  \
@@ -662,18 +663,20 @@ void SkGpuDevice::drawRect(const SkDraw&
     SkScalar width = paint.getStrokeWidth();
 
     /*
         We have special code for hairline strokes, miter-strokes, and fills.
         Anything else we just call our path code.
      */
     bool usePath = doStroke && width > 0 &&
                     paint.getStrokeJoin() != SkPaint::kMiter_Join;
-    // another two reasons we might need to call drawPath...
-    if (paint.getMaskFilter() || paint.getPathEffect()) {
+    // another three reasons we might need to call drawPath...
+    if (paint.getMaskFilter() ||
+        paint.getPathEffect() ||
+        paint.getRasterizer()) {
         usePath = true;
     }
     // until we aa rotated rects...
     if (!usePath && paint.isAntiAlias() && !fContext->getMatrix().rectStaysRect()) {
         usePath = true;
     }
     // small miter limit means right angles show bevel...
     if (SkPaint::kMiter_Join == paint.getStrokeJoin() &&
@@ -744,32 +747,43 @@ inline bool shouldDrawBlurWithCPU(const 
     if (rect.width() <= MIN_GPU_BLUR_SIZE &&
         rect.height() <= MIN_GPU_BLUR_SIZE &&
         radius <= MIN_GPU_BLUR_RADIUS) {
         return true;
     }
     return false;
 }
 
-bool drawWithGPUMaskFilter(GrContext* context, const SkPath& devPath, const SkStrokeRec& stroke,
-                           SkMaskFilter* filter, const SkRegion& clip,
+bool drawWithGPUMaskFilter(GrContext* context, SkPath& path, bool pathIsMutable,
+                           const SkStrokeRec& stroke, SkMaskFilter* filter,
+                           SkRasterizer *rasterizer, const SkRegion& clip,
                            SkBounder* bounder, GrPaint* grp) {
+    if (rasterizer) {
+        return false;
+    }
+
     SkMaskFilter::BlurInfo info;
     SkMaskFilter::BlurType blurType = filter->asABlur(&info);
     if (SkMaskFilter::kNone_BlurType == blurType) {
         return false;
     }
     SkScalar radius = info.fIgnoreTransform ? info.fRadius
                                             : context->getMatrix().mapRadius(info.fRadius);
     radius = SkMinScalar(radius, MAX_BLUR_RADIUS);
     if (radius <= 0) {
         return false;
     }
 
-    SkRect srcRect = devPath.getBounds();
+    SkPath tmpPath;
+    SkPath* devPathPtr = pathIsMutable ? &path : &tmpPath;
+
+    // transform the path into device space
+    path.transform(context->getMatrix(), devPathPtr);
+
+    SkRect srcRect = devPathPtr->getBounds();
     if (shouldDrawBlurWithCPU(srcRect, radius)) {
         return false;
     }
 
     float sigma = SkScalarToFloat(radius) * BLUR_SIGMA_SCALE;
     float sigma3 = sigma * 3.0f;
 
     SkRect clipRect;
@@ -829,17 +843,17 @@ bool drawWithGPUMaskFilter(GrContext* co
         }
 
         GrContext::AutoMatrix am;
 
         // Draw hard shadow to pathTexture with path top-left at origin using tempPaint.
         SkMatrix translate;
         translate.setTranslate(offset.fX, offset.fY);
         am.set(context, translate);
-        context->drawPath(tempPaint, devPath, stroke);
+        context->drawPath(tempPaint, *devPathPtr, stroke);
 
         // If we're doing a normal blur, we can clobber the pathTexture in the
         // gaussianBlur.  Otherwise, we need to save it for later compositing.
         bool isNormalBlur = blurType == SkMaskFilter::kNormal_BlurType;
         blurTexture.reset(context->gaussianBlur(pathTexture, isNormalBlur,
                                                 srcRect, sigma, sigma));
         if (NULL == blurTexture) {
             return false;
@@ -885,32 +899,52 @@ bool drawWithGPUMaskFilter(GrContext* co
 
     grp->coverageStage(MASK_IDX)->reset();
     grp->coverageStage(MASK_IDX)->setEffect(
         GrSimpleTextureEffect::Create(blurTexture, matrix))->unref();
     context->drawRect(*grp, finalRect);
     return true;
 }
 
-bool drawWithMaskFilter(GrContext* context, const SkPath& devPath,
-                        SkMaskFilter* filter, const SkRegion& clip, SkBounder* bounder,
+bool drawWithMaskFilter(GrContext* context, SkPath& path, bool pathIsMutable,
+                        SkMaskFilter* filter, SkRasterizer* rasterizer,
+                        const SkRegion& clip, SkBounder* bounder,
                         GrPaint* grp, SkPaint::Style style) {
     SkMask  srcM, dstM;
 
-    if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), filter, &context->getMatrix(), &srcM,
-                            SkMask::kComputeBoundsAndRenderImage_CreateMode, style)) {
-        return false;
+    if (rasterizer) {
+        if (!rasterizer->rasterize(path, context->getMatrix(),
+                                   &clip.getBounds(), filter, &srcM,
+                                   SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
+            return false;
+        }
+    } else {
+        SkPath tmpPath;
+        SkPath* devPathPtr = pathIsMutable ? &path : &tmpPath;
+
+        // transform the path into device space
+        path.transform(context->getMatrix(), devPathPtr);
+        if (!SkDraw::DrawToMask(*devPathPtr, &clip.getBounds(), filter, &context->getMatrix(),
+                                &srcM, SkMask::kComputeBoundsAndRenderImage_CreateMode,
+                                style)) {
+            return false;
+        }
     }
     SkAutoMaskFreeImage autoSrc(srcM.fImage);
 
-    if (!filter->filterMask(&dstM, srcM, context->getMatrix(), NULL)) {
+    if (filter && !filter->filterMask(&dstM, srcM, context->getMatrix(), NULL)) {
         return false;
+    } else if (!filter) {
+        dstM = srcM;
     }
-    // this will free-up dstM when we're done (allocated in filterMask())
-    SkAutoMaskFreeImage autoDst(dstM.fImage);
+
+    // this will free-up dstM when we're done (allocated in filterMask()).
+    // If we don't have a filter, then srcM and dstM are the same, so
+    // don't free it twice.
+    SkAutoMaskFreeImage autoDst(filter ? dstM.fImage : NULL);
 
     if (clip.quickReject(dstM.fBounds)) {
         return false;
     }
     if (bounder && !bounder->doIRect(dstM.fBounds)) {
         return false;
     }
 
@@ -1003,34 +1037,32 @@ void SkGpuDevice::drawPath(const SkDraw&
                                              cullRect)) {
         pathPtr = &effectPath;
     }
 
     if (!pathEffect && doHairLine) {
         stroke.setHairlineStyle();
     }
 
-    if (paint.getMaskFilter()) {
+    if (paint.getMaskFilter() || paint.getRasterizer()) {
         if (!stroke.isHairlineStyle()) {
             if (stroke.applyToPath(&tmpPath, *pathPtr)) {
                 pathPtr = &tmpPath;
                 stroke.setFillStyle();
             }
         }
 
-        // avoid possibly allocating a new path in transform if we can
-        SkPath* devPathPtr = pathIsMutable ? pathPtr : &tmpPath;
-
-        // transform the path into device space
-        pathPtr->transform(fContext->getMatrix(), devPathPtr);
-        if (!drawWithGPUMaskFilter(fContext, *devPathPtr, stroke, paint.getMaskFilter(),
-                                   *draw.fClip, draw.fBounder, &grPaint)) {
+        if (!drawWithGPUMaskFilter(fContext, *pathPtr, pathIsMutable,
+                                   stroke, paint.getMaskFilter(),
+                                   paint.getRasterizer(), *draw.fClip,
+                                   draw.fBounder, &grPaint)) {
             SkPaint::Style style = stroke.isHairlineStyle() ? SkPaint::kStroke_Style :
                                                               SkPaint::kFill_Style;
-            drawWithMaskFilter(fContext, *devPathPtr, paint.getMaskFilter(),
+            drawWithMaskFilter(fContext, *pathPtr, pathIsMutable,
+                               paint.getMaskFilter(), paint.getRasterizer(),
                                *draw.fClip, draw.fBounder, &grPaint, style);
         }
         return;
     }
 
     fContext->drawPath(grPaint, *pathPtr, stroke);
 }
 
--- a/gfx/skia/src/gpu/gl/GrGpuGL.cpp
+++ b/gfx/skia/src/gpu/gl/GrGpuGL.cpp
@@ -1,16 +1,16 @@
 /*
  * Copyright 2011 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
 
-
+#include <algorithm>
 #include "GrGpuGL.h"
 #include "GrGLStencilBuffer.h"
 #include "GrGLPath.h"
 #include "GrGLShaderBuilder.h"
 #include "GrTemplates.h"
 #include "GrTypes.h"
 #include "SkTemplates.h"
 
@@ -1383,25 +1383,63 @@ bool GrGpuGL::readPixelsWillPayForYFlip(
     if (left >= 0 && (left + width) < renderTarget->width()) {
            return 0 == rowBytes ||
                   GrBytesPerPixel(config) * width == rowBytes;
     } else {
         return false;
     }
 }
 
+static void swizzleRow(void* buffer, int byteLen) {
+    uint8_t* src = (uint8_t*)buffer;
+    uint8_t* end = src + byteLen;
+
+    GrAssert((end - src) % 4 == 0);
+
+    for (; src != end; src += 4) {
+        std::swap(src[0], src[2]);
+    }
+}
+
+bool GrGpuGL::canReadBGRA() const
+{
+    if (kDesktop_GrGLBinding == this->glBinding() ||
+        this->hasExtension("GL_EXT_bgra"))
+        return true;
+
+    if (this->hasExtension("GL_EXT_read_format_bgra")) {
+        GrGLint readFormat = 0;
+        GrGLint readType = 0;
+
+        GL_CALL(GetIntegerv(GR_GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat));
+        GL_CALL(GetIntegerv(GR_GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType));
+
+        return readFormat == GR_GL_BGRA && readType == GR_GL_UNSIGNED_BYTE;
+    }
+
+    return false;
+}
+
 bool GrGpuGL::onReadPixels(GrRenderTarget* target,
                            int left, int top,
                            int width, int height,
                            GrPixelConfig config,
                            void* buffer,
                            size_t rowBytes) {
     GrGLenum format;
     GrGLenum type;
     bool flipY = kBottomLeft_GrSurfaceOrigin == target->origin();
+    bool needSwizzle = false;
+
+    if (kBGRA_8888_GrPixelConfig == config && !this->canReadBGRA()) {
+        // Read RGBA and swizzle after
+        config = kRGBA_8888_GrPixelConfig;
+        needSwizzle = true;
+    }
+
     if (!this->configToGLFormats(config, false, NULL, &format, &type)) {
         return false;
     }
     size_t bpp = GrBytesPerPixel(config);
     if (!adjust_pixel_ops_params(target->width(), target->height(), bpp,
                                  &left, &top, &width, &height,
                                  const_cast<const void**>(&buffer),
                                  &rowBytes)) {
@@ -1480,31 +1518,42 @@ bool GrGpuGL::onReadPixels(GrRenderTarge
             // flip y in-place by rows
             const int halfY = height >> 1;
             char* top = reinterpret_cast<char*>(buffer);
             char* bottom = top + (height - 1) * rowBytes;
             for (int y = 0; y < halfY; y++) {
                 memcpy(tmpRow, top, tightRowBytes);
                 memcpy(top, bottom, tightRowBytes);
                 memcpy(bottom, tmpRow, tightRowBytes);
+
+                if (needSwizzle) {
+                    swizzleRow(top, tightRowBytes);
+                    swizzleRow(bottom, tightRowBytes);
+                }
+
                 top += rowBytes;
                 bottom -= rowBytes;
             }
         }
     } else {
-        GrAssert(readDst != buffer);        GrAssert(rowBytes != tightRowBytes);
+        GrAssert(readDst != buffer);
+        GrAssert(rowBytes != tightRowBytes);
         // copy from readDst to buffer while flipping y
         // const int halfY = height >> 1;
         const char* src = reinterpret_cast<const char*>(readDst);
         char* dst = reinterpret_cast<char*>(buffer);
         if (flipY) {
             dst += (height-1) * rowBytes;
         }
         for (int y = 0; y < height; y++) {
             memcpy(dst, src, tightRowBytes);
+            if (needSwizzle) {
+                swizzleRow(dst, tightRowBytes);
+            }
+
             src += readDstRowBytes;
             if (!flipY) {
                 dst += rowBytes;
             } else {
                 dst -= rowBytes;
             }
         }
     }
--- a/gfx/skia/src/gpu/gl/GrGpuGL.h
+++ b/gfx/skia/src/gpu/gl/GrGpuGL.h
@@ -245,16 +245,18 @@ private:
                        size_t rowBytes);
 
     bool createRenderTargetObjects(int width, int height,
                                    GrGLuint texID,
                                    GrGLRenderTarget::Desc* desc);
 
     void fillInConfigRenderableTable();
 
+    bool canReadBGRA() const;
+
     GrGLContext fGLContext;
 
     // GL program-related state
     ProgramCache*               fProgramCache;
     SkAutoTUnref<GrGLProgram>   fCurrentProgram;
 
     ///////////////////////////////////////////////////////////////////////////
     ///@name Caching of GL State
--- a/gfx/skia/src/ports/SkFontHost_linux.cpp
+++ b/gfx/skia/src/ports/SkFontHost_linux.cpp
@@ -112,30 +112,16 @@ static FamilyRec* find_family(const SkTy
                 return curr;
             }
         }
         curr = curr->fNext;
     }
     return NULL;
 }
 
-static SkTypeface* find_from_uniqueID(uint32_t uniqueID) {
-    FamilyRec* curr = gFamilyHead;
-    while (curr != NULL) {
-        for (int i = 0; i < 4; i++) {
-            SkTypeface* face = curr->fFaces[i];
-            if (face != NULL && face->uniqueID() == uniqueID) {
-                return face;
-            }
-        }
-        curr = curr->fNext;
-    }
-    return NULL;
-}
-
 /*  Remove reference to this face from its family. If the resulting family
  is empty (has no faces), return that family, otherwise return NULL
  */
 static FamilyRec* remove_from_family(const SkTypeface* face) {
     FamilyRec* family = find_family(face);
     SkASSERT(family->fFaces[face->style()] == face);
     family->fFaces[face->style()] = NULL;
 
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -39,41 +39,66 @@ gfxUserFontSet::GetUserFontsLog()
 #define LOG_ENABLED() PR_LOG_TEST(GetUserFontsLog(), PR_LOG_DEBUG)
 
 static uint64_t sFontSetGeneration = 0;
 
 // TODO: support for unicode ranges not yet implemented
 
 gfxProxyFontEntry::gfxProxyFontEntry(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
              uint32_t aWeight,
-             uint32_t aStretch,
+             int32_t aStretch,
              uint32_t aItalicStyle,
              const nsTArray<gfxFontFeature>& aFeatureSettings,
              uint32_t aLanguageOverride,
              gfxSparseBitSet *aUnicodeRanges)
     : gfxFontEntry(NS_LITERAL_STRING("Proxy")),
       mLoadingState(NOT_LOADING),
       mUnsupportedFormat(false),
       mLoader(nullptr)
 {
     mIsProxy = true;
     mSrcList = aFontFaceSrcList;
     mSrcIndex = 0;
     mWeight = aWeight;
     mStretch = aStretch;
+    // XXX Currently, we don't distinguish 'italic' and 'oblique' styles;
+    // we need to fix this. (Bug 543715)
     mItalic = (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0;
     mFeatureSettings.AppendElements(aFeatureSettings);
     mLanguageOverride = aLanguageOverride;
     mIsUserFont = true;
 }
 
 gfxProxyFontEntry::~gfxProxyFontEntry()
 {
 }
 
+bool
+gfxProxyFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+                           uint32_t aWeight,
+                           int32_t aStretch,
+                           uint32_t aItalicStyle,
+                           const nsTArray<gfxFontFeature>& aFeatureSettings,
+                           uint32_t aLanguageOverride,
+                           gfxSparseBitSet *aUnicodeRanges)
+{
+    // XXX font entries don't distinguish italic from oblique (bug 543715)
+    bool isItalic =
+        (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0;
+
+    return mWeight == aWeight &&
+           mStretch == aStretch &&
+           mItalic == isItalic &&
+           mFeatureSettings == aFeatureSettings &&
+           mLanguageOverride == aLanguageOverride &&
+           mSrcList == aFontFaceSrcList;
+           // XXX once we support unicode-range (bug 475891),
+           // we'll need to compare that here as well
+}
+
 gfxFont*
 gfxProxyFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
 {
     // cannot create an actual font for a proxy entry
     return nullptr;
 }
 
 gfxUserFontSet::gfxUserFontSet()
@@ -85,44 +110,74 @@ gfxUserFontSet::gfxUserFontSet()
 gfxUserFontSet::~gfxUserFontSet()
 {
 }
 
 gfxFontEntry*
 gfxUserFontSet::AddFontFace(const nsAString& aFamilyName,
                             const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                             uint32_t aWeight,
-                            uint32_t aStretch,
+                            int32_t aStretch,
                             uint32_t aItalicStyle,
                             const nsTArray<gfxFontFeature>& aFeatureSettings,
                             const nsString& aLanguageOverride,
                             gfxSparseBitSet *aUnicodeRanges)
 {
-    gfxProxyFontEntry *proxyEntry = nullptr;
-
     nsAutoString key(aFamilyName);
     ToLowerCase(key);
 
     bool found;
 
     if (aWeight == 0)
         aWeight = NS_FONT_WEIGHT_NORMAL;
 
     // stretch, italic/oblique ==> zero implies normal
 
     gfxMixedFontFamily *family = mFontFamilies.GetWeak(key, &found);
     if (!family) {
         family = new gfxMixedFontFamily(aFamilyName);
         mFontFamilies.Put(key, family);
     }
 
-    // construct a new face and add it into the family
     uint32_t languageOverride =
         gfxFontStyle::ParseFontLanguageOverride(aLanguageOverride);
-    proxyEntry =
+
+    // If there's already a proxy in the family whose descriptors all match,
+    // we can just move it to the end of the list instead of adding a new
+    // face that will always "shadow" the old one.
+    // Note that we can't do this for "real" (non-proxy) entries, even if the
+    // style descriptors match, as they might have had a different source list,
+    // but we no longer have the old source list available to check.
+    nsTArray<nsRefPtr<gfxFontEntry> >& fontList = family->GetFontList();
+    for (uint32_t i = 0, count = fontList.Length(); i < count; i++) {
+        if (!fontList[i]->mIsProxy) {
+            continue;
+        }
+
+        gfxProxyFontEntry *existingProxyEntry =
+            static_cast<gfxProxyFontEntry*>(fontList[i].get());
+        if (!existingProxyEntry->Matches(aFontFaceSrcList,
+                                         aWeight, aStretch, aItalicStyle,
+                                         aFeatureSettings, languageOverride,
+                                         aUnicodeRanges)) {
+            continue;
+        }
+
+        // We've found an entry that matches the new face exactly, so advance
+        // it to the end of the list.
+        // (Hold a strong reference while doing this, in case the only thing
+        // keeping the proxyFontEntry alive is the reference from family!)
+        nsRefPtr<gfxFontEntry> ref(existingProxyEntry);
+        family->RemoveFontEntry(existingProxyEntry);
+        family->AddFontEntry(existingProxyEntry);
+        return existingProxyEntry;
+    }
+
+    // construct a new face and add it into the family
+    gfxProxyFontEntry *proxyEntry =
         new gfxProxyFontEntry(aFontFaceSrcList, aWeight, aStretch,
                               aItalicStyle,
                               aFeatureSettings,
                               languageOverride,
                               aUnicodeRanges);
     family->AddFontEntry(proxyEntry);
 #ifdef PR_LOGGING
     if (LOG_ENABLED()) {
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -36,16 +36,30 @@ struct gfxFontFaceSrc {
     uint32_t               mFormatFlags;
 
     nsString               mLocalName;     // full font name if local
     nsCOMPtr<nsIURI>       mURI;           // uri if url
     nsCOMPtr<nsIURI>       mReferrer;      // referrer url if url
     nsCOMPtr<nsIPrincipal> mOriginPrincipal; // principal if url
 };
 
+inline bool
+operator==(const gfxFontFaceSrc& a, const gfxFontFaceSrc& b)
+{
+    bool equals;
+    return (a.mIsLocal && b.mIsLocal &&
+            a.mLocalName == b.mLocalName) ||
+           (!a.mIsLocal && !b.mIsLocal &&
+            a.mUseOriginPrincipal == b.mUseOriginPrincipal &&
+            a.mFormatFlags == b.mFormatFlags &&
+            NS_SUCCEEDED(a.mURI->Equals(b.mURI, &equals)) && equals &&
+            NS_SUCCEEDED(a.mReferrer->Equals(b.mReferrer, &equals)) && equals &&
+            a.mOriginPrincipal->Equals(b.mOriginPrincipal));
+}
+
 // Subclassed to store platform-specific code cleaned out when font entry is
 // deleted.
 // Lifetime: from when platform font is created until it is deactivated.
 // If the platform does not need to add any platform-specific code/data here,
 // then the gfxUserFontSet will allocate a base gfxUserFontData and attach
 // to the entry to track the basic user font info fields here.
 class gfxUserFontData {
 public:
@@ -163,23 +177,24 @@ public:
         STATUS_LOADED,
         STATUS_FORMAT_NOT_SUPPORTED,
         STATUS_ERROR,
         STATUS_END_OF_LIST
     };
 
 
     // add in a font face
-    // weight, stretch - 0 == unknown, [1, 9] otherwise
+    // weight - 0 == unknown, [100, 900] otherwise (multiples of 100)
+    // stretch = [NS_FONT_STRETCH_ULTRA_CONDENSED, NS_FONT_STRETCH_ULTRA_EXPANDED]
     // italic style = constants in gfxFontConstants.h, e.g. NS_FONT_STYLE_NORMAL
     // TODO: support for unicode ranges not yet implemented
     gfxFontEntry *AddFontFace(const nsAString& aFamilyName,
                               const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                               uint32_t aWeight,
-                              uint32_t aStretch,
+                              int32_t aStretch,
                               uint32_t aItalicStyle,
                               const nsTArray<gfxFontFeature>& aFeatureSettings,
                               const nsString& aLanguageOverride,
                               gfxSparseBitSet *aUnicodeRanges = nullptr);
 
     // add in a font face for which we have the gfxFontEntry already
     void AddFontFace(const nsAString& aFamilyName, gfxFontEntry* aFontEntry);
 
@@ -423,24 +438,33 @@ private:
 // acts a placeholder until the real font is downloaded
 
 class gfxProxyFontEntry : public gfxFontEntry {
     friend class gfxUserFontSet;
 
 public:
     gfxProxyFontEntry(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                       uint32_t aWeight,
-                      uint32_t aStretch,
+                      int32_t aStretch,
                       uint32_t aItalicStyle,
                       const nsTArray<gfxFontFeature>& aFeatureSettings,
                       uint32_t aLanguageOverride,
                       gfxSparseBitSet *aUnicodeRanges);
 
     virtual ~gfxProxyFontEntry();
 
+    // Return whether the entry matches the given list of attributes
+    bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
+                 uint32_t aWeight,
+                 int32_t aStretch,
+                 uint32_t aItalicStyle,
+                 const nsTArray<gfxFontFeature>& aFeatureSettings,
+                 uint32_t aLanguageOverride,
+                 gfxSparseBitSet *aUnicodeRanges);
+
     virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold);
 
     // note that code depends on the ordering of these values!
     enum LoadingState {
         NOT_LOADING = 0,     // not started to load any font resources yet
         LOADING_STARTED,     // loading has started; hide fallback font
         LOADING_ALMOST_DONE, // timeout happened but we're nearly done,
                              // so keep hiding fallback font
--- a/js/src/assembler/assembler/MacroAssemblerARM.h
+++ b/js/src/assembler/assembler/MacroAssemblerARM.h
@@ -1106,27 +1106,27 @@ public:
     }
 
     DataLabelPtr storePtrWithPatch(ImplicitAddress address)
     {
         return storePtrWithPatch(ImmPtr(0), address);
     }
 
     // Floating point operators
-    bool supportsFloatingPoint() const
+    static bool supportsFloatingPoint()
     {
         return s_isVFPPresent;
     }
 
-    bool supportsFloatingPointTruncate() const
+    static bool supportsFloatingPointTruncate()
     {
         return true;
     }
 
-    bool supportsFloatingPointSqrt() const
+    static bool supportsFloatingPointSqrt()
     {
         return s_isVFPPresent;
     }
 
     void moveDouble(FPRegisterID src, FPRegisterID dest)
     {
         m_assembler.fcpyd_r(dest, src);
     }
--- a/js/src/assembler/assembler/MacroAssemblerARMv7.h
+++ b/js/src/assembler/assembler/MacroAssemblerARMv7.h
@@ -502,31 +502,31 @@ public:
     {
         move(imm, dataTempRegister);
         store32(dataTempRegister, address);
     }
 
 
     // Floating-point operations:
 
-    bool supportsFloatingPoint() const { return true; }
+    static bool supportsFloatingPoint() { return true; }
     // On x86(_64) the MacroAssembler provides an interface to truncate a double to an integer.
     // If a value is not representable as an integer, and possibly for some values that are,
     // (on x86 INT_MIN, since this is indistinguishable from results for out-of-range/NaN input)
     // a branch will  be taken.  It is not clear whether this interface will be well suited to
     // other platforms.  On ARMv7 the hardware truncation operation produces multiple possible
     // failure values (saturates to INT_MIN & INT_MAX, NaN reulsts in a value of 0).  This is a
     // temporary solution while we work out what this interface should be.  Either we need to
     // decide to make this interface work on all platforms, rework the interface to make it more
     // generic, or decide that the MacroAssembler cannot practically be used to abstracted these
     // operations, and make clients go directly to the m_assembler to plant truncation instructions.
     // In short, FIXME:.
-    bool supportsFloatingPointTruncate() const { return false; }
+    static bool supportsFloatingPointTruncate() { return false; }
 
-    bool supportsFloatingPointSqrt() const
+    static bool supportsFloatingPointSqrt()
     {
         return false;
     }
 
     void loadDouble(ImplicitAddress address, FPRegisterID dest)
     {
         RegisterID base = address.base;
         int32_t offset = address.offset;
--- a/js/src/assembler/assembler/MacroAssemblerMIPS.h
+++ b/js/src/assembler/assembler/MacroAssemblerMIPS.h
@@ -1384,35 +1384,35 @@ public:
             move(imm, immTempRegister);
             move(ImmPtr(address), addrTempRegister);
             m_assembler.sw(immTempRegister, addrTempRegister, 0);
         }
     }
 
     // Floating-point operations:
 
-    bool supportsFloatingPoint() const
+    static bool supportsFloatingPoint()
     {
 #if WTF_MIPS_DOUBLE_FLOAT
         return true;
 #else
         return false;
 #endif
     }
 
-    bool supportsFloatingPointTruncate() const
+    static bool supportsFloatingPointTruncate()
     {
 #if WTF_MIPS_DOUBLE_FLOAT && WTF_MIPS_ISA_AT_LEAST(2)
         return true;
 #else
         return false;
 #endif
     }
 
-    bool supportsFloatingPointSqrt() const
+    static bool supportsFloatingPointSqrt()
     {
 #if WTF_MIPS_DOUBLE_FLOAT && WTF_MIPS_ISA_AT_LEAST(2)
         return true;
 #else
         return false;
 #endif
     }
 
--- a/js/src/assembler/assembler/MacroAssemblerSparc.h
+++ b/js/src/assembler/assembler/MacroAssemblerSparc.h
@@ -1102,27 +1102,27 @@ namespace JSC {
         }
 
         DataLabelPtr storePtrWithPatch(ImplicitAddress address)
         {
             return storePtrWithPatch(ImmPtr(0), address);
         }
 
         // Floating point operators
-        bool supportsFloatingPoint() const
+        static bool supportsFloatingPoint()
         {
             return true;
         }
 
-        bool supportsFloatingPointTruncate() const
+        static bool supportsFloatingPointTruncate()
         {
             return true;
         }
 
-        bool supportsFloatingPointSqrt() const
+        static bool supportsFloatingPointSqrt()
         {
             return true;
         }
 
         void moveDouble(FPRegisterID src, FPRegisterID dest)
         {
             m_assembler.fmovd_r(src, dest);
         }
--- a/js/src/assembler/assembler/MacroAssemblerX86.h
+++ b/js/src/assembler/assembler/MacroAssemblerX86.h
@@ -36,19 +36,17 @@
 
 #include "assembler/assembler/MacroAssemblerX86Common.h"
 
 namespace JSC {
 
 class MacroAssemblerX86 : public MacroAssemblerX86Common {
 public:
     MacroAssemblerX86()
-        : m_isSSE2Present(isSSE2Present())
-    {
-    }
+    { }
 
     static const Scale ScalePtr = TimesFour;
     static const unsigned int TotalRegisters = 8;
 
     using MacroAssemblerX86Common::add32;
     using MacroAssemblerX86Common::and32;
     using MacroAssemblerX86Common::sub32;
     using MacroAssemblerX86Common::or32;
@@ -224,24 +222,22 @@ public:
         m_assembler.pusha();
     }
 
     void popAllRegs()
     {
         m_assembler.popa();
     }
 
-    bool supportsFloatingPoint() const { return m_isSSE2Present; }
+    static bool supportsFloatingPoint() { return isSSE2Present(); }
     // See comment on MacroAssemblerARMv7::supportsFloatingPointTruncate()
-    bool supportsFloatingPointTruncate() const { return m_isSSE2Present; }
-    bool supportsFloatingPointSqrt() const { return m_isSSE2Present; }
+    static bool supportsFloatingPointTruncate() { return isSSE2Present(); }
+    static bool supportsFloatingPointSqrt() { return isSSE2Present(); }
 
 private:
-    const bool m_isSSE2Present;
-
     friend class LinkBuffer;
     friend class RepatchBuffer;
 
     static void linkCall(void* code, Call call, FunctionPtr function)
     {
         X86Assembler::linkCall(code, call.m_jmp, function.value());
     }
 
--- a/js/src/assembler/assembler/MacroAssemblerX86_64.h
+++ b/js/src/assembler/assembler/MacroAssemblerX86_64.h
@@ -563,20 +563,20 @@ public:
         storePtr(ImmPtr(reinterpret_cast<void *>(imm.u.u64)), address);
     }
 
     void storeDouble(ImmDouble imm, BaseIndex address)
     {
         storePtr(ImmPtr(reinterpret_cast<void *>(imm.u.u64)), address);
     }
 
-    bool supportsFloatingPoint() const { return true; }
+    static bool supportsFloatingPoint() { return true; }
     // See comment on MacroAssemblerARMv7::supportsFloatingPointTruncate()
-    bool supportsFloatingPointTruncate() const { return true; }
-    bool supportsFloatingPointSqrt() const { return true; }
+    static bool supportsFloatingPointTruncate() { return true; }
+    static bool supportsFloatingPointSqrt() { return true; }
 
 private:
     friend class LinkBuffer;
     friend class RepatchBuffer;
 
     static void linkCall(void* code, Call call, FunctionPtr function)
     {
         if (!call.isFlagSet(Call::Near))
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -466,16 +466,19 @@ FunctionBox::FunctionBox(ExclusiveContex
     inGenexpLambda(false),
     hasDestructuringArgs(false),
     useAsm(directives.asmJS()),
     insideUseAsm(outerpc && outerpc->useAsmOrInsideUseAsm()),
     usesArguments(false),
     usesApply(false),
     funCxFlags()
 {
+    // Functions created at parse time may be set singleton after parsing and
+    // baked into JIT code, so they must be allocated tenured. They are held by
+    // the JSScript so cannot be collected during a minor GC anyway.
     JS_ASSERT(fun->isTenured());
 
     if (!outerpc) {
         inWith = false;
 
     } else if (outerpc->parsingWith) {
         // This covers cases that don't involve eval().  For example:
         //
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -334,16 +334,76 @@ js::Nursery::forwardBufferPointer(HeapSl
      * isInside will still be true, even if this zero-size allocation abuts the
      * end of the allocable area. Thus, it is always safe to read the first
      * word of |old| here.
      */
     *pSlotsElems = *reinterpret_cast<HeapSlot **>(old);
     JS_ASSERT(!isInside(*pSlotsElems));
 }
 
+void
+js::Nursery::collectToFixedPoint(MinorCollectionTracer *trc)
+{
+    for (RelocationOverlay *p = trc->head; p; p = p->next()) {
+        JSObject *obj = static_cast<JSObject*>(p->forwardingAddress());
+        traceObject(trc, obj);
+    }
+}
+
+JS_ALWAYS_INLINE void
+js::Nursery::traceObject(MinorCollectionTracer *trc, JSObject *obj)
+{
+    Class *clasp = obj->getClass();
+    if (clasp->trace)
+        clasp->trace(trc, obj);
+
+    if (!obj->isNative())
+        return;
+
+    if (!obj->hasEmptyElements())
+        markSlots(trc, obj->getDenseElements(), obj->getDenseInitializedLength());
+
+    HeapSlot *fixedStart, *fixedEnd, *dynStart, *dynEnd;
+    obj->getSlotRange(0, obj->slotSpan(), &fixedStart, &fixedEnd, &dynStart, &dynEnd);
+    markSlots(trc, fixedStart, fixedEnd);
+    markSlots(trc, dynStart, dynEnd);
+}
+
+JS_ALWAYS_INLINE void
+js::Nursery::markSlots(MinorCollectionTracer *trc, HeapSlot *vp, uint32_t nslots)
+{
+    markSlots(trc, vp, vp + nslots);
+}
+
+JS_ALWAYS_INLINE void
+js::Nursery::markSlots(MinorCollectionTracer *trc, HeapSlot *vp, HeapSlot *end)
+{
+    for (; vp != end; ++vp)
+        markSlot(trc, vp);
+}
+
+JS_ALWAYS_INLINE void
+js::Nursery::markSlot(MinorCollectionTracer *trc, HeapSlot *slotp)
+{
+    if (!slotp->isObject())
+        return;
+
+    JSObject *obj = &slotp->toObject();
+    if (!isInside(obj))
+        return;
+
+    if (getForwardedPointer(&obj)) {
+        slotp->unsafeGet()->setObject(*obj);
+        return;
+    }
+
+    JSObject *tenured = static_cast<JSObject*>(moveToTenured(trc, obj));
+    slotp->unsafeGet()->setObject(*tenured);
+}
+
 void *
 js::Nursery::moveToTenured(MinorCollectionTracer *trc, JSObject *src)
 {
     Zone *zone = src->zone();
     AllocKind dstKind = GetObjectAllocKindForCopy(trc->runtime, src);
     JSObject *dst = static_cast<JSObject *>(allocateFromTenured(zone, dstKind));
     if (!dst)
         MOZ_CRASH();
@@ -586,26 +646,23 @@ js::Nursery::collect(JSRuntime *rt, JS::
     rt->newObjectCache.clearNurseryObjects(rt);
 
     /*
      * Most of the work is done here. This loop iterates over objects that have
      * been moved to the major heap. If these objects have any outgoing pointers
      * to the nursery, then those nursery objects get moved as well, until no
      * objects are left to move. That is, we iterate to a fixed point.
      */
-    for (RelocationOverlay *p = trc.head; p; p = p->next()) {
-        JSObject *obj = static_cast<JSObject*>(p->forwardingAddress());
-        JS_TraceChildren(&trc, obj, MapAllocToTraceKind(obj->tenuredGetAllocKind()));
-    }
+    collectToFixedPoint(&trc);
 
     /* Resize the nursery. */
     double promotionRate = trc.tenuredSize / double(allocationEnd() - start());
-    if (promotionRate > 0.5)
+    if (promotionRate > 0.05)
         growAllocableSpace();
-    else if (promotionRate < 0.1)
+    else if (promotionRate < 0.01)
         shrinkAllocableSpace();
 
     /* Sweep. */
     sweep(rt->defaultFreeOp());
     rt->gcStoreBuffer.clear();
 
     /*
      * We ignore gcMaxBytes when allocating for minor collection. However, if we
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -29,17 +29,17 @@ class CodeGenerator;
 class MacroAssembler;
 class ICStubCompiler;
 class BaselineCompiler;
 }
 
 class Nursery
 {
   public:
-    const static int NumNurseryChunks = 8;
+    const static int NumNurseryChunks = 16;
     const static int LastNurseryChunk = NumNurseryChunks - 1;
     const static size_t Alignment = gc::ChunkSize;
     const static size_t NurserySize = gc::ChunkSize * NumNurseryChunks;
 
     explicit Nursery(JSRuntime *rt)
       : runtime_(rt),
         position_(0),
         currentEnd_(0),
@@ -201,16 +201,21 @@ class Nursery
 
     /* Allocates a new GC thing from the tenured generation during minor GC. */
     void *allocateFromTenured(Zone *zone, gc::AllocKind thingKind);
 
     /*
      * Move the object at |src| in the Nursery to an already-allocated cell
      * |dst| in Tenured.
      */
+    void collectToFixedPoint(gc::MinorCollectionTracer *trc);
+    JS_ALWAYS_INLINE void traceObject(gc::MinorCollectionTracer *trc, JSObject *src);
+    JS_ALWAYS_INLINE void markSlots(gc::MinorCollectionTracer *trc, HeapSlot *vp, uint32_t nslots);
+    JS_ALWAYS_INLINE void markSlots(gc::MinorCollectionTracer *trc, HeapSlot *vp, HeapSlot *end);
+    JS_ALWAYS_INLINE void markSlot(gc::MinorCollectionTracer *trc, HeapSlot *slotp);
     void *moveToTenured(gc::MinorCollectionTracer *trc, JSObject *src);
     size_t moveObjectToTenured(JSObject *dst, JSObject *src, gc::AllocKind dstKind);
     size_t moveElementsToTenured(JSObject *dst, JSObject *src, gc::AllocKind dstKind);
     size_t moveSlotsToTenured(JSObject *dst, JSObject *src, gc::AllocKind dstKind);
 
     /* Handle relocation of slots/elements pointers stored in Ion frames. */
     void setSlotsForwardingPointer(HeapSlot *oldSlots, HeapSlot *newSlots, uint32_t nslots);
     void setElementsForwardingPointer(ObjectElements *oldHeader, ObjectElements *newHeader,
--- a/js/src/ion/AsmJS.cpp
+++ b/js/src/ion/AsmJS.cpp
@@ -4659,19 +4659,20 @@ ParseFunction(ModuleCompiler &m, ParseNo
         return false;  // This will throw a SyntaxError, no need to m.fail.
 
     RootedPropertyName name(m.cx(), tokenStream.currentToken().name());
 
     ParseNode *fn = m.parser().handler.newFunctionDefinition();
     if (!fn)
         return false;
 
+    // This flows into FunctionBox, so must be tenured.
     RootedFunction fun(m.cx(), NewFunction(m.cx(), NullPtr(), NULL, 0, JSFunction::INTERPRETED,
                                            m.cx()->global(), name, JSFunction::FinalizeKind,
-                                           GenericObject));
+                                           TenuredObject));
     if (!fun)
         return false;
 
     AsmJSParseContext *outerpc = m.parser().pc;
 
     Directives directives(outerpc);
     FunctionBox *funbox = m.parser().newFunctionBox(fn, fun, outerpc, directives);
     if (!funbox)
@@ -6348,17 +6349,17 @@ Warn(JSContext *cx, int code, const char
 extern bool
 EnsureAsmJSSignalHandlersInstalled(JSRuntime *rt);
 
 bool
 js::CompileAsmJS(JSContext *cx, AsmJSParser &parser, ParseNode *stmtList, bool *validated)
 {
     *validated = false;
 
-    if (!JSC::MacroAssembler().supportsFloatingPoint())
+    if (!JSC::MacroAssembler::supportsFloatingPoint())
         return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by lack of floating point support");
 
     if (cx->runtime()->gcSystemPageSize != AsmJSPageSize)
         return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by non 4KiB system page size");
 
     if (!cx->hasOption(JSOPTION_ASMJS))
         return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by javascript.options.asmjs in about:config");
 
@@ -6407,17 +6408,17 @@ js::CompileAsmJS(JSContext *cx, AsmJSPar
     return Warn(cx, JSMSG_USE_ASM_TYPE_OK, compilationTimeReport);
 }
 
 JSBool
 js::IsAsmJSCompilationAvailable(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    bool available = JSC::MacroAssembler().supportsFloatingPoint() &&
+    bool available = JSC::MacroAssembler::supportsFloatingPoint() &&
                      !cx->compartment()->debugMode() &&
                      cx->hasOption(JSOPTION_ASMJS);
 
     args.rval().set(BooleanValue(available));
     return true;
 }
 
 static bool
--- a/js/src/ion/AsmJSLink.cpp
+++ b/js/src/ion/AsmJSLink.cpp
@@ -256,18 +256,22 @@ DynamicallyLinkModule(JSContext *cx, Cal
 AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module)
   : cx_(cx),
     module_(module),
     errorRejoinSP_(NULL),
     profiler_(NULL),
     resumePC_(NULL)
 {
     if (cx->runtime()->spsProfiler.enabled()) {
+        // Use a profiler string that matches jsMatch regex in
+        // browser/devtools/profiler/cleopatra/js/parserWorker.js.
+        // (For now use a single static string to avoid further slowing down
+        // calls into asm.js.)
         profiler_ = &cx->runtime()->spsProfiler;
-        profiler_->enterNative("asm.js code", this);
+        profiler_->enterNative("asm.js code :0", this);
     }
 
     prev_ = cx_->runtime()->mainThread.asmJSActivationStack_;
 
     JSRuntime::AutoLockForOperationCallback lock(cx_->runtime());
     cx_->runtime()->mainThread.asmJSActivationStack_ = this;
 
     (void) errorRejoinSP_;  // squelch GCC warning
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -254,16 +254,21 @@ IonBuilder::canInlineTarget(JSFunction *
         return false;
     }
 
     if (target->getParent() != &script()->global()) {
         IonSpew(IonSpew_Inlining, "Cannot inline due to scope mismatch");
         return false;
     }
 
+    if (!target->hasScript()) {
+        IonSpew(IonSpew_Inlining, "Cannot inline due to lack of Non-Lazy script");
+        return false;
+    }
+
     RootedScript inlineScript(cx, target->nonLazyScript());
     ExecutionMode executionMode = info().executionMode();
     if (!CanIonCompile(inlineScript, executionMode)) {
         IonSpew(IonSpew_Inlining, "%s:%d Cannot inline due to disable Ion compilation",
                                   inlineScript->filename(), inlineScript->lineno);
         return false;
     }
 
@@ -6321,16 +6326,25 @@ IonBuilder::jsop_getelem()
 
     current->popn(2);
 
     MInstruction *ins;
 
     bool cacheable = obj->mightBeType(MIRType_Object) && !obj->mightBeType(MIRType_String) &&
         (index->mightBeType(MIRType_Int32) || index->mightBeType(MIRType_String));
 
+    bool nonNativeGetElement =
+        script()->analysis()->getCode(pc).nonNativeGetElement ||
+        inspector->hasSeenNonNativeGetElement(pc);
+
+    // Turn off cacheing if the element is int32 and we've seen non-native objects as the target
+    // of this getelem.
+    if (index->mightBeType(MIRType_Int32) && nonNativeGetElement)
+        cacheable = false;
+
     types::StackTypeSet *types = types::TypeScript::BytecodeTypes(script(), pc);
     bool barrier = PropertyReadNeedsTypeBarrier(cx, obj, NULL, types);
 
     // Always add a barrier if the index might be a string, so that the cache
     // can attach stubs for particular properties.
     if (index->mightBeType(MIRType_String))
         barrier = true;
 
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -5037,17 +5037,19 @@ class MLoadTypedArrayElementHole
         return getOperand(1);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::TypedArrayElement);
     }
 };
 
 // Load a value fallibly or infallibly from a statically known typed array.
-class MLoadTypedArrayElementStatic : public MUnaryInstruction
+class MLoadTypedArrayElementStatic
+  : public MUnaryInstruction,
+    public IntPolicy<0>
 {
     MLoadTypedArrayElementStatic(TypedArrayObject *typedArray, MDefinition *ptr)
       : MUnaryInstruction(ptr), typedArray_(typedArray), fallible_(true)
     {
         int type = typedArray_->type();
         if (type == TypedArrayObject::TYPE_FLOAT32 || type == TypedArrayObject::TYPE_FLOAT64)
             setResultType(MIRType_Double);
         else
@@ -5076,16 +5078,20 @@ class MLoadTypedArrayElementStatic : pub
     bool fallible() const {
         return fallible_;
     }
 
     void setInfallible() {
         fallible_ = false;
     }
 
+    TypePolicy *typePolicy() {
+        return this;
+    }
+
     void computeRange();
     bool truncate();
 };
 
 class MStoreTypedArrayElement
   : public MTernaryInstruction,
     public StoreTypedArrayPolicy
 {
--- a/js/src/ion/TypePolicy.cpp
+++ b/js/src/ion/TypePolicy.cpp
@@ -319,16 +319,19 @@ template bool StringPolicy<2>::staticAdj
 template <unsigned Op>
 bool
 IntPolicy<Op>::staticAdjustInputs(MInstruction *def)
 {
     MDefinition *in = def->getOperand(Op);
     if (in->type() == MIRType_Int32)
         return true;
 
+    if (in->type() != MIRType_Value)
+        in = boxAt(def, in);
+
     MUnbox *replace = MUnbox::New(in, MIRType_Int32, MUnbox::Fallible);
     def->block()->insertBefore(def, replace);
     def->replaceOperand(Op, replace);
     return true;
 }
 
 template bool IntPolicy<0>::staticAdjustInputs(MInstruction *def);
 template bool IntPolicy<1>::staticAdjustInputs(MInstruction *def);
@@ -557,19 +560,19 @@ StoreTypedArrayHolePolicy::adjustInputs(
 
     return adjustValueInput(ins, store->arrayType(), store->value(), 3);
 }
 
 bool
 StoreTypedArrayElementStaticPolicy::adjustInputs(MInstruction *ins)
 {
     MStoreTypedArrayElementStatic *store = ins->toStoreTypedArrayElementStatic();
-    JS_ASSERT(store->ptr()->type() == MIRType_Int32);
 
-    return adjustValueInput(ins, store->viewType(), store->value(), 1);
+    return IntPolicy<0>::staticAdjustInputs(ins) &&
+        adjustValueInput(ins, store->viewType(), store->value(), 1);
 }
 
 bool
 ClampPolicy::adjustInputs(MInstruction *ins)
 {
     MDefinition *in = ins->toClampToUint8()->input();
 
     switch (in->type()) {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug893732.js
@@ -0,0 +1,14 @@
+
+var arr = new Int8Array(100);
+function f(a) {
+  for(var i=0; i<30; i++) {
+    x = a[85.3];
+  }
+}
+f(arr);
+
+var buf = serialize(new Date(NaN));
+var n =  -(8.64e15 + 1);
+var nbuf = serialize(n);
+for (var j = 0; j < 8; j++)
+  buf[j + (0.00000000123)] = nbuf[j];
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -99,16 +99,18 @@ class Bytecode
     /* Call whose result should be monitored. */
     bool monitoredTypesReturn : 1;
 
     /*
      * Dynamically observed state about the execution of this opcode. These are
      * hints about the script for use during compilation.
      */
     bool arrayWriteHole: 1;     /* SETELEM which has written to an array hole. */
+    bool getStringElement:1;    /* GETELEM which has accessed string properties. */
+    bool nonNativeGetElement:1; /* GETELEM on a non-native, non-array object. */
     bool accessGetter: 1;       /* Property read on a shape with a getter hook. */
 
     /* Stack depth before this opcode. */
     uint32_t stackDepth;
 
   private:
 
     /* If this is a JSOP_LOOPHEAD or JSOP_LOOPENTRY, information about the loop. */
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -649,17 +649,17 @@ JS_FRIEND_API(bool) JS::isGCEnabled() { 
 #endif
 
 static const JSSecurityCallbacks NullSecurityCallbacks = { };
 
 static bool
 JitSupportsFloatingPoint()
 {
 #if defined(JS_ION)
-    if (!JSC::MacroAssembler().supportsFloatingPoint())
+    if (!JSC::MacroAssembler::supportsFloatingPoint())
         return false;
 
 #if defined(JS_ION) && WTF_ARM_ARCH_VERSION == 6
     if (!js::ion::hasVFP())
         return false;
 #endif
 
     return true;
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -307,17 +307,18 @@ AtomizeAndCopyChars(ExclusiveContext *cx
     JSFlatString *flat = js_NewStringCopyN<allowGC>(cx, tbchars, length);
     if (!flat)
         return NULL;
 
     JSAtom *atom = flat->morphAtomizedStringIntoAtom();
 
     if (!atoms.relookupOrAdd(p, AtomHasher::Lookup(tbchars, length),
                              AtomStateEntry(atom, bool(ib)))) {
-        js_ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
+        if (allowGC)
+            js_ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
         return NULL;
     }
 
     return atom;
 }
 
 template <AllowGC allowGC>
 JSAtom *
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -725,16 +725,17 @@ DumpHeapVisitRoot(JSTracer *trc, void **
 }
 
 void
 js::DumpHeapComplete(JSRuntime *rt, FILE *fp)
 {
     JSDumpHeapTracer dtrc(fp);
 
     JS_TracerInit(&dtrc, rt, DumpHeapVisitRoot);
+    dtrc.eagerlyTraceWeakMaps = TraceWeakMapKeysValues;
     TraceRuntime(&dtrc);
 
     fprintf(dtrc.output, "==========\n");
 
     JS_TracerInit(&dtrc, rt, DumpHeapVisitChild);
     IterateZonesCompartmentsArenasCells(rt, &dtrc,
                                         DumpHeapVisitZone,
                                         DumpHeapVisitCompartment,
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -4854,16 +4854,20 @@ js::ReleaseAllJITCode(FreeOp *fop)
 
             /*
              * Discard baseline script if it's not marked as active. Note that
              * this also resets the active flag.
              */
             ion::FinishDiscardBaselineScript(fop, script);
         }
     }
+
+    /* Sweep now invalidated compiler outputs from each compartment. */
+    for (CompartmentsIter comp(fop->runtime()); !comp.done(); comp.next())
+        comp->types.sweepCompilerOutputs(fop, false);
 #endif
 }
 
 /*
  * There are three possible PCCount profiling states:
  *
  * 1. None: Neither scripts nor the runtime have count information.
  * 2. Profile: Active scripts have count information, the runtime does not.
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -349,28 +349,56 @@ ToIdOperation(JSContext *cx, HandleScrip
     return true;
 }
 
 static JS_ALWAYS_INLINE bool
 GetObjectElementOperation(JSContext *cx, JSOp op, JSObject *objArg, bool wasObject,
                           HandleValue rref, MutableHandleValue res)
 {
     do {
+        // Don't call GetPcScript (needed for analysis) from inside Ion since it's expensive.
+        bool analyze = cx->currentlyRunningInInterpreter();
+
         uint32_t index;
         if (IsDefinitelyIndex(rref, &index)) {
+            if (analyze && !objArg->isNative() && !objArg->is<TypedArrayObject>()) {
+                JSScript *script = NULL;
+                jsbytecode *pc = NULL;
+                types::TypeScript::GetPcScript(cx, &script, &pc);
+
+                if (script->hasAnalysis())
+                    script->analysis()->getCode(pc).nonNativeGetElement = true;
+            }
+
             if (JSObject::getElementNoGC(cx, objArg, objArg, index, res.address()))
                 break;
 
             RootedObject obj(cx, objArg);
             if (!JSObject::getElement(cx, obj, obj, index, res))
                 return false;
             objArg = obj;
             break;
         }
 
+        if (analyze) {
+            JSScript *script = NULL;
+            jsbytecode *pc = NULL;
+            types::TypeScript::GetPcScript(cx, &script, &pc);
+
+            if (script->hasAnalysis()) {
+                script->analysis()->getCode(pc).getStringElement = true;
+
+                if (!objArg->is<ArrayObject>() && !objArg->isNative() &&
+                    !objArg->is<TypedArrayObject>())
+                {
+                    script->analysis()->getCode(pc).nonNativeGetElement = true;
+                }
+            }
+        }
+
         if (ValueMightBeSpecial(rref)) {
             RootedObject obj(cx, objArg);
             Rooted<SpecialId> special(cx);
             res.set(rref);
             if (ValueIsSpecial(obj, res, &special, cx)) {
                 if (!JSObject::getSpecial(cx, obj, obj, special, res))
                     return false;
                 objArg = obj;
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1248,17 +1248,17 @@ SetObjectElementOperation(JSContext *cx,
     if (obj->isNative() && JSID_IS_INT(id)) {
         uint32_t length = obj->getDenseInitializedLength();
         int32_t i = JSID_TO_INT(id);
         if ((uint32_t)i >= length) {
             // In an Ion activation, GetPcScript won't work.  For non-baseline activations,
             // that's ok, because optimized ion doesn't generate analysis info.  However,
             // baseline must generate this information, so it passes the script and pc in
             // as arguments.
-            if (script) {
+            if (script || cx->currentlyRunningInInterpreter()) {
                 JS_ASSERT(!!script == !!pc);
                 if (!script)
                     types::TypeScript::GetPcScript(cx, script.address(), &pc);
 
                 if (script->hasAnalysis())
                     script->analysis()->getCode(pc).arrayWriteHole = true;
             }
         }
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -1300,16 +1300,18 @@ class ObjectImpl : public gc::Cell
 
     bool setFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag,
                  GenerateShape generateShape = GENERATE_NONE);
     bool clearFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag);
 
     bool toDictionaryMode(ExclusiveContext *cx);
 
   private:
+    friend class Nursery;
+
     /*
      * Get internal pointers to the range of values starting at start and
      * running for length.
      */
     void getSlotRangeUnchecked(uint32_t start, uint32_t length,
                                HeapSlot **fixedStart, HeapSlot **fixedEnd,
                                HeapSlot **slotsStart, HeapSlot **slotsEnd)
     {
--- a/js/src/vm/SPSProfiler.cpp
+++ b/js/src/vm/SPSProfiler.cpp
@@ -200,16 +200,18 @@ SPSProfiler::pop()
  * Serializes the script/function pair into a "descriptive string" which is
  * allowed to fail. This function cannot trigger a GC because it could finalize
  * some scripts, resize the hash table of profile strings, and invalidate the
  * AddPtr held while invoking allocProfileString.
  */
 const char*
 SPSProfiler::allocProfileString(JSContext *cx, JSScript *script, JSFunction *maybeFun)
 {
+    // Note: this profiler string is regexp-matched by
+    // browser/devtools/profiler/cleopatra/js/parserWorker.js.
     DebugOnly<uint64_t> gcBefore = cx->runtime()->gcNumber;
     StringBuffer buf(cx);
     bool hasAtom = maybeFun != NULL && maybeFun->displayAtom() != NULL;
     if (hasAtom) {
         if (!buf.append(maybeFun->displayAtom()))
             return NULL;
         if (!buf.append(" ("))
             return NULL;
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -2036,16 +2036,19 @@ ContainerState::ProcessDisplayItems(cons
   if (aFlags & NO_COMPONENT_ALPHA) {
     if (!ChooseActiveScrolledRoot(aList, &lastActiveScrolledRoot)) {
       lastActiveScrolledRoot = mContainerReferenceFrame;
     }
 
     topLeft = lastActiveScrolledRoot->GetOffsetToCrossDoc(mContainerReferenceFrame);
   }
 
+  int32_t maxLayers = nsDisplayItem::MaxActiveLayers();
+  int layerCount = 0;
+
   for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) {
     NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item),
       "items in a container layer should all have the same app units per dev pixel");
 
     nsIntRect itemVisibleRect =
       ScaleToOutsidePixels(item->GetVisibleRect(), false);
     bool snap;
     nsRect itemContent = item->GetBounds(mBuilder, &snap);
@@ -2078,23 +2081,29 @@ ContainerState::ProcessDisplayItems(cons
       forceInactive = false;
       isFixed = mBuilder->IsFixedItem(item, &activeScrolledRoot);
       if (activeScrolledRoot != lastActiveScrolledRoot) {
         lastActiveScrolledRoot = activeScrolledRoot;
         topLeft = activeScrolledRoot->GetOffsetToCrossDoc(mContainerReferenceFrame);
       }
     }
 
+    if (maxLayers != -1 && layerCount >= maxLayers) {
+      forceInactive = true;
+    }
+
     // Assign the item to a layer
     if (layerState == LAYER_ACTIVE_FORCE ||
         (layerState == LAYER_INACTIVE && !mManager->IsWidgetLayerManager()) ||
         (!forceInactive &&
          (layerState == LAYER_ACTIVE_EMPTY ||
           layerState == LAYER_ACTIVE))) {
 
+      layerCount++;
+
       // LAYER_ACTIVE_EMPTY means the layer is created just for its metadata.
       // We should never see an empty layer with any visible content!
       NS_ASSERTION(layerState != LAYER_ACTIVE_EMPTY ||
                    itemVisibleRect.IsEmpty(),
                    "State is LAYER_ACTIVE_EMPTY but visible rect is not.");
 
       // As long as the new layer isn't going to be a ThebesLayer, 
       // InvalidateForLayerChange doesn't need the new layer pointer.
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1492,16 +1492,30 @@ nsDisplayItem::ForceActiveLayers()
   if (!sForceCached) {
     Preferences::AddBoolVarCache(&sForce, "layers.force-active", false);
     sForceCached = true;
   }
 
   return sForce;
 }
 
+/* static */ int32_t
+nsDisplayItem::MaxActiveLayers()
+{
+  static int32_t sMaxLayers = false;
+  static bool sMaxLayersCached = false;
+
+  if (!sMaxLayersCached) {
+    Preferences::AddIntVarCache(&sMaxLayers, "layers.max-active", -1);
+    sMaxLayersCached = true;
+  }
+
+  return sMaxLayersCached;
+}
+
 bool
 nsDisplayItem::RecomputeVisibility(nsDisplayListBuilder* aBuilder,
                                    nsRegion* aVisibleRegion) {
   nsRect bounds = GetClippedBounds(aBuilder);
 
   nsRegion itemVisible;
   if (ForceVisiblityForFixedItem(aBuilder, this)) {
     itemVisible.And(GetDisplayPortBounds(aBuilder, this), bounds);
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -999,16 +999,22 @@ public:
 
   /**
    * Returns true if all layers that can be active should be forced to be
    * active. Requires setting the pref layers.force-active=true.
    */
   static bool ForceActiveLayers();
 
   /**
+   * Returns the maximum number of layers that should be created
+   * or -1 for no limit. Requires setting the pref layers.max-acitve.
+   */
+  static int32_t MaxActiveLayers();
+
+  /**
    * @return LAYER_NONE if BuildLayer will return null. In this case
    * there is no layer for the item, and Paint should be called instead
    * to paint the content using Thebes.
    * Return LAYER_INACTIVE if there is a layer --- BuildLayer will
    * not return null (unless there's an error) --- but the layer contents
    * are not changing frequently. In this case it makes sense to composite
    * the layer into a ThebesLayer with other content, so we don't have to
    * recomposite it every time we paint.
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -21,16 +21,18 @@
 #include "nsContentUtils.h"
 #include "nsFrameLoader.h"
 #include "nsIObserver.h"
 #include "nsSubDocumentFrame.h"
 #include "nsView.h"
 #include "nsViewportFrame.h"
 #include "RenderFrameParent.h"
 #include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/CompositorChild.h"
+#include "ClientLayerManager.h"
 
 typedef nsContentView::ViewConfig ViewConfig;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
 namespace mozilla {
 namespace layout {
 
@@ -597,26 +599,30 @@ RenderFrameParent::RenderFrameParent(nsF
 {
   mContentViews[FrameMetrics::ROOT_SCROLL_ID] =
     new nsContentView(aFrameLoader, FrameMetrics::ROOT_SCROLL_ID);
 
   *aId = 0;
 
   nsRefPtr<LayerManager> lm = GetFrom(mFrameLoader);
   // Perhaps the document containing this frame currently has no presentation?
-  if (lm && lm->AsLayerManagerComposite()) {
+  if (lm && lm->GetBackendType() == LAYERS_CLIENT) {
     *aTextureFactoryIdentifier = lm->GetTextureFactoryIdentifier();
   } else {
     *aTextureFactoryIdentifier = TextureFactoryIdentifier();
   }
 
   if (CompositorParent::CompositorLoop()) {
     // Our remote frame will push layers updates to the compositor,
     // and we'll keep an indirect reference to that tree.
     *aId = mLayersId = CompositorParent::AllocateLayerTreeId();
+    if (lm && lm->GetBackendType() == LAYERS_CLIENT) {
+      ClientLayerManager *clientManager = static_cast<ClientLayerManager*>(lm.get());
+      clientManager->GetRemoteRenderer()->SendNotifyChildCreated(mLayersId);
+    }
     if (aScrollingBehavior == ASYNC_PAN_ZOOM) {
       mContentController = new RemoteContentController(this);
       mPanZoomController = new AsyncPanZoomController(
         mContentController, AsyncPanZoomController::USE_GESTURE_DETECTOR);
       CompositorParent::SetPanZoomControllerForLayerTree(mLayersId,
                                                          mPanZoomController);
     }
   }
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1123,17 +1123,17 @@ skip-if(B2G) == 430412-1.html 430412-1-r
 == 430813-1.html 430813-1-ref.html
 == 430813-2.html 430813-2-ref.html
 == 430813-3.html 430813-3-ref.html
 == 431341-1.html 431341-1-ref.html
 == 431341-2.html 431341-2-ref.html
 == 431520-1.html 431520-1-ref.html
 == 431948-1.html 431948-1-ref.html
 == 433640-1.html 433640-1-ref.html
-fails-if(Android) == 433700.html 433700-ref.html
+== 433700.html 433700-ref.html
 == 436356-1.html 436356-1-ref.html
 == 436356-2.html 436356-2-ref.html
 == 438537-1.html 438537-1-ref.html
 == 438981-1.xhtml about:blank
 == 438987-1.html 438987-1-ref.html
 == 438987-2a.html 438987-2-ref.html
 == 438987-2b.html 438987-2-ref.html
 == 438987-2c.html 438987-2-ref.html
@@ -1485,17 +1485,17 @@ random-if(!haveTestPlugin) == 546071-1.h
 == 550325-3.html 550325-1-ref.html
 == 550716-1.html 550716-1-ref.html
 skip-if(B2G) fuzzy-if(Android&&AndroidVersion>=15,12,300) == 551463-1.html 551463-1-ref.html
 == 551699-1.html 551699-1-ref.html
 == 552334-1.html 552334-1-ref.html
 # 553571 depends on MS Indic shaping behavior and Win7 font support;
 # not expected to be reliable on XP or non-Windows platforms
 random-if(!winWidget) random-if(/^Windows\x20NT\x205/.test(http.oscpu)) != 553571-1.html 553571-1-notref.html # expect dotted circle in test, not in ref
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,91) random-if(d2d) == 555388-1.html 555388-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,91) random-if(d2d) skip-if(azureSkiaGL) == 555388-1.html 555388-1-ref.html
 == 556661-1.html 556661-1-ref.html
 skip-if(B2G) fails-if(Android) == 557087-1.html 557087-ref.html
 skip-if(B2G) fails-if(Android) == 557087-2.html 557087-ref.html
 == 557736-1.html 557736-1-ref.html
 != 558011-1.xul 558011-1-ref.xul
 == 559284-1.html 559284-1-ref.html
 skip-if(B2G) fails-if(Android) == 560455-1.xul 560455-1-ref.xul
 == 561981-1.html 561981-1-ref.html
@@ -1671,17 +1671,17 @@ fails-if(layersGPUAccelerated&&cocoaWidg
 != 656875.html about:blank
 == 658952.html 658952-ref.html
 == 660682-1.html 660682-1-ref.html
 == 664127-1.xul 664127-1-ref.xul
 skip-if(B2G) == 665597-1.html 665597-1-ref.html
 skip-if(B2G) == 665597-2.html 665597-2-ref.html
 == 668319-1.xul about:blank
 != 669015-1.xul 669015-1-notref.xul
-== 670442-1.html 670442-1-ref.html
+fuzzy-if(azureSkiaGL,14,2000) == 670442-1.html 670442-1-ref.html
 == 670467-1.html 670467-1-ref.html
 == 670467-2.html 670467-2-ref.html
 == 690164-1.html 690164-1-ref.html
 == 690643-1.html 690643-1-ref.html
 != 691087-1.html 691087-1-ref.html
 == 691571-1.html 691571-1-ref.html
 == 696307-1.html 696307-1-ref.html
 == 696739-1.html 696739-1-ref.html
--- a/layout/reftests/canvas/reftest.list
+++ b/layout/reftests/canvas/reftest.list
@@ -32,17 +32,17 @@ skip-if(B2G) asserts-if(cocoaWidget,0-2)
 != text-big-stroke.html text-big-fill.html
 
 == text-context-state-test.html text-context-state-ref.html
 == text-font-inherit.html text-big-fill.html
 == text-space-replace-test.html text-space-replace-ref.html
 
 == text-no-frame-test.html text-no-frame-ref.html
 == text-no-frame-2-test.html text-not-in-doc-ref.html
-== text-not-in-doc-test.html text-not-in-doc-ref.html
+fuzzy-if(azureSkiaGL,10,400) == text-not-in-doc-test.html text-not-in-doc-ref.html
 
 == text-bidi-ltr-test.html text-bidi-ltr-ref.html
 != text-bidi-ltr-test.html text-bidi-ltr-notref.html # for bug 698185
 == text-bidi-rtl-test.html text-bidi-rtl-ref.html
 
 != text-font-lang.html text-font-lang-notref.html
 
 == text-measure.html text-measure-ref.html
@@ -57,20 +57,20 @@ random-if(!d2d) != text-subpixel-1.html 
 ## (Marking "random" rather than "fails" because it would pass for people
 ## if they have an Emoji font installed when running the tests.)
 ## WAS: random-if(OSX==10.7) != text-emoji.html text-emoji-notref.html
 random-if(!cocoaWidget||OSX==10.6||OSX==10.7) != text-emoji.html text-emoji-notref.html
 
 # azure quartz uses CGDrawLinearGradient instead of DrawShading
 # so we have less control over degenerate behaviour as tested by this
 # test
-fails-if(azureQuartz) == linear-gradient-1a.html linear-gradient-1-ref.html
+fuzzy-if(azureSkiaGL,256,601) fails-if(azureQuartz) == linear-gradient-1a.html linear-gradient-1-ref.html
 
 # this passes with cairo on 10.7 and 10.8 but not with azure for reasons unknown
-fails-if(OSX==10.6||(azureQuartz&&(OSX==10.7||OSX==10.8))) == linear-gradient-1b.html linear-gradient-1-ref.html
+fails-if(azureSkiaGL||OSX==10.6||(azureQuartz&&(OSX==10.7||OSX==10.8))) == linear-gradient-1b.html linear-gradient-1-ref.html
 
 == zero-dimensions.html zero-dimensions-ref.html
 
 == evenodd-fill-sanity.html data:text/html,<body>Pass
 != evenodd-fill-1.html nonzero-fill-1.html
 == evenodd-fill-1.html evenodd-fill-ref.html
 == evenodd-fill-2.html evenodd-fill-ref.html
 == evenodd-fill-3.html nonzero-fill-2.html
--- a/layout/reftests/css-gradients/reftest.list
+++ b/layout/reftests/css-gradients/reftest.list
@@ -1,14 +1,14 @@
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) == linear-1a.html linear-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) == linear-1b.html linear-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) == linear-keywords-1a.html linear-keywords-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) == linear-keywords-1b.html linear-keywords-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fuzzy-if(azureSkiaGL,3,89700) == linear-1a.html linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fuzzy-if(azureSkiaGL,3,89700) == linear-1b.html linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fuzzy-if(azureSkiaGL,2,89997) == linear-keywords-1a.html linear-keywords-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fuzzy-if(azureSkiaGL,2,89997) == linear-keywords-1b.html linear-keywords-1-ref.html
 fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) == linear-percent.html linear-percent-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,92400) == linear-mix.html linear-mix-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,92400) fuzzy-if(azureSkiaGL,2,143400) == linear-mix.html linear-mix-ref.html
 == linear-diagonal-1a.html linear-diagonal-1-ref.html
 == linear-diagonal-1b.html linear-diagonal-1-ref.html
 == linear-diagonal-1c.html linear-diagonal-1-ref.html
 == linear-diagonal-2a.html linear-diagonal-2-ref.html
 == linear-diagonal-2b.html linear-diagonal-2-ref.html
 == linear-diagonal-2c.html linear-diagonal-2-ref.html
 == linear-diagonal-3a.html linear-diagonal-3-ref.html
 == linear-diagonal-3b.html linear-diagonal-3-ref.html
@@ -33,37 +33,37 @@ fails-if(d2d) == linear-repeat-1g.html l
 == linear-rotated-1.html linear-rotated-1-ref.html
 == linear-size-1a.html linear-size-1-ref.html
 == linear-stops-1a.html linear-stops-1-ref.html
 == linear-stops-1b.html linear-stops-1-ref.html
 == linear-stops-1c.html linear-stops-1-ref.html
 == linear-stops-1d.html linear-stops-1-ref.html
 == linear-stops-1e.html linear-stops-1-ref.html
 == linear-stops-1f.html linear-stops-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) == linear-vertical-1a.html linear-vertical-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) == linear-vertical-1b.html linear-vertical-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) == linear-vertical-1c.html linear-vertical-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) == linear-vertical-1d.html linear-vertical-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) == linear-vertical-1e.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) fuzzy-if(azureSkiaGL,2,89700) == linear-vertical-1a.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) fuzzy-if(azureSkiaGL,2,89700) == linear-vertical-1b.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) fuzzy-if(azureSkiaGL,2,89700) == linear-vertical-1c.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) fuzzy-if(azureSkiaGL,2,89700) == linear-vertical-1d.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,88500) fuzzy-if(azureSkiaGL,2,89700) == linear-vertical-1e.html linear-vertical-1-ref.html
 == linear-vertical-subpixel-1.html linear-vertical-subpixel-1-ref.html
 == linear-viewport.html linear-viewport-ref.html
 == linear-zero-length-1a.html linear-zero-length-1-ref.html
 == linear-zero-length-1b.html linear-zero-length-1-ref.html
 == linear-zero-length-1c.html linear-zero-length-1-ref.html
 == nostops.html about:blank
 == onestop.html about:blank
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,51018) random-if(d2d) == radial-1a.html radial-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,51018) random-if(d2d) == radial-1b.html radial-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,51018) random-if(d2d) == radial-1c.html radial-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) == radial-2a.html radial-2-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) == radial-2b.html radial-2-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) == radial-2c.html radial-2-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) == radial-2d.html radial-2-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) == radial-2e.html radial-2-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) == radial-2f.html radial-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,51018) fuzzy-if(azureSkiaGL,2,88024) random-if(d2d) == radial-1a.html radial-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,51018) fuzzy-if(azureSkiaGL,2,88024) random-if(d2d) == radial-1b.html radial-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,51018) fuzzy-if(azureSkiaGL,2,88024) random-if(d2d) == radial-1c.html radial-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) fuzzy-if(azureSkiaGL,2,90000) == radial-2a.html radial-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) fuzzy-if(azureSkiaGL,2,90000) == radial-2b.html radial-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) fuzzy-if(azureSkiaGL,2,90000) == radial-2c.html radial-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) fuzzy-if(azureSkiaGL,2,90000) == radial-2d.html radial-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) fuzzy-if(azureSkiaGL,2,90000) == radial-2e.html radial-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,7860) fuzzy-if(azureSkiaGL,2,90000) == radial-2f.html radial-2-ref.html
 == radial-position-1a.html radial-position-1-ref.html
 == radial-position-1b.html radial-position-1-ref.html
 == radial-shape-closest-corner-1a.html radial-shape-closest-corner-1-ref.html
 == radial-shape-closest-corner-1b.html radial-shape-closest-corner-1-ref.html
 == radial-shape-closest-corner-1c.html radial-shape-closest-corner-1-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1a.html radial-shape-closest-side-1-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1b.html radial-shape-closest-side-1-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1c.html radial-shape-closest-side-1-ref.html
@@ -100,33 +100,33 @@ fuzzy-if(d2d,124,2612) == repeating-radi
 == twostops-1b.html twostops-1-ref.html
 fails-if(OSX==10.6) == twostops-1c.html twostops-1-ref.html # bug 524173
 == twostops-1d.html twostops-1-ref.html
 == twostops-1e.html twostops-1-ref.html
 == twostops-1f.html twostops-1-ref.html
 == twostops-1g.html twostops-1-ref.html
 
 # from http://www.xanthir.com/:4bhipd by way of http://a-ja.net/newgrad.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) == aja-linear-1a.html aja-linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-1a.html aja-linear-1-ref.html
 fails-if(!d2d) == aja-linear-1b.html aja-linear-1-ref.html # bug 526694
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) == aja-linear-1c.html aja-linear-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) == aja-linear-1d.html aja-linear-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) == aja-linear-1e.html aja-linear-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) == aja-linear-1f.html aja-linear-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) == aja-linear-1g.html aja-linear-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) == aja-linear-2a.html aja-linear-2-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) == aja-linear-2b.html aja-linear-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-1c.html aja-linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-1d.html aja-linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-1e.html aja-linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-1f.html aja-linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-1g.html aja-linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-2a.html aja-linear-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-2b.html aja-linear-2-ref.html
 fails == aja-linear-2c.html aja-linear-2-ref.html # bug 522607
 fails-if(!d2d) == aja-linear-2d.html aja-linear-2-ref.html # bug 526694
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,19999) == aja-linear-3a.html aja-linear-3-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,19999) == aja-linear-3b.html aja-linear-3-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,20000) == aja-linear-4a.html aja-linear-4-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,20000) == aja-linear-4b.html aja-linear-4-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,20000) == aja-linear-5a.html aja-linear-5-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,2,16477) == aja-linear-6a.html aja-linear-6-ref.html # bug 526708
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,19999) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-3a.html aja-linear-3-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,19999) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-3b.html aja-linear-3-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-4a.html aja-linear-4-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-4b.html aja-linear-4-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,20000) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-5a.html aja-linear-5-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,2,16477) fuzzy-if(azureSkiaGL,8,20000) == aja-linear-6a.html aja-linear-6-ref.html # bug 526708
 fails == aja-linear-6b.html aja-linear-6-ref.html # bug 522607
 skip-if(B2G) == height-dependence-1.html height-dependence-1-ref.html
 skip-if(B2G) fuzzy-if(cocoaWidget,1,40000) == height-dependence-2.html height-dependence-2-ref.html
 skip-if(B2G) == height-dependence-3.html height-dependence-3-ref.html
 
 fails-if(d2d) == linear-onestopposition-1.html linear-onestopposition-1-ref.html # bug 638664
 == linear-onestopposition-1.html linear-onestopposition-1-ref2.html
 fails-if(d2d) fails-if(cocoaWidget) == radial-onestopposition-1a.html radial-onestopposition-1-ref.html # bug 638664
--- a/layout/reftests/image-element/reftest.list
+++ b/layout/reftests/image-element/reftest.list
@@ -1,16 +1,16 @@
 == bug-364968.html bug-364968-ref.html
 == bug-463204.html bug-463204-ref.html
 fails-if(Android) == canvas-outside-document.html canvas-inside-document.html
 == mozsetimageelement-01.html mozsetimageelement-01-ref.html
 skip-if(B2G) == mozsetimageelement-02.html about:blank # bug 773482
 skip-if(B2G) == image-outside-document-invalidate.html about:blank # bug 773482
 skip-if(B2G) == canvas-outside-document-invalidate-01.html about:blank # bug 773482
-skip-if(B2G) fails-if(azureSkia&&winWidget) fails-if(cocoaWidget) == canvas-outside-document-invalidate-02.html about:blank # See bug 666800 # bug 773482
+skip-if(B2G) fails-if(azureSkia) fails-if(cocoaWidget) == canvas-outside-document-invalidate-02.html about:blank # See bug 666800 # bug 773482
 #fails with Skia due to Skia bug http://code.google.com/p/skia/issues/detail?id=568
 == element-paint-simple.html element-paint-simple-ref.html
 == element-paint-repeated.html element-paint-repeated-ref.html
 == element-paint-recursion.html element-paint-recursion-ref.html
 skip-if(B2G) HTTP(..) == element-paint-continuation.html element-paint-continuation-ref.html
 == element-paint-transform-01.html element-paint-transform-01-ref.html
 random-if(d2d) == element-paint-transform-02.html element-paint-transform-02-ref.html # bug 587133
 == element-paint-background-size-01.html element-paint-background-size-01-ref.html
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -194,17 +194,17 @@ pref(svg.marker-improvements.enabled,tru
 == mask-transformed-02.svg pass.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-01.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-02.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-03.svg mask-type-01-ref.svg
 pref(layout.css.masking.enabled,true) fuzzy-if(d2d,1,6400) == mask-type-04.svg mask-type-01-ref.svg
 == nested-viewBox-01.svg pass.svg
 == nesting-invalid-01.svg nesting-invalid-01-ref.svg
 == non-scaling-stroke-01.svg non-scaling-stroke-01-ref.svg 
-fuzzy-if(Android,1,99) fuzzy-if(!contentSameGfxBackendAsCanvas,1,99) == non-scaling-stroke-02.svg non-scaling-stroke-02-ref.svg 
+fuzzy-if(Android,1,99) fuzzy-if(!contentSameGfxBackendAsCanvas,9,99) == non-scaling-stroke-02.svg non-scaling-stroke-02-ref.svg 
 == non-scaling-stroke-03.svg non-scaling-stroke-03-ref.svg 
 == objectBoundingBox-and-clipPath.svg pass.svg
 # Bug 588684
 random-if(gtk2Widget) == objectBoundingBox-and-fePointLight-01.svg objectBoundingBox-and-fePointLight-01-ref.svg
 random-if(gtk2Widget) == objectBoundingBox-and-fePointLight-02.svg objectBoundingBox-and-fePointLight-02-ref.svg
 == objectBoundingBox-and-mask.svg pass.svg
 == objectBoundingBox-and-mask-02.svg pass.svg
 == objectBoundingBox-and-pattern-01a.svg objectBoundingBox-and-pattern-01-ref.svg
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -408,49 +408,64 @@ nsUserFontSet::StartLoad(gfxMixedFontFam
 static PLDHashOperator DetachFontEntries(const nsAString& aKey,
                                          nsRefPtr<gfxMixedFontFamily>& aFamily,
                                          void* aUserArg)
 {
   aFamily->DetachFontEntries();
   return PL_DHASH_NEXT;
 }
 
+static PLDHashOperator RemoveIfEmpty(const nsAString& aKey,
+                                     nsRefPtr<gfxMixedFontFamily>& aFamily,
+                                     void* aUserArg)
+{
+  return aFamily->GetFontList().Length() ? PL_DHASH_NEXT : PL_DHASH_REMOVE;
+}
+
 bool
 nsUserFontSet::UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules)
 {
   bool modified = false;
 
-  // destroy any current loaders, as the entries they refer to
-  // may be about to get replaced
-  if (mLoaders.Count() > 0) {
-    modified = true; // trigger reflow so that any necessary downloads
-                        // will be reinitiated
-  }
-  mLoaders.EnumerateEntries(DestroyIterator, nullptr);
+  // The @font-face rules that make up the user font set have changed,
+  // so we need to update the set. However, we want to preserve existing
+  // font entries wherever possible, so that we don't discard and then
+  // re-download resources in the (common) case where at least some of the
+  // same rules are still present.
 
   nsTArray<FontFaceRuleRecord> oldRules;
   mRules.SwapElements(oldRules);
 
-  // destroy the font family records; we need to re-create them
-  // because we might end up with faces in a different order,
-  // even if they're the same font entries as before
+  // Remove faces from the font family records; we need to re-insert them
+  // because we might end up with faces in a different order even if they're
+  // the same font entries as before. (The order can affect font selection
+  // where multiple faces match the requested style, perhaps with overlapping
+  // unicode-range coverage.)
   mFontFamilies.Enumerate(DetachFontEntries, nullptr);
-  mFontFamilies.Clear();
 
   for (uint32_t i = 0, i_end = aRules.Length(); i < i_end; ++i) {
-    // insert each rule into our list, migrating old font entries if possible
+    // Insert each rule into our list, migrating old font entries if possible
     // rather than creating new ones; set  modified  to true if we detect
-    // that rule ordering has changed, or if a new entry is created
+    // that rule ordering has changed, or if a new entry is created.
     InsertRule(aRules[i].mRule, aRules[i].mSheetType, oldRules, modified);
   }
 
-  // if any rules are left in the old list, note that the set has changed
+  // Remove any residual families that have no font entries (i.e., they were
+  // not defined at all by the updated set of @font-face rules).
+  mFontFamilies.Enumerate(RemoveIfEmpty, nullptr);
+
+  // If any rules are left in the old list, note that the set has changed
+  // (even if the new set was built entirely by migrating old font entries).
   if (oldRules.Length() > 0) {
     modified = true;
-    // any in-progress loaders for obsolete rules should be cancelled
+    // Any in-progress loaders for obsolete rules should be cancelled,
+    // as the resource being downloaded will no longer be required.
+    // We need to explicitly remove any loaders here, otherwise the loaders
+    // will keep their "orphaned" font entries alive until they complete,
+    // even after the oldRules array is deleted.
     size_t count = oldRules.Length();
     for (size_t i = 0; i < count; ++i) {
       gfxFontEntry *fe = oldRules[i].mFontEntry;
       if (!fe->mIsProxy) {
         continue;
       }
       gfxProxyFontEntry *proxy = static_cast<gfxProxyFontEntry*>(fe);
       if (proxy->mLoader != nullptr) {
@@ -510,17 +525,17 @@ nsUserFontSet::InsertRule(nsCSSFontFaceR
       }
       return;
     }
   }
 
   // this is a new rule:
 
   uint32_t weight = NS_STYLE_FONT_WEIGHT_NORMAL;
-  uint32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL;
+  int32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL;
   uint32_t italicStyle = NS_STYLE_FONT_STYLE_NORMAL;
   nsString languageOverride;
 
   // set up weight
   aRule->GetDesc(eCSSFontDesc_Weight, val);
   unit = val.GetUnit();
   if (unit == eCSSUnit_Integer || unit == eCSSUnit_Enumerated) {
     weight = val.GetIntValue();
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -140,16 +140,18 @@ MOCHITEST_FILES =	test_acid3_test46.html
 		test_parse_rule.html \
 		test_parse_url.html \
 		test_parser_diagnostics_unprintables.html \
 		test_pixel_lengths.html \
 		test_pointer-events.html \
 		test_property_database.html \
 		test_priority_preservation.html \
 		test_property_syntax_errors.html \
+		test_redundant_font_download.html \
+		redundant_font_download.sjs \
 		test_rem_unit.html \
 		test_rule_insertion.html \
 		test_rule_serialization.html \
 		test_rules_out_of_sheets.html \
 		test_selectors.html \
 		test_selectors_on_anonymous_content.html \
 		test_shorthand_property_getters.html \
 		test_specified_value_serialization.html \
new file mode 100644
--- /dev/null
+++ b/layout/style/test/redundant_font_download.sjs
@@ -0,0 +1,60 @@
+const BinaryOutputStream =
+  Components.Constructor("@mozilla.org/binaryoutputstream;1",
+                         "nsIBinaryOutputStream",
+                         "setOutputStream");
+
+// this is simply a hex dump of a red square .PNG image
+const RED_SQUARE =
+  [
+    0x89,  0x50,  0x4E,  0x47,  0x0D,  0x0A,  0x1A,  0x0A,  0x00,  0x00,
+    0x00,  0x0D,  0x49,  0x48,  0x44,  0x52,  0x00,  0x00,  0x00,  0x20,
+    0x00,  0x00,  0x00,  0x20,  0x08,  0x02,  0x00,  0x00,  0x00,  0xFC,
+    0x18,  0xED,  0xA3,  0x00,  0x00,  0x00,  0x01,  0x73,  0x52,  0x47,
+    0x42,  0x00,  0xAE,  0xCE,  0x1C,  0xE9,  0x00,  0x00,  0x00,  0x28,
+    0x49,  0x44,  0x41,  0x54,  0x48,  0xC7,  0xED,  0xCD,  0x41,  0x0D,
+    0x00,  0x00,  0x08,  0x04,  0xA0,  0xD3,  0xFE,  0x9D,  0x35,  0x85,
+    0x0F,  0x37,  0x28,  0x40,  0x4D,  0x6E,  0x75,  0x04,  0x02,  0x81,
+    0x40,  0x20,  0x10,  0x08,  0x04,  0x02,  0xC1,  0x93,  0x60,  0x01,
+    0xA3,  0xC4,  0x01,  0x3F,  0x58,  0x1D,  0xEF,  0x27,  0x00,  0x00,
+    0x00,  0x00,  0x49,  0x45,  0x4E,  0x44,  0xAE,  0x42,  0x60,  0x82
+  ];
+
+function handleRequest(request, response)
+{
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+
+  response.setHeader("Cache-Control", "no-cache");
+
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/plain", false);
+
+  var log = getState("bug-879963-request-log") || "";
+
+  var stream = new BinaryOutputStream(response.bodyOutputStream);
+
+  if (query["q"] == "init") {
+    log = "init"; // initialize the log, and return a PNG image
+    response.setHeader("Content-Type", "image/png", false);
+    stream.writeByteArray(RED_SQUARE, RED_SQUARE.length);
+  } else if (query["q"] == "image") {
+    log = log + ";" + query["q"];
+    response.setHeader("Content-Type", "image/png", false);
+    stream.writeByteArray(RED_SQUARE, RED_SQUARE.length);
+  } else if (query["q"] == "font") {
+    log = log + ";" + query["q"];
+    // we don't provide a real font; that's ok, OTS will just reject it
+    response.write("Junk");
+  } else if (query["q"] == "report") {
+    // don't include the actual "report" request in the log we return
+    response.write(log);
+  } else {
+    log = log + ";" + query["q"];
+    response.setStatusLine(request.httpVersion, 404, "Not Found");
+  }
+
+  setState("bug-879963-request-log", log);
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_redundant_font_download.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=879963 -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for bug 879963</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+  <!-- Two <style> elements with identical @font-face rules.
+       Although multiple @font-face at-rules for the same family are legal,
+       and add faces to the family, we should NOT download the same resource
+       twice just because we have a duplicate face entry. -->
+  <style type="text/css">
+    @font-face {
+      font-family: foo;
+      src: url("redundant_font_download.sjs?q=font");
+    }
+    .test {
+      font-family: foo;
+    }
+  </style>
+
+  <style type="text/css">
+    @font-face {
+      font-family: foo;
+      src: url("redundant_font_download.sjs?q=font");
+    }
+    .test {
+      font-family: foo;
+    }
+  </style>
+
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=879963">Mozilla Bug 879963</a>
+
+  <div>
+    <!-- the 'init' request returns an image (just so we can see it's working)
+         and initializes the request logging in our sjs server -->
+    <img src="redundant_font_download.sjs?q=init">
+  </div>
+
+  <div id="test">
+    Test
+  </div>
+
+  <div>
+    <img id="image2" src="">
+  </div>
+
+  <script type="application/javascript">
+    // helper to retrieve the server's request log as text
+    function getRequestLog() {
+      var xmlHttp = new XMLHttpRequest();
+      xmlHttp.open("GET", "redundant_font_download.sjs?q=report", false);
+      xmlHttp.send(null);
+      return xmlHttp.responseText;
+    }
+
+    // retrieve just the most recent request the server has seen
+    function getLastRequest() {
+      return getRequestLog().split(";").pop();
+    }
+
+    // poll the server at intervals of 'delay' ms until it has seen a specific request,
+    // or until maxTime ms have passed
+    function waitForRequest(request, delay, maxTime, func) {
+      timeLimit = Date.now() + maxTime;
+      var intervalId = window.setInterval(function() {
+        var req = getLastRequest();
+        if (req == request || Date.now() > timeLimit) {
+          window.clearInterval(intervalId);
+          func();
+        }
+      }.bind(this), delay);
+    }
+
+    // initially disable the second of the <style> elements,
+    // so we only have a single copy of the font-face
+    document.getElementsByTagName("style")[1].disabled = true;
+
+    SimpleTest.waitForExplicitFinish();
+
+    // We perform a series of actions that trigger requests to the .sjs server,
+    // and poll the server's request log at each stage to check that it has
+    // seen the request we expected before we proceed to the next step.
+    function startTest() {
+      is(getRequestLog(), "init", "request log has been initialized");
+
+      // this should trigger a font download
+      document.getElementById("test").className = "test";
+
+      // wait to confirm that the server has received the request
+      waitForRequest("font", 10, 5000, continueTest1);
+    }
+
+    function continueTest1() {
+      is(getRequestLog(), "init;font", "server received font request");
+
+      // trigger a request for the second image, to provide an explicit
+      // delimiter in the log before we enable the second @font-face rule
+      document.getElementById("image2").src = "redundant_font_download.sjs?q=image";
+
+      waitForRequest("image", 10, 5000, continueTest2);
+    }
+
+    function continueTest2() {
+      is(getRequestLog(), "init;font;image", "server received image request");
+
+      // enable the second <style> element: we should NOT see a second font request,
+      // we expect waitForRequest to time out instead
+      document.getElementsByTagName("style")[1].disabled = false;
+
+      waitForRequest("font", 10, 1000, continueTest3);
+    }
+
+    function continueTest3() {
+      is(getRequestLog(), "init;font;image", "should NOT have re-requested the font");
+
+      SimpleTest.finish();
+    }
+
+    waitForRequest("init", 10, 5000, startTest);
+
+  </script>
+
+</body>
+
+</html>
--- a/layout/tools/reftest/reftest.js
+++ b/layout/tools/reftest/reftest.js
@@ -538,27 +538,29 @@ function BuildConditionSandbox(aURL) {
         sandbox.smallScreen = true;
     }
 
 #if REFTEST_B2G
     // XXX nsIGfxInfo isn't available in B2G
     sandbox.d2d = false;
     sandbox.azureQuartz = false;
     sandbox.azureSkia = false;
+    sandbox.azureSkiaGL = false;
     sandbox.contentSameGfxBackendAsCanvas = false;
 #else
     var gfxInfo = (NS_GFXINFO_CONTRACTID in CC) && CC[NS_GFXINFO_CONTRACTID].getService(CI.nsIGfxInfo);
     try {
       sandbox.d2d = gfxInfo.D2DEnabled;
     } catch (e) {
       sandbox.d2d = false;
     }
     var info = gfxInfo.getInfo();
     sandbox.azureQuartz = info.AzureCanvasBackend == "quartz";
     sandbox.azureSkia = info.AzureCanvasBackend == "skia";
+    sandbox.azureSkiaGL = info.AzureSkiaAccelerated; // FIXME: assumes GL right now
     // true if we are using the same Azure backend for rendering canvas and content
     sandbox.contentSameGfxBackendAsCanvas = info.AzureContentBackend == info.AzureCanvasBackend
                                             || (info.AzureContentBackend == "none" && info.AzureCanvasBackend == "cairo");
 #endif
 
     sandbox.layersGPUAccelerated =
       gWindowUtils.layerManagerType != "Basic";
     sandbox.layersOpenGL =
--- a/mfbt/MathAlgorithms.h
+++ b/mfbt/MathAlgorithms.h
@@ -200,17 +200,17 @@ namespace detail {
 #  endif
   }
 
   inline uint_fast8_t
   CountTrailingZeroes64(uint64_t u)
   {
 #  if defined(MOZ_BITSCAN_WINDOWS64)
     unsigned long index;
-    _BitScanForward64(&idx, static_cast<unsigned __int64>(u));
+    _BitScanForward64(&index, static_cast<unsigned __int64>(u));
     return uint_fast8_t(index);
 #  else
     uint32_t lo = uint32_t(u);
     if (lo != 0)
       return CountTrailingZeroes32(lo);
     return 32 + CountTrailingZeroes32(uint32_t(u >> 32));
 #  endif
   }
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -541,16 +541,22 @@ pref("ui.dragThresholdX", 25);
 pref("ui.dragThresholdY", 25);
 
 pref("layers.acceleration.disabled", false);
 pref("layers.offmainthreadcomposition.enabled", true);
 pref("layers.async-video.enabled", true);
 pref("layers.progressive-paint", true);
 pref("layers.low-precision-buffer", true);
 pref("layers.low-precision-resolution", 250);
+// We want to limit layers for two reasons:
+// 1) We can't scroll smoothly if we have to many draw calls
+// 2) Pages that have too many layers consume too much memory and crash.
+// By limiting the number of layers on mobile we're making the main thread
+// work harder keep scrolling smooth and memory low.
+pref("layers.max-active", 20);
 
 pref("notification.feature.enabled", true);
 pref("dom.webnotifications.enabled", true);
 
 // prevent tooltips from showing up
 pref("browser.chrome.toolbar_tips", false);
 pref("indexedDB.feature.enabled", true);
 pref("dom.indexedDB.warningQuota", 5);
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -61,16 +61,17 @@ import android.view.MotionEvent;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 import android.widget.Toast;
 
+import java.io.File;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.EnumSet;
 import java.util.Vector;
 
 abstract public class BrowserApp extends GeckoApp
                                  implements TabsPanel.TabsLayoutChangeListener,
                                             PropertyAnimator.PropertyAnimationListener,
@@ -385,16 +386,21 @@ abstract public class BrowserApp extends
 
         super.onStatePurged();
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         mAboutHomeStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_ABOUTHOME");
 
+        String args = getIntent().getStringExtra("args");
+        if (args != null && args.contains("--guest-mode")) {
+            mProfile = GeckoProfile.createGuestProfile(this);
+        }
+
         super.onCreate(savedInstanceState);
 
         mBrowserToolbar = (BrowserToolbar) findViewById(R.id.browser_toolbar);
 
         ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener());
         ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
             @Override
             public boolean onInterceptMotionEvent(View view, MotionEvent event) {
@@ -1591,16 +1597,18 @@ abstract public class BrowserApp extends
         Tab tab = Tabs.getInstance().getSelectedTab();
         MenuItem bookmark = aMenu.findItem(R.id.bookmark);
         MenuItem forward = aMenu.findItem(R.id.forward);
         MenuItem share = aMenu.findItem(R.id.share);
         MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
         MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
         MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
         MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
+        MenuItem enterGuestMode = aMenu.findItem(R.id.enter_guest_mode);
+        MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_mode);
 
         // Only show the "Quit" menu item on pre-ICS or television devices.
         // In ICS+, it's easy to kill an app through the task switcher.
         aMenu.findItem(R.id.quit).setVisible(Build.VERSION.SDK_INT < 14 || HardwareUtils.isTelevision());
 
         if (tab == null || tab.getURL() == null) {
             bookmark.setEnabled(false);
             forward.setEnabled(false);
@@ -1652,16 +1660,21 @@ abstract public class BrowserApp extends
         saveAsPDF.setEnabled(!(tab.getURL().equals("about:home") ||
                                tab.getContentType().equals("application/vnd.mozilla.xul+xml")));
 
         // Disable find in page for about:home, since it won't work on Java content
         findInPage.setEnabled(!isAboutHome(tab));
 
         charEncoding.setVisible(GeckoPreferences.getCharEncodingState());
 
+        if (mProfile.inGuestMode())
+            exitGuestMode.setVisible(true);
+        else
+            enterGuestMode.setVisible(true);
+
         return true;
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         Tab tab = null;
         Intent intent = null;
         switch (item.getItemId()) {
@@ -1740,16 +1753,24 @@ abstract public class BrowserApp extends
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("DesktopMode:Change", args.toString()));
                 return true;
             case R.id.new_tab:
                 addTab();
                 return true;
             case R.id.new_private_tab:
                 addPrivateTab();
                 return true;
+            case R.id.enter_guest_mode:
+                doRestart("--guest-mode");
+                System.exit(0);
+                return true;
+            case R.id.exit_guest_mode:
+                doRestart();
+                System.exit(0);
+                return true;
             default:
                 return super.onOptionsItemSelected(item);
         }
     }
 
     /**
      * This will detect if the key pressed is back. If so, will show the history.
      */
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -172,17 +172,17 @@ abstract public class GeckoApp
     public View getView() { return mGeckoLayout; }
     private View mCameraView;
     private OrientationEventListener mCameraOrientationEventListener;
     public List<GeckoAppShell.AppStateListener> mAppStateListeners;
     private static GeckoApp sAppContext;
     protected MenuPanel mMenuPanel;
     protected Menu mMenu;
     private static GeckoThread sGeckoThread;
-    private GeckoProfile mProfile;
+    protected GeckoProfile mProfile;
     public static int mOrientation;
     protected boolean mIsRestoringActivity;
     private String mCurrentResponse = "";
     public static boolean sIsUsingCustomProfile = false;
 
     private PromptService mPromptService;
     private TextSelection mTextSelection;
 
@@ -1219,43 +1219,45 @@ abstract public class GeckoApp
         // The clock starts...now. Better hurry!
         mJavaUiStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_JAVAUI");
         mGeckoReadyStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_GECKOREADY");
 
         Intent intent = getIntent();
         String args = intent.getStringExtra("args");
         earlyStartJavaSampler(intent);
 
-        String profileName = null;
-        String profilePath = null;
-        if (args != null) {
-            if (args.contains("-P")) {
-                Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
-                Matcher m = p.matcher(args);
-                if (m.find()) {
-                    profileName = m.group(1);
+        if (mProfile == null) {
+            String profileName = null;
+            String profilePath = null;
+            if (args != null) {
+                if (args.contains("-P")) {
+                    Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
+                    Matcher m = p.matcher(args);
+                    if (m.find()) {
+                        profileName = m.group(1);
+                    }
                 }
-            }
 
-            if (args.contains("-profile")) {
-                Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
-                Matcher m = p.matcher(args);
-                if (m.find()) {
-                    profilePath =  m.group(1);
+                if (args.contains("-profile")) {
+                    Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
+                    Matcher m = p.matcher(args);
+                    if (m.find()) {
+                        profilePath =  m.group(1);
+                    }
+                    if (profileName == null) {
+                        profileName = getDefaultProfileName();
+                        if (profileName == null)
+                            profileName = "default";
+                    }
+                    GeckoApp.sIsUsingCustomProfile = true;
                 }
-                if (profileName == null) {
-                    profileName = getDefaultProfileName();
-                    if (profileName == null)
-                        profileName = "default";
+
+                if (profileName != null || profilePath != null) {
+                    mProfile = GeckoProfile.get(this, profileName, profilePath);
                 }
-                GeckoApp.sIsUsingCustomProfile = true;
-            }
-
-            if (profileName != null || profilePath != null) {
-                mProfile = GeckoProfile.get(this, profileName, profilePath);
             }
         }
 
         BrowserDB.initialize(getProfile().getName());
         ((GeckoApplication)getApplication()).initialize();
 
         sAppContext = this;
         GeckoAppShell.setContextGetter(this);
@@ -2187,27 +2189,33 @@ abstract public class GeckoApp
             Map.Entry<String,String> entry = envIter.next();
             intent.putExtra("env" + c, entry.getKey() + "="
                             + entry.getValue());
             c++;
         }
     }
 
     public void doRestart() {
-        doRestart(RESTARTER_ACTION);
+        doRestart(RESTARTER_ACTION, null);
     }
 
-    public void doRestart(String action) {
+    public void doRestart(String args) {
+        doRestart(RESTARTER_ACTION, args);
+    }
+
+    public void doRestart(String action, String args) {
         Log.d(LOGTAG, "doRestart(\"" + action + "\")");
         try {
             Intent intent = new Intent(action);
             intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, RESTARTER_CLASS);
             /* TODO: addEnvToIntent(intent); */
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                             Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+            if (args != null)
+                intent.putExtra("args", args);
             Log.d(LOGTAG, "Restart intent: " + intent.toString());
             GeckoAppShell.killAnyZombies();
             startActivity(intent);
         } catch (Exception e) {
             Log.e(LOGTAG, "Error effecting restart.", e);
         }
         finish();
         // Give the restart process time to start before we die
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1366,28 +1366,20 @@ public class GeckoAppShell
 
     /**
      * Returns the colour depth of the default screen. This will either be
      * 24 or 16.
      */
     public static synchronized int getScreenDepth() {
         if (sScreenDepth == 0) {
             sScreenDepth = 16;
-            if (getGeckoInterface() != null) {
-                switch (getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay().getPixelFormat()) {
-                    case PixelFormat.RGBA_8888 :
-                    case PixelFormat.RGBX_8888 :
-                    case PixelFormat.RGB_888 :
-                    {
-                        if (isHighMemoryDevice()) {
-                            sScreenDepth = 24;
-                        }
-                        break;
-                    }
-                }
+            PixelFormat info = new PixelFormat();
+            PixelFormat.getPixelFormatInfo(getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay().getPixelFormat(), info);
+            if (info.bitsPerPixel >= 24 && isHighMemoryDevice()) {
+                sScreenDepth = 24;
             }
         }
 
         return sScreenDepth;
     }
 
     public static synchronized void setScreenDepthOverride(int aScreenDepth) {
         if (sScreenDepth != 0) {
--- a/mobile/android/base/GeckoProfile.java
+++ b/mobile/android/base/GeckoProfile.java
@@ -28,60 +28,72 @@ public final class GeckoProfile {
     private static HashMap<String, GeckoProfile> sProfileCache = new HashMap<String, GeckoProfile>();
     private static String sDefaultProfileName = null;
 
     private final Context mContext;
     private final String mName;
     private File mMozDir;
     private File mDir;
 
+    private boolean mInGuestMode = false;
+
     static private INIParser getProfilesINI(Context context) {
       File filesDir = context.getFilesDir();
       File mozillaDir = new File(filesDir, "mozilla");
       File profilesIni = new File(mozillaDir, "profiles.ini");
       return new INIParser(profilesIni);
     }
 
     public static GeckoProfile get(Context context) {
-        if (context instanceof GeckoApp)
+        if (context instanceof GeckoApp) {
+            if (((GeckoApp)context).mProfile != null)
+                return ((GeckoApp)context).mProfile;
             return get(context, ((GeckoApp)context).getDefaultProfileName());
+        }
 
         return get(context, "");
     }
 
     public static GeckoProfile get(Context context, String profileName) {
         synchronized (sProfileCache) {
             GeckoProfile profile = sProfileCache.get(profileName);
             if (profile != null)
                 return profile;
         }
-        return get(context, profileName, null);
+        return get(context, profileName, (File)null);
     }
 
     public static GeckoProfile get(Context context, String profileName, String profilePath) {
+        File dir = null;
+        if (!TextUtils.isEmpty(profilePath))
+            dir = new File(profilePath);
+        return get(context, profileName, dir);
+    }
+
+    public static GeckoProfile get(Context context, String profileName, File profileDir) {
         if (context == null) {
             throw new IllegalArgumentException("context must be non-null");
         }
 
         // if no profile was passed in, look for the default profile listed in profiles.ini
         // if that doesn't exist, look for a profile called 'default'
-        if (TextUtils.isEmpty(profileName) && TextUtils.isEmpty(profilePath)) {
+        if (TextUtils.isEmpty(profileName) && profileDir == null) {
             profileName = GeckoProfile.findDefaultProfile(context);
             if (profileName == null)
                 profileName = "default";
         }
 
         // actually try to look up the profile
         synchronized (sProfileCache) {
             GeckoProfile profile = sProfileCache.get(profileName);
             if (profile == null) {
-                profile = new GeckoProfile(context, profileName, profilePath);
+                profile = new GeckoProfile(context, profileName, profileDir);
                 sProfileCache.put(profileName, profile);
             } else {
-                profile.setDir(profilePath);
+                profile.setDir(profileDir);
             }
             return profile;
         }
     }
 
     public static File ensureMozillaDirectory(Context context) throws IOException {
         synchronized (context) {
             File filesDir = context.getFilesDir();
@@ -94,35 +106,54 @@ public final class GeckoProfile {
             return mozDir;
         }
     }
 
     public static boolean removeProfile(Context context, String profileName) {
         return new GeckoProfile(context, profileName).remove();
     }
 
+    public static GeckoProfile createGuestProfile(Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must be non-null");
+        }
+        try {
+            File guestDir = context.getDir("guest", context.MODE_PRIVATE);
+            if (guestDir.exists())
+                guestDir.delete();
+            guestDir.mkdir();
+            GeckoProfile profile = get(context, "guest", guestDir);
+            profile.mInGuestMode = true;
+            return profile;
+        } catch (Exception ex) {
+            Log.e(LOGTAG, "Error creating guest profile", ex);
+        }
+        return null;
+    }
+
     private GeckoProfile(Context context, String profileName) {
         mContext = context;
         mName = profileName;
     }
 
-    private GeckoProfile(Context context, String profileName, String profilePath) {
+    private GeckoProfile(Context context, String profileName, File profileDir) {
         mContext = context;
         mName = profileName;
-        setDir(profilePath);
+        setDir(profileDir);
     }
 
-    private void setDir(String profilePath) {
-        if (!TextUtils.isEmpty(profilePath)) {
-            File dir = new File(profilePath);
-            if (dir.exists() && dir.isDirectory()) {
-                mDir = dir;
-            } else {
-                Log.w(LOGTAG, "requested profile directory missing: " + profilePath);
-            }
+    public boolean inGuestMode() {
+        return mInGuestMode;
+    }
+
+    private void setDir(File dir) {
+        if (dir != null && dir.exists() && dir.isDirectory()) {
+            mDir = dir;
+        } else {
+            Log.w(LOGTAG, "requested profile directory missing: " + dir);
         }
     }
 
     public String getName() {
         return mName;
     }
 
     public synchronized File getDir() {
--- a/mobile/android/base/GeckoThread.java
+++ b/mobile/android/base/GeckoThread.java
@@ -15,16 +15,17 @@ import android.content.Intent;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.util.Log;
 import android.app.Activity;
+import java.io.IOException;
 
 
 import java.util.Locale;
 
 public class GeckoThread extends Thread implements GeckoEventListener {
     private static final String LOGTAG = "GeckoThread";
 
     public enum LaunchState {
@@ -93,17 +94,26 @@ public class GeckoThread extends Thread 
         }
         if (GeckoApp.ACTION_BOOKMARK.equals(action)) {
             return "-bookmark";
         }
         return null;
     }
 
     private String addCustomProfileArg(String args) {
-        String profile = GeckoAppShell.getGeckoInterface() == null || GeckoApp.sIsUsingCustomProfile ? "" : (" -P " + GeckoAppShell.getGeckoInterface().getProfile().getName());
+        String profile = "";
+        if (GeckoAppShell.getGeckoInterface() != null) {
+            if (GeckoAppShell.getGeckoInterface().getProfile().inGuestMode()) {
+                try {
+                    profile = " -profile " + GeckoAppShell.getGeckoInterface().getProfile().getDir().getCanonicalPath();
+                } catch (IOException ioe) { Log.e(LOGTAG, "error getting guest profile path", ioe); }
+            } else if (GeckoApp.sIsUsingCustomProfile) {
+                profile = " -P " + GeckoAppShell.getGeckoInterface().getProfile().getName();
+            }
+        }
         return (args != null ? args : "") + profile;
     }
 
     @Override
     public void run() {
         Looper.prepare();
         ThreadUtils.setGeckoThread(this, new Handler());
 
--- a/mobile/android/base/PageActionLayout.java
+++ b/mobile/android/base/PageActionLayout.java
@@ -161,43 +161,60 @@ public class PageActionLayout extends Li
         if (buttonClickedId.equals(MENU_BUTTON_KEY)) {
             showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
             return true;
         } else {
             return mPageActionList.get(buttonClickedId).onLongClick();
         }
     }
 
+    private void setActionForView(final ImageButton view, final PageAction pageAction) {
+        if (pageAction == null) {
+            view.setTag(null);
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run () {
+                    view.setImageDrawable(null);
+                    view.setVisibility(View.GONE);
+                    view.setContentDescription(null);
+                }
+            });
+            return;
+        }
+
+        view.setTag(pageAction.getID());
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run () {
+                view.setImageDrawable(pageAction.getDrawable());
+                view.setVisibility(View.VISIBLE);
+                view.setContentDescription(pageAction.getTitle());
+            }
+        });
+    }
+
     private void refreshPageActionIcons() {
         final Resources resources = mContext.getResources();
         for(int index = 0; index < this.getChildCount(); index++) {
             final ImageButton v = (ImageButton)this.getChildAt(index);
             final PageAction pageAction = getPageActionForViewAt(index);
 
-            if (index == (this.getChildCount() - 1)) {
-                String id = (pageAction != null) ? pageAction.getID() : null;
-                v.setTag((mPageActionList.size() > mMaxVisiblePageActions) ? MENU_BUTTON_KEY : id);
+            // If there are more pageactions then buttons, set the menu icon. Otherwise set the page action's icon if there is a page action.
+            if (index == (this.getChildCount() - 1) && mPageActionList.size() > mMaxVisiblePageActions) {
+                v.setTag(MENU_BUTTON_KEY);
                 ThreadUtils.postToUiThread(new Runnable() {
                     @Override
                     public void run () {
-                        // If there are more pageactions then buttons, set the menu icon. Otherwise set the page action's icon if there is a page action.
-                        Drawable d = (pageAction != null) ? pageAction.getDrawable() : null;
-                        v.setImageDrawable((mPageActionList.size() > mMaxVisiblePageActions) ? resources.getDrawable(R.drawable.icon_pageaction) : d);
+                        v.setImageDrawable(resources.getDrawable(R.drawable.icon_pageaction));
                         v.setVisibility((pageAction != null) ? View.VISIBLE : View.GONE);
+                        v.setContentDescription(resources.getString(R.string.page_action_dropmarker_description));
                     }
                 });
             } else {
-                v.setTag((pageAction != null) ? pageAction.getID() : null);
-                ThreadUtils.postToUiThread(new Runnable() {
-                    @Override
-                    public void run () {
-                        v.setImageDrawable((pageAction != null) ? pageAction.getDrawable() : null);
-                        v.setVisibility((pageAction != null) ? View.VISIBLE : View.GONE);
-                    }
-                });
+                setActionForView(v, pageAction);
             }
         }
     }
 
     private PageAction getPageActionForViewAt(int index) {
         /**
          * We show the user the most recent pageaction added since this keeps the user aware of any new page actions being added
          * Also, the order of the pageAction is important i.e. if a page action is added, instead of shifting the pagactions to the
@@ -308,9 +325,9 @@ public class PageActionLayout extends Li
 
         public boolean onLongClick() {
             if (mOnPageActionClickListeners != null) {
                 return mOnPageActionClickListeners.onLongClick(mId);
             }
             return false;
         }
     }
-}
\ No newline at end of file
+}
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -247,17 +247,20 @@ size. -->
 <!ENTITY site_settings_clear        "Clear">
 <!ENTITY site_settings_no_settings  "There are no settings to clear.">
 
 <!ENTITY reading_list "Reading List">
 <!ENTITY reading_list_added "Page added to your Reading List">
 <!ENTITY reading_list_removed "Page removed from your Reading List">
 <!ENTITY reading_list_failed "Failed to add page to your Reading List">
 <!ENTITY reading_list_duplicate "Page already in your Reading List">
-<!ENTITY reader "Reader">
+
+<!-- Localization note : These strings are used as alternate text for accessibility.
+     They are not visible in the UI. -->
+<!ENTITY page_action_dropmarker_description "Additional Actions">
 
 <!ENTITY masterpassword_create_title "Create Master Password">
 <!ENTITY masterpassword_remove_title "Remove Master Password">
 <!ENTITY masterpassword_password "Password">
 <!ENTITY masterpassword_confirm "Confirm password">
 
 <!ENTITY button_ok "OK">
 <!ENTITY button_cancel "Cancel">
@@ -345,8 +348,12 @@ just addresses the organization to follo
 
 <!ENTITY updater_downloading_title2 "Downloading &brandShortName;">
 <!ENTITY updater_downloading_title_failed2 "Download failed">
 <!ENTITY updater_downloading_select2 "Touch to apply update once downloaded">
 <!ENTITY updater_downloading_retry2 "Touch to retry">
 
 <!ENTITY updater_apply_title2 "Update available for &brandShortName;">
 <!ENTITY updater_apply_select2 "Touch to update">
+
+<!-- Guest mode -->
+<!ENTITY enter_guest_mode "Enter Guest Mode">
+<!ENTITY exit_guest_mode "Exit Guest Mode">
--- a/mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
@@ -69,9 +69,17 @@
     <item android:id="@+id/char_encoding"
           android:icon="@drawable/ic_menu_character_encoding"
           android:visible="false"
           android:title="@string/char_encoding"/>
 
     <item android:id="@+id/settings"
           android:icon="@drawable/ic_menu_settings"
           android:title="@string/settings" />
+
+    <item android:id="@+id/enter_guest_mode"
+          android:visible="false"
+          android:title="@string/enter_guest_mode"/>
+
+    <item android:id="@+id/exit_guest_mode"
+          android:visible="false"
+          android:title="@string/exit_guest_mode"/>
 </menu>
--- a/mobile/android/base/resources/menu-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-v11/browser_app_menu.xml
@@ -70,9 +70,17 @@
     <item android:id="@+id/char_encoding"
           android:icon="@drawable/ic_menu_character_encoding"
           android:visible="false"
           android:title="@string/char_encoding"/>
 
     <item android:id="@+id/settings"
           android:icon="@drawable/ic_menu_settings"
           android:title="@string/settings" />
+
+    <item android:id="@+id/enter_guest_mode"
+          android:visible="false"
+          android:title="@string/enter_guest_mode"/>
+
+    <item android:id="@+id/exit_guest_mode"
+          android:visible="false"
+          android:title="@string/exit_guest_mode"/>
 </menu>
--- a/mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
@@ -70,9 +70,17 @@
     <item android:id="@+id/char_encoding"
           android:icon="@drawable/ic_menu_character_encoding"
           android:visible="false"
           android:title="@string/char_encoding"/>
 
     <item android:id="@+id/settings"
           android:icon="@drawable/ic_menu_settings"
           android:title="@string/settings" />
+
+    <item android:id="@+id/enter_guest_mode"
+          android:visible="false"
+          android:title="@string/enter_guest_mode"/>
+
+    <item android:id="@+id/exit_guest_mode"
+          android:visible="false"
+          android:title="@string/exit_guest_mode"/>
 </menu>
--- a/mobile/android/base/resources/menu/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu/browser_app_menu.xml
@@ -48,9 +48,17 @@
           android:title="@string/apps"/>
 
     <item android:id="@+id/char_encoding"
           android:visible="false"
           android:title="@string/char_encoding"/>
 
     <item android:id="@+id/settings"
           android:title="@string/settings" />
+
+    <item android:id="@+id/enter_guest_mode"
+          android:visible="false"
+          android:title="@string/enter_guest_mode"/>
+
+    <item android:id="@+id/exit_guest_mode"
+          android:visible="false"
+          android:title="@string/exit_guest_mode"/>
 </menu>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -191,16 +191,18 @@
   <string name="site_settings_no_settings">&site_settings_no_settings;</string>
 
   <string name="reading_list">&reading_list;</string>
   <string name="reading_list_added">&reading_list_added;</string>
   <string name="reading_list_removed">&reading_list_removed;</string>
   <string name="reading_list_failed">&reading_list_failed;</string>
   <string name="reading_list_duplicate">&reading_list_duplicate;</string>
 
+  <string name="page_action_dropmarker_description">&page_action_dropmarker_description;</string>
+
   <string name="contextmenu_open_new_tab">&contextmenu_open_new_tab;</string>
   <string name="contextmenu_open_private_tab">&contextmenu_open_private_tab;</string>
   <string name="contextmenu_open_in_reader">&contextmenu_open_in_reader;</string>
   <string name="contextmenu_remove_history">&contextmenu_remove_history;</string>
   <string name="contextmenu_remove_bookmark">&contextmenu_remove_bookmark;</string>
   <string name="contextmenu_add_to_launcher">&contextmenu_add_to_launcher;</string>
   <string name="contextmenu_share">&contextmenu_share;</string>
   <string name="contextmenu_pasteandgo">&contextmenu_pasteandgo;</string>
@@ -311,9 +313,13 @@
   <!-- Search suggestions opt-in -->
   <string name="suggestions_prompt">&suggestions_prompt2;</string>
 
   <!-- Wallpaper Notifications -->
   <string name="wallpaper_success">&wallpaper_success;</string>
   <string name="wallpaper_progress">&wallpaper_progress;</string>
   <string name="wallpaper_fail">&wallpaper_fail;</string>
 
+
+  <!-- Guest mode -->
+  <string name="enter_guest_mode">&enter_guest_mode;</string>
+  <string name="exit_guest_mode">&exit_guest_mode;</string>
 </resources>
--- a/mobile/android/chrome/content/downloads.js
+++ b/mobile/android/chrome/content/downloads.js
@@ -11,17 +11,16 @@ function dump(a) {
 
 const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
 
 var Downloads = {
   _initialized: false,
   _dlmgr: null,
   _progressAlert: null,
   _privateDownloads: [],
-  isForeground : true,
 
   _getLocalFile: function dl__getLocalFile(aFileURI) {
     // if this is a URL, get the file from that
     // XXX it's possible that using a null char-set here is bad
     const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL);
     return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
   },
 
@@ -30,18 +29,16 @@ var Downloads = {
       return;
     this._initialized = true;
 
     // Monitor downloads and display alerts
     this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
     this._progressAlert = new AlertDownloadProgressListener();
     this._dlmgr.addPrivacyAwareListener(this._progressAlert);
     Services.obs.addObserver(this, "last-pb-context-exited", true);
-    Services.obs.addObserver(this, "application-background", false);
-    Services.obs.addObserver(this, "application-foreground", false);
   },
 
   openDownload: function dl_openDownload(aDownload) {
     let fileUri = aDownload.target.spec;
     let guid = aDownload.guid;
     let f = this._getLocalFile(fileUri);
     try {
       f.launch();
@@ -102,39 +99,26 @@ var Downloads = {
 
     var notifier = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
     notifier.showAlertNotification(aIcon, aTitle, aMessage, true, "", observer,
                                    aDownload.target.spec.replace("file:", "download:"));
   },
 
   // observer for last-pb-context-exited
   observe: function dl_observe(aSubject, aTopic, aData) {
-    switch (aTopic) {
-      case "last-pb-context-exited": {
-        let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-        let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
-        let download;
-        while ((download = this._privateDownloads.pop())) {
-          try {
-            let notificationName = download.target.spec.replace("file:", "download:");
-            progressListener.onCancel(notificationName);
-          } catch (e) {
-            dump("Error removing private download: " + e);
-          }
-        }
-        break;
+    let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
+    let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
+    let download;
+    while ((download = this._privateDownloads.pop())) {
+      try {
+        let notificationName = download.target.spec.replace("file:", "download:");
+        progressListener.onCancel(notificationName);
+      } catch (e) {
+        dump("Error removing private download: " + e);
       }
-
-      case "application-foreground":
-        this.isForeground = true;
-        break;
-
-      case "application-background":
-        this.isForeground = false;
-        break;
     }
   },
 
   QueryInterface: function (aIID) {
     if (!aIID.equals(Ci.nsISupports) &&
         !aIID.equals(Ci.nsIObserver) &&
         !aIID.equals(Ci.nsISupportsWeakReference))
       throw Components.results.NS_ERROR_NO_INTERFACE;
@@ -192,24 +176,18 @@ AlertDownloadProgressListener.prototype 
         progressListener.onCancel(notificationName);
 
         if (aDownload.isPrivate) {
           let index = this._privateDownloads.indexOf(aDownload);
           if (index != -1) {
             this._privateDownloads.splice(index, 1);
           }
         }
-        // Checking existance of MimeInfo and if there is at least one application handler in addition
-        // to default one.
-        let existsAvailableHandler = (aDownload.MIMEInfo && aDownload.MIMEInfo.possibleApplicationHandlers.length > 1);
 
-        // We want to show the download finished notification only if it is not automatically opened.
-        // A download is automatically opened if it has a default handler and fennec is in foreground.
-        if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED &&
-            !(existsAvailableHandler && Downloads.isForeground)) {
+        if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
           Downloads.showAlert(aDownload, Strings.browser.GetStringFromName("alertDownloadsDone2"),
                               aDownload.displayName);
         }
         break;
       }
     }
   },
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4048,16 +4048,20 @@ pref("layers.acceleration.force-enabled"
 pref("layers.acceleration.force-enabled", false);
 #endif
 
 pref("layers.acceleration.draw-fps", false);
 
 pref("layers.draw-borders", false);
 pref("layers.frame-counter", false);
 
+// Max number of layers per container. See Overwrite in mobile prefs.
+pref("layers.max-active", -1);
+
+
 #ifdef XP_MACOSX
 pref("layers.offmainthreadcomposition.enabled", true);
 #else
 pref("layers.offmainthreadcomposition.enabled", false);
 #endif
 // same effect as layers.offmainthreadcomposition.enabled, but specifically for
 // use with tests.
 pref("layers.offmainthreadcomposition.testing.enabled", false);
new file mode 100644
--- /dev/null
+++ b/python/configobj/PKG-INFO
@@ -0,0 +1,47 @@
+Metadata-Version: 1.0
+Name: configobj
+Version: 4.7.2
+Summary: Config file reading, writing and validation.
+Home-page: http://www.voidspace.org.uk/python/configobj.html
+Author: Michael Foord & Nicola Larosa
+Author-email: fuzzyman@voidspace.org.uk
+License: UNKNOWN
+Download-URL: http://www.voidspace.org.uk/downloads/configobj-4.7.2.zip
+Description: **ConfigObj** is a simple but powerful config file reader and writer: an *ini
+        file round tripper*. Its main feature is that it is very easy to use, with a
+        straightforward programmer's interface and a simple syntax for config files.
+        It has lots of other features though :
+        
+        * Nested sections (subsections), to any level
+        * List values
+        * Multiple line values
+        * Full Unicode support
+        * String interpolation (substitution)
+        * Integrated with a powerful validation system
+        
+        - including automatic type checking/conversion
+        - and allowing default values
+        - repeated sections
+        
+        * All comments in the file are preserved
+        * The order of keys/sections is preserved
+        * Powerful ``unrepr`` mode for storing/retrieving Python data-types
+        
+        | Release 4.7.2 fixes several bugs in 4.7.1
+        | Release 4.7.1 fixes a bug with the deprecated options keyword in
+        | 4.7.0.
+        | Release 4.7.0 improves performance adds features for validation and
+        | fixes some bugs.
+Keywords: config,ini,dictionary,application,admin,sysadmin,configuration,validation
+Platform: UNKNOWN
+Classifier: Development Status :: 6 - Mature
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.3
+Classifier: Programming Language :: Python :: 2.4
+Classifier: Programming Language :: Python :: 2.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Operating System :: OS Independent
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
new file mode 100644
--- /dev/null
+++ b/python/configobj/configobj.py
@@ -0,0 +1,2468 @@
+# configobj.py
+# A config file reader/writer that supports nested sections in config files.
+# Copyright (C) 2005-2010 Michael Foord, Nicola Larosa
+# E-mail: fuzzyman AT voidspace DOT org DOT uk
+#         nico AT tekNico DOT net
+
+# ConfigObj 4
+# http://www.voidspace.org.uk/python/configobj.html
+
+# Released subject to the BSD License
+# Please see http://www.voidspace.org.uk/python/license.shtml
+
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# For information about bugfixes, updates and support, please join the
+# ConfigObj mailing list:
+# http://lists.sourceforge.net/lists/listinfo/configobj-develop
+# Comments, suggestions and bug reports welcome.
+
+from __future__ import generators
+
+import os
+import re
+import sys
+
+from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
+
+
+# imported lazily to avoid startup performance hit if it isn't used
+compiler = None
+
+# A dictionary mapping BOM to
+# the encoding to decode with, and what to set the
+# encoding attribute to.
+BOMS = {
+    BOM_UTF8: ('utf_8', None),
+    BOM_UTF16_BE: ('utf16_be', 'utf_16'),
+    BOM_UTF16_LE: ('utf16_le', 'utf_16'),
+    BOM_UTF16: ('utf_16', 'utf_16'),
+    }
+# All legal variants of the BOM codecs.
+# TODO: the list of aliases is not meant to be exhaustive, is there a
+#   better way ?
+BOM_LIST = {
+    'utf_16': 'utf_16',
+    'u16': 'utf_16',
+    'utf16': 'utf_16',
+    'utf-16': 'utf_16',
+    'utf16_be': 'utf16_be',
+    'utf_16_be': 'utf16_be',
+    'utf-16be': 'utf16_be',
+    'utf16_le': 'utf16_le',
+    'utf_16_le': 'utf16_le',
+    'utf-16le': 'utf16_le',
+    'utf_8': 'utf_8',
+    'u8': 'utf_8',
+    'utf': 'utf_8',
+    'utf8': 'utf_8',
+    'utf-8': 'utf_8',
+    }
+
+# Map of encodings to the BOM to write.
+BOM_SET = {
+    'utf_8': BOM_UTF8,
+    'utf_16': BOM_UTF16,
+    'utf16_be': BOM_UTF16_BE,
+    'utf16_le': BOM_UTF16_LE,
+    None: BOM_UTF8
+    }
+
+
+def match_utf8(encoding):
+    return BOM_LIST.get(encoding.lower()) == 'utf_8'
+
+
+# Quote strings used for writing values
+squot = "'%s'"
+dquot = '"%s"'
+noquot = "%s"
+wspace_plus = ' \r\n\v\t\'"'
+tsquot = '"""%s"""'
+tdquot = "'''%s'''"
+
+# Sentinel for use in getattr calls to replace hasattr
+MISSING = object()
+
+__version__ = '4.7.2'
+
+try:
+    any
+except NameError:
+    def any(iterable):
+        for entry in iterable:
+            if entry:
+                return True
+        return False
+
+
+__all__ = (
+    '__version__',
+    'DEFAULT_INDENT_TYPE',
+    'DEFAULT_INTERPOLATION',
+    'ConfigObjError',
+    'NestingError',
+    'ParseError',
+    'DuplicateError',
+    'ConfigspecError',
+    'ConfigObj',
+    'SimpleVal',
+    'InterpolationError',
+    'InterpolationLoopError',
+    'MissingInterpolationOption',
+    'RepeatSectionError',
+    'ReloadError',
+    'UnreprError',
+    'UnknownType',
+    'flatten_errors',
+    'get_extra_values'
+)
+
+DEFAULT_INTERPOLATION = 'configparser'
+DEFAULT_INDENT_TYPE = '    '
+MAX_INTERPOL_DEPTH = 10
+
+OPTION_DEFAULTS = {
+    'interpolation': True,
+    'raise_errors': False,
+    'list_values': True,
+    'create_empty': False,
+    'file_error': False,
+    'configspec': None,
+    'stringify': True,
+    # option may be set to one of ('', ' ', '\t')
+    'indent_type': None,
+    'encoding': None,
+    'default_encoding': None,
+    'unrepr': False,
+    'write_empty_values': False,
+}
+
+
+
+def getObj(s):
+    global compiler
+    if compiler is None:
+        import compiler
+    s = "a=" + s
+    p = compiler.parse(s)
+    return p.getChildren()[1].getChildren()[0].getChildren()[1]
+
+
+class UnknownType(Exception):
+    pass
+
+
+class Builder(object):
+    
+    def build(self, o):
+        m = getattr(self, 'build_' + o.__class__.__name__, None)
+        if m is None:
+            raise UnknownType(o.__class__.__name__)
+        return m(o)
+    
+    def build_List(self, o):
+        return map(self.build, o.getChildren())
+    
+    def build_Const(self, o):
+        return o.value
+    
+    def build_Dict(self, o):
+        d = {}
+        i = iter(map(self.build, o.getChildren()))
+        for el in i:
+            d[el] = i.next()
+        return d
+    
+    def build_Tuple(self, o):
+        return tuple(self.build_List(o))
+    
+    def build_Name(self, o):
+        if o.name == 'None':
+            return None
+        if o.name == 'True':
+            return True
+        if o.name == 'False':
+            return False
+        
+        # An undefined Name
+        raise UnknownType('Undefined Name')
+    
+    def build_Add(self, o):
+        real, imag = map(self.build_Const, o.getChildren())
+        try:
+            real = float(real)
+        except TypeError:
+            raise UnknownType('Add')
+        if not isinstance(imag, complex) or imag.real != 0.0:
+            raise UnknownType('Add')
+        return real+imag
+    
+    def build_Getattr(self, o):
+        parent = self.build(o.expr)
+        return getattr(parent, o.attrname)
+    
+    def build_UnarySub(self, o):
+        return -self.build_Const(o.getChildren()[0])
+    
+    def build_UnaryAdd(self, o):
+        return self.build_Const(o.getChildren()[0])
+
+
+_builder = Builder()
+
+
+def unrepr(s):
+    if not s:
+        return s
+    return _builder.build(getObj(s))
+
+
+
+class ConfigObjError(SyntaxError):
+    """
+    This is the base class for all errors that ConfigObj raises.
+    It is a subclass of SyntaxError.
+    """
+    def __init__(self, message='', line_number=None, line=''):
+        self.line = line
+        self.line_number = line_number
+        SyntaxError.__init__(self, message)
+
+
+class NestingError(ConfigObjError):
+    """
+    This error indicates a level of nesting that doesn't match.
+    """
+
+
+class ParseError(ConfigObjError):
+    """
+    This error indicates that a line is badly written.
+    It is neither a valid ``key = value`` line,
+    nor a valid section marker line.
+    """
+
+
+class ReloadError(IOError):
+    """
+    A 'reload' operation failed.
+    This exception is a subclass of ``IOError``.
+    """
+    def __init__(self):
+        IOError.__init__(self, 'reload failed, filename is not set.')
+
+
+class DuplicateError(ConfigObjError):
+    """
+    The keyword or section specified already exists.
+    """
+
+
+class ConfigspecError(ConfigObjError):
+    """
+    An error occured whilst parsing a configspec.
+    """
+
+
+class InterpolationError(ConfigObjError):
+    """Base class for the two interpolation errors."""
+
+
+class InterpolationLoopError(InterpolationError):
+    """Maximum interpolation depth exceeded in string interpolation."""
+
+    def __init__(self, option):
+        InterpolationError.__init__(
+            self,
+            'interpolation loop detected in value "%s".' % option)
+
+
+class RepeatSectionError(ConfigObjError):
+    """
+    This error indicates additional sections in a section with a
+    ``__many__`` (repeated) section.
+    """
+
+
+class MissingInterpolationOption(InterpolationError):
+    """A value specified for interpolation was missing."""
+    def __init__(self, option):
+        msg = 'missing option "%s" in interpolation.' % option
+        InterpolationError.__init__(self, msg)
+
+
+class UnreprError(ConfigObjError):
+    """An error parsing in unrepr mode."""
+
+
+
+class InterpolationEngine(object):
+    """
+    A helper class to help perform string interpolation.
+
+    This class is an abstract base class; its descendants perform
+    the actual work.
+    """
+
+    # compiled regexp to use in self.interpolate()
+    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
+    _cookie = '%'
+
+    def __init__(self, section):
+        # the Section instance that "owns" this engine
+        self.section = section
+
+
+    def interpolate(self, key, value):
+        # short-cut
+        if not self._cookie in value:
+            return value
+        
+        def recursive_interpolate(key, value, section, backtrail):
+            """The function that does the actual work.
+
+            ``value``: the string we're trying to interpolate.
+            ``section``: the section in which that string was found
+            ``backtrail``: a dict to keep track of where we've been,
+            to detect and prevent infinite recursion loops
+
+            This is similar to a depth-first-search algorithm.
+            """
+            # Have we been here already?
+            if (key, section.name) in backtrail:
+                # Yes - infinite loop detected
+                raise InterpolationLoopError(key)
+            # Place a marker on our backtrail so we won't come back here again
+            backtrail[(key, section.name)] = 1
+
+            # Now start the actual work
+            match = self._KEYCRE.search(value)
+            while match:
+                # The actual parsing of the match is implementation-dependent,
+                # so delegate to our helper function
+                k, v, s = self._parse_match(match)
+                if k is None:
+                    # That's the signal that no further interpolation is needed
+                    replacement = v
+                else:
+                    # Further interpolation may be needed to obtain final value
+                    replacement = recursive_interpolate(k, v, s, backtrail)
+                # Replace the matched string with its final value
+                start, end = match.span()
+                value = ''.join((value[:start], replacement, value[end:]))
+                new_search_start = start + len(replacement)
+                # Pick up the next interpolation key, if any, for next time
+                # through the while loop
+                match = self._KEYCRE.search(value, new_search_start)
+
+            # Now safe to come back here again; remove marker from backtrail
+            del backtrail[(key, section.name)]
+
+            return value
+
+        # Back in interpolate(), all we have to do is kick off the recursive
+        # function with appropriate starting values
+        value = recursive_interpolate(key, value, self.section, {})
+        return value
+
+
+    def _fetch(self, key):
+        """Helper function to fetch values from owning section.
+
+        Returns a 2-tuple: the value, and the section where it was found.
+        """
+        # switch off interpolation before we try and fetch anything !
+        save_interp = self.section.main.interpolation
+        self.section.main.interpolation = False
+
+        # Start at section that "owns" this InterpolationEngine
+        current_section = self.section
+        while True:
+            # try the current section first
+            val = current_section.get(key)
+            if val is not None and not isinstance(val, Section):
+                break
+            # try "DEFAULT" next
+            val = current_section.get('DEFAULT', {}).get(key)
+            if val is not None and not isinstance(val, Section):
+                break
+            # move up to parent and try again
+            # top-level's parent is itself
+            if current_section.parent is current_section:
+                # reached top level, time to give up
+                break
+            current_section = current_section.parent
+
+        # restore interpolation to previous value before returning
+        self.section.main.interpolation = save_interp
+        if val is None:
+            raise MissingInterpolationOption(key)
+        return val, current_section
+
+
+    def _parse_match(self, match):
+        """Implementation-dependent helper function.
+
+        Will be passed a match object corresponding to the interpolation
+        key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
+        key in the appropriate config file section (using the ``_fetch()``
+        helper function) and return a 3-tuple: (key, value, section)
+
+        ``key`` is the name of the key we're looking for
+        ``value`` is the value found for that key
+        ``section`` is a reference to the section where it was found
+
+        ``key`` and ``section`` should be None if no further
+        interpolation should be performed on the resulting value
+        (e.g., if we interpolated "$$" and returned "$").
+        """
+        raise NotImplementedError()
+    
+
+
+class ConfigParserInterpolation(InterpolationEngine):
+    """Behaves like ConfigParser."""
+    _cookie = '%'
+    _KEYCRE = re.compile(r"%\(([^)]*)\)s")
+
+    def _parse_match(self, match):
+        key = match.group(1)
+        value, section = self._fetch(key)
+        return key, value, section
+
+
+
+class TemplateInterpolation(InterpolationEngine):
+    """Behaves like string.Template."""
+    _cookie = '$'
+    _delimiter = '$'
+    _KEYCRE = re.compile(r"""
+        \$(?:
+          (?P<escaped>\$)              |   # Two $ signs
+          (?P<named>[_a-z][_a-z0-9]*)  |   # $name format
+          {(?P<braced>[^}]*)}              # ${name} format
+        )
+        """, re.IGNORECASE | re.VERBOSE)
+
+    def _parse_match(self, match):
+        # Valid name (in or out of braces): fetch value from section
+        key = match.group('named') or match.group('braced')
+        if key is not None:
+            value, section = self._fetch(key)
+            return key, value, section
+        # Escaped delimiter (e.g., $$): return single delimiter
+        if match.group('escaped') is not None:
+            # Return None for key and section to indicate it's time to stop
+            return None, self._delimiter, None
+        # Anything else: ignore completely, just return it unchanged
+        return None, match.group(), None
+
+
+interpolation_engines = {
+    'configparser': ConfigParserInterpolation,
+    'template': TemplateInterpolation,
+}
+
+
+def __newobj__(cls, *args):
+    # Hack for pickle
+    return cls.__new__(cls, *args) 
+
+class Section(dict):
+    """
+    A dictionary-like object that represents a section in a config file.
+    
+    It does string interpolation if the 'interpolation' attribute
+    of the 'main' object is set to True.
+    
+    Interpolation is tried first from this object, then from the 'DEFAULT'
+    section of this object, next from the parent and its 'DEFAULT' section,
+    and so on until the main object is reached.
+    
+    A Section will behave like an ordered dictionary - following the
+    order of the ``scalars`` and ``sections`` attributes.
+    You can use this to change the order of members.
+    
+    Iteration follows the order: scalars, then sections.
+    """
+
+    
+    def __setstate__(self, state):
+        dict.update(self, state[0])
+        self.__dict__.update(state[1])
+
+    def __reduce__(self):
+        state = (dict(self), self.__dict__)
+        return (__newobj__, (self.__class__,), state)
+    
+    
+    def __init__(self, parent, depth, main, indict=None, name=None):
+        """
+        * parent is the section above
+        * depth is the depth level of this section
+        * main is the main ConfigObj
+        * indict is a dictionary to initialise the section with
+        """
+        if indict is None:
+            indict = {}
+        dict.__init__(self)
+        # used for nesting level *and* interpolation
+        self.parent = parent
+        # used for the interpolation attribute
+        self.main = main
+        # level of nesting depth of this Section
+        self.depth = depth
+        # purely for information
+        self.name = name
+        #
+        self._initialise()
+        # we do this explicitly so that __setitem__ is used properly
+        # (rather than just passing to ``dict.__init__``)
+        for entry, value in indict.iteritems():
+            self[entry] = value
+            
+            
+    def _initialise(self):
+        # the sequence of scalar values in this Section
+        self.scalars = []
+        # the sequence of sections in this Section
+        self.sections = []
+        # for comments :-)
+        self.comments = {}
+        self.inline_comments = {}
+        # the configspec
+        self.configspec = None
+        # for defaults
+        self.defaults = []
+        self.default_values = {}
+        self.extra_values = []
+        self._created = False
+
+
+    def _interpolate(self, key, value):
+        try:
+            # do we already have an interpolation engine?
+            engine = self._interpolation_engine
+        except AttributeError:
+            # not yet: first time running _interpolate(), so pick the engine
+            name = self.main.interpolation
+            if name == True:  # note that "if name:" would be incorrect here
+                # backwards-compatibility: interpolation=True means use default
+                name = DEFAULT_INTERPOLATION
+            name = name.lower()  # so that "Template", "template", etc. all work
+            class_ = interpolation_engines.get(name, None)
+            if class_ is None:
+                # invalid value for self.main.interpolation
+                self.main.interpolation = False
+                return value
+            else:
+                # save reference to engine so we don't have to do this again
+                engine = self._interpolation_engine = class_(self)
+        # let the engine do the actual work
+        return engine.interpolate(key, value)
+
+
+    def __getitem__(self, key):
+        """Fetch the item and do string interpolation."""
+        val = dict.__getitem__(self, key)
+        if self.main.interpolation: 
+            if isinstance(val, basestring):
+                return self._interpolate(key, val)
+            if isinstance(val, list):
+                def _check(entry):
+                    if isinstance(entry, basestring):
+                        return self._interpolate(key, entry)
+                    return entry
+                new = [_check(entry) for entry in val]
+                if new != val:
+                    return new
+        return val
+
+
+    def __setitem__(self, key, value, unrepr=False):
+        """
+        Correctly set a value.
+        
+        Making dictionary values Section instances.
+        (We have to special case 'Section' instances - which are also dicts)
+        
+        Keys must be strings.
+        Values need only be strings (or lists of strings) if
+        ``main.stringify`` is set.
+        
+        ``unrepr`` must be set when setting a value to a dictionary, without
+        creating a new sub-section.
+        """
+        if not isinstance(key, basestring):
+            raise ValueError('The key "%s" is not a string.' % key)
+        
+        # add the comment
+        if key not in self.comments:
+            self.comments[key] = []
+            self.inline_comments[key] = ''
+        # remove the entry from defaults
+        if key in self.defaults:
+            self.defaults.remove(key)
+        #
+        if isinstance(value, Section):
+            if key not in self:
+                self.sections.append(key)
+            dict.__setitem__(self, key, value)
+        elif isinstance(value, dict) and not unrepr:
+            # First create the new depth level,
+            # then create the section
+            if key not in self:
+                self.sections.append(key)
+            new_depth = self.depth + 1
+            dict.__setitem__(
+                self,
+                key,
+                Section(
+                    self,
+                    new_depth,
+                    self.main,
+                    indict=value,
+                    name=key))
+        else:
+            if key not in self:
+                self.scalars.append(key)
+            if not self.main.stringify:
+                if isinstance(value, basestring):
+                    pass
+                elif isinstance(value, (list, tuple)):
+                    for entry in value:
+                        if not isinstance(entry, basestring):
+                            raise TypeError('Value is not a string "%s".' % entry)
+                else:
+                    raise TypeError('Value is not a string "%s".' % value)
+            dict.__setitem__(self, key, value)
+
+
+    def __delitem__(self, key):
+        """Remove items from the sequence when deleting."""
+        dict. __delitem__(self, key)
+        if key in self.scalars:
+            self.scalars.remove(key)
+        else:
+            self.sections.remove(key)
+        del self.comments[key]
+        del self.inline_comments[key]
+
+
+    def get(self, key, default=None):
+        """A version of ``get`` that doesn't bypass string interpolation."""
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+
+    def update(self, indict):
+        """
+        A version of update that uses our ``__setitem__``.
+        """
+        for entry in indict:
+            self[entry] = indict[entry]
+
+
+    def pop(self, key, default=MISSING):
+        """
+        'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        If key is not found, d is returned if given, otherwise KeyError is raised'
+        """
+        try:
+            val = self[key]
+        except KeyError:
+            if default is MISSING:
+                raise
+            val = default
+        else:
+            del self[key]
+        return val
+
+
+    def popitem(self):
+        """Pops the first (key,val)"""
+        sequence = (self.scalars + self.sections)
+        if not sequence:
+            raise KeyError(": 'popitem(): dictionary is empty'")
+        key = sequence[0]
+        val =  self[key]
+        del self[key]
+        return key, val
+
+
+    def clear(self):
+        """
+        A version of clear that also affects scalars/sections
+        Also clears comments and configspec.
+        
+        Leaves other attributes alone :
+            depth/main/parent are not affected
+        """
+        dict.clear(self)
+        self.scalars = []
+        self.sections = []
+        self.comments = {}
+        self.inline_comments = {}
+        self.configspec = None
+        self.defaults = []
+        self.extra_values = []
+
+
+    def setdefault(self, key, default=None):
+        """A version of setdefault that sets sequence if appropriate."""
+        try:
+            return self[key]
+        except KeyError:
+            self[key] = default
+            return self[key]
+
+
+    def items(self):
+        """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
+        return zip((self.scalars + self.sections), self.values())
+
+
+    def keys(self):
+        """D.keys() -> list of D's keys"""
+        return (self.scalars + self.sections)
+
+
+    def values(self):
+        """D.values() -> list of D's values"""
+        return [self[key] for key in (self.scalars + self.sections)]
+
+
+    def iteritems(self):
+        """D.iteritems() -> an iterator over the (key, value) items of D"""
+        return iter(self.items())
+
+
+    def iterkeys(self):
+        """D.iterkeys() -> an iterator over the keys of D"""
+        return iter((self.scalars + self.sections))
+
+    __iter__ = iterkeys
+
+
+    def itervalues(self):
+        """D.itervalues() -> an iterator over the values of D"""
+        return iter(self.values())
+
+
+    def __repr__(self):
+        """x.__repr__() <==> repr(x)"""
+        def _getval(key):
+            try:
+                return self[key]
+            except MissingInterpolationOption:
+                return dict.__getitem__(self, key)
+        return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key))))
+            for key in (self.scalars + self.sections)])
+
+    __str__ = __repr__
+    __str__.__doc__ = "x.__str__() <==> str(x)"
+
+
+    # Extra methods - not in a normal dictionary
+
+    def dict(self):
+        """
+        Return a deepcopy of self as a dictionary.
+        
+        All members that are ``Section`` instances are recursively turned to
+        ordinary dictionaries - by calling their ``dict`` method.
+        
+        >>> n = a.dict()
+        >>> n == a
+        1
+        >>> n is a
+        0
+        """
+        newdict = {}
+        for entry in self:
+            this_entry = self[entry]
+            if isinstance(this_entry, Section):
+                this_entry = this_entry.dict()
+            elif isinstance(this_entry, list):
+                # create a copy rather than a reference
+                this_entry = list(this_entry)
+            elif isinstance(this_entry, tuple):
+                # create a copy rather than a reference
+                this_entry = tuple(this_entry)
+            newdict[entry] = this_entry
+        return newdict
+
+
+    def merge(self, indict):
+        """
+        A recursive update - useful for merging config files.
+        
+        >>> a = '''[section1]
+        ...     option1 = True
+        ...     [[subsection]]
+        ...     more_options = False
+        ...     # end of file'''.splitlines()
+        >>> b = '''# File is user.ini
+        ...     [section1]
+        ...     option1 = False
+        ...     # end of file'''.splitlines()
+        >>> c1 = ConfigObj(b)
+        >>> c2 = ConfigObj(a)
+        >>> c2.merge(c1)
+        >>> c2
+        ConfigObj({'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}})
+        """
+        for key, val in indict.items():
+            if (key in self and isinstance(self[key], dict) and
+                                isinstance(val, dict)):
+                self[key].merge(val)
+            else:   
+                self[key] = val
+
+
+    def rename(self, oldkey, newkey):
+        """
+        Change a keyname to another, without changing position in sequence.
+        
+        Implemented so that transformations can be made on keys,
+        as well as on values. (used by encode and decode)
+        
+        Also renames comments.
+        """
+        if oldkey in self.scalars:
+            the_list = self.scalars
+        elif oldkey in self.sections:
+            the_list = self.sections
+        else:
+            raise KeyError('Key "%s" not found.' % oldkey)
+        pos = the_list.index(oldkey)
+        #
+        val = self[oldkey]
+        dict.__delitem__(self, oldkey)
+        dict.__setitem__(self, newkey, val)
+        the_list.remove(oldkey)
+        the_list.insert(pos, newkey)
+        comm = self.comments[oldkey]
+        inline_comment = self.inline_comments[oldkey]
+        del self.comments[oldkey]
+        del self.inline_comments[oldkey]
+        self.comments[newkey] = comm
+        self.inline_comments[newkey] = inline_comment
+
+
+    def walk(self, function, raise_errors=True,
+            call_on_sections=False, **keywargs):
+        """
+        Walk every member and call a function on the keyword and value.
+        
+        Return a dictionary of the return values
+        
+        If the function raises an exception, raise the errror
+        unless ``raise_errors=False``, in which case set the return value to
+        ``False``.
+        
+        Any unrecognised keyword arguments you pass to walk, will be pased on
+        to the function you pass in.
+        
+        Note: if ``call_on_sections`` is ``True`` then - on encountering a
+        subsection, *first* the function is called for the *whole* subsection,
+        and then recurses into it's members. This means your function must be
+        able to handle strings, dictionaries and lists. This allows you
+        to change the key of subsections as well as for ordinary members. The
+        return value when called on the whole subsection has to be discarded.
+        
+        See  the encode and decode methods for examples, including functions.
+        
+        .. admonition:: caution
+        
+            You can use ``walk`` to transform the names of members of a section
+            but you mustn't add or delete members.
+        
+        >>> config = '''[XXXXsection]
+        ... XXXXkey = XXXXvalue'''.splitlines()
+        >>> cfg = ConfigObj(config)
+        >>> cfg
+        ConfigObj({'XXXXsection': {'XXXXkey': 'XXXXvalue'}})
+        >>> def transform(section, key):
+        ...     val = section[key]
+        ...     newkey = key.replace('XXXX', 'CLIENT1')
+        ...     section.rename(key, newkey)
+        ...     if isinstance(val, (tuple, list, dict)):
+        ...         pass
+        ...     else:
+        ...         val = val.replace('XXXX', 'CLIENT1')
+        ...         section[newkey] = val
+        >>> cfg.walk(transform, call_on_sections=True)
+        {'CLIENT1section': {'CLIENT1key': None}}
+        >>> cfg
+        ConfigObj({'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}})
+        """
+        out = {}
+        # scalars first
+        for i in range(len(self.scalars)):
+            entry = self.scalars[i]
+            try:
+                val = function(self, entry, **keywargs)
+                # bound again in case name has changed
+                entry = self.scalars[i]
+                out[entry] = val
+            except Exception:
+                if raise_errors:
+                    raise
+                else:
+                    entry = self.scalars[i]
+                    out[entry] = False
+        # then sections
+        for i in range(len(self.sections)):
+            entry = self.sections[i]
+            if call_on_sections:
+                try:
+                    function(self, entry, **keywargs)
+                except Exception:
+                    if raise_errors:
+                        raise
+                    else:
+                        entry = self.sections[i]
+                        out[entry] = False
+                # bound again in case name has changed
+                entry = self.sections[i]
+            # previous result is discarded
+            out[entry] = self[entry].walk(
+                function,
+                raise_errors=raise_errors,
+                call_on_sections=call_on_sections,
+                **keywargs)
+        return out
+
+
+    def as_bool(self, key):
+        """
+        Accepts a key as input. The corresponding value must be a string or
+        the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
+        retain compatibility with Python 2.2.
+        
+        If the string is one of  ``True``, ``On``, ``Yes``, or ``1`` it returns 
+        ``True``.
+        
+        If the string is one of  ``False``, ``Off``, ``No``, or ``0`` it returns 
+        ``False``.
+        
+        ``as_bool`` is not case sensitive.
+        
+        Any other input will raise a ``ValueError``.
+        
+        >>> a = ConfigObj()
+        >>> a['a'] = 'fish'
+        >>> a.as_bool('a')
+        Traceback (most recent call last):
+        ValueError: Value "fish" is neither True nor False
+        >>> a['b'] = 'True'
+        >>> a.as_bool('b')
+        1
+        >>> a['b'] = 'off'
+        >>> a.as_bool('b')
+        0
+        """
+        val = self[key]
+        if val == True:
+            return True
+        elif val == False:
+            return False
+        else:
+            try:
+                if not isinstance(val, basestring):
+                    # TODO: Why do we raise a KeyError here?
+                    raise KeyError()
+                else:
+                    return self.main._bools[val.lower()]
+            except KeyError:
+                raise ValueError('Value "%s" is neither True nor False' % val)
+
+
+    def as_int(self, key):
+        """
+        A convenience method which coerces the specified value to an integer.
+        
+        If the value is an invalid literal for ``int``, a ``ValueError`` will
+        be raised.
+        
+        >>> a = ConfigObj()
+        >>> a['a'] = 'fish'
+        >>> a.as_int('a')
+        Traceback (most recent call last):
+        ValueError: invalid literal for int() with base 10: 'fish'
+        >>> a['b'] = '1'
+        >>> a.as_int('b')
+        1
+        >>> a['b'] = '3.2'
+        >>> a.as_int('b')
+        Traceback (most recent call last):
+        ValueError: invalid literal for int() with base 10: '3.2'
+        """
+        return int(self[key])
+
+
+    def as_float(self, key):
+        """
+        A convenience method which coerces the specified value to a float.
+        
+        If the value is an invalid literal for ``float``, a ``ValueError`` will
+        be raised.
+        
+        >>> a = ConfigObj()
+        >>> a['a'] = 'fish'
+        >>> a.as_float('a')
+        Traceback (most recent call last):
+        ValueError: invalid literal for float(): fish
+        >>> a['b'] = '1'
+        >>> a.as_float('b')
+        1.0
+        >>> a['b'] = '3.2'
+        >>> a.as_float('b')
+        3.2000000000000002
+        """
+        return float(self[key])
+    
+    
+    def as_list(self, key):
+        """
+        A convenience method which fetches the specified value, guaranteeing
+        that it is a list.
+        
+        >>> a = ConfigObj()
+        >>> a['a'] = 1
+        >>> a.as_list('a')
+        [1]
+        >>> a['a'] = (1,)
+        >>> a.as_list('a')
+        [1]
+        >>> a['a'] = [1]
+        >>> a.as_list('a')
+        [1]
+        """
+        result = self[key]
+        if isinstance(result, (tuple, list)):
+            return list(result)
+        return [result]
+        
+
+    def restore_default(self, key):
+        """
+        Restore (and return) default value for the specified key.
+        
+        This method will only work for a ConfigObj that was created
+        with a configspec and has been validated.
+        
+        If there is no default value for this key, ``KeyError`` is raised.
+        """
+        default = self.default_values[key]
+        dict.__setitem__(self, key, default)
+        if key not in self.defaults:
+            self.defaults.append(key)
+        return default
+
+    
+    def restore_defaults(self):
+        """
+        Recursively restore default values to all members
+        that have them.
+        
+        This method will only work for a ConfigObj that was created
+        with a configspec and has been validated.
+        
+        It doesn't delete or modify entries without default values.
+        """
+        for key in self.default_values:
+            self.restore_default(key)
+            
+        for section in self.sections:
+            self[section].restore_defaults()
+
+
+class ConfigObj(Section):
+    """An object to read, create, and write config files."""
+
+    _keyword = re.compile(r'''^ # line start
+        (\s*)                   # indentation
+        (                       # keyword
+            (?:".*?")|          # double quotes
+            (?:'.*?')|          # single quotes
+            (?:[^'"=].*?)       # no quotes
+        )
+        \s*=\s*                 # divider
+        (.*)                    # value (including list values and comments)
+        $   # line end
+        ''',
+        re.VERBOSE)
+
+    _sectionmarker = re.compile(r'''^
+        (\s*)                     # 1: indentation
+        ((?:\[\s*)+)              # 2: section marker open
+        (                         # 3: section name open
+            (?:"\s*\S.*?\s*")|    # at least one non-space with double quotes
+            (?:'\s*\S.*?\s*')|    # at least one non-space with single quotes
+            (?:[^'"\s].*?)        # at least one non-space unquoted
+        )                         # section name close
+        ((?:\s*\])+)              # 4: section marker close
+        \s*(\#.*)?                # 5: optional comment
+        $''',
+        re.VERBOSE)
+
+    # this regexp pulls list values out as a single string
+    # or single values and comments
+    # FIXME: this regex adds a '' to the end of comma terminated lists
+    #   workaround in ``_handle_value``
+    _valueexp = re.compile(r'''^
+        (?:
+            (?:
+                (
+                    (?:
+                        (?:
+                            (?:".*?")|              # double quotes
+                            (?:'.*?')|              # single quotes
+                            (?:[^'",\#][^,\#]*?)    # unquoted
+                        )
+                        \s*,\s*                     # comma
+                    )*      # match all list items ending in a comma (if any)
+                )
+                (
+                    (?:".*?")|                      # double quotes
+                    (?:'.*?')|                      # single quotes
+                    (?:[^'",\#\s][^,]*?)|           # unquoted
+                    (?:(?<!,))                      # Empty value
+                )?          # last item in a list - or string value
+            )|
+            (,)             # alternatively a single comma - empty list
+        )
+        \s*(\#.*)?          # optional comment
+        $''',
+        re.VERBOSE)
+
+    # use findall to get the members of a list value
+    _listvalueexp = re.compile(r'''
+        (
+            (?:".*?")|          # double quotes
+            (?:'.*?')|          # single quotes
+            (?:[^'",\#]?.*?)       # unquoted
+        )
+        \s*,\s*                 # comma
+        ''',
+        re.VERBOSE)
+
+    # this regexp is used for the value
+    # when lists are switched off
+    _nolistvalue = re.compile(r'''^
+        (
+            (?:".*?")|          # double quotes
+            (?:'.*?')|          # single quotes
+            (?:[^'"\#].*?)|     # unquoted
+            (?:)                # Empty value
+        )
+        \s*(\#.*)?              # optional comment
+        $''',
+        re.VERBOSE)
+
+    # regexes for finding triple quoted values on one line
+    _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
+    _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
+    _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
+    _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
+
+    _triple_quote = {
+        "'''": (_single_line_single, _multi_line_single),
+        '"""': (_single_line_double, _multi_line_double),
+    }
+
+    # Used by the ``istrue`` Section method
+    _bools = {
+        'yes': True, 'no': False,
+        'on': True, 'off': False,
+        '1': True, '0': False,
+        'true': True, 'false': False,
+        }
+
+
+    def __init__(self, infile=None, options=None, configspec=None, encoding=None,
+                 interpolation=True, raise_errors=False, list_values=True,
+                 create_empty=False, file_error=False, stringify=True,
+                 indent_type=None, default_encoding=None, unrepr=False,
+                 write_empty_values=False, _inspec=False):
+        """
+        Parse a config file or create a config file object.
+        
+        ``ConfigObj(infile=None, configspec=None, encoding=None,
+                    interpolation=True, raise_errors=False, list_values=True,
+                    create_empty=False, file_error=False, stringify=True,
+                    indent_type=None, default_encoding=None, unrepr=False,
+                    write_empty_values=False, _inspec=False)``
+        """
+        self._inspec = _inspec
+        # init the superclass
+        Section.__init__(self, self, 0, self)
+        
+        infile = infile or []
+        
+        _options = {'configspec': configspec,
+                    'encoding': encoding, 'interpolation': interpolation,
+                    'raise_errors': raise_errors, 'list_values': list_values,
+                    'create_empty': create_empty, 'file_error': file_error,
+                    'stringify': stringify, 'indent_type': indent_type,
+                    'default_encoding': default_encoding, 'unrepr': unrepr,
+                    'write_empty_values': write_empty_values}
+
+        if options is None:
+            options = _options
+        else:
+            import warnings
+            warnings.warn('Passing in an options dictionary to ConfigObj() is '
+                          'deprecated. Use **options instead.',
+                          DeprecationWarning, stacklevel=2)
+            
+            # TODO: check the values too.
+            for entry in options:
+                if entry not in OPTION_DEFAULTS:
+                    raise TypeError('Unrecognised option "%s".' % entry)
+            for entry, value in OPTION_DEFAULTS.items():
+                if entry not in options:
+                    options[entry] = value
+                keyword_value = _options[entry]
+                if value != keyword_value:
+                    options[entry] = keyword_value
+        
+        # XXXX this ignores an explicit list_values = True in combination
+        # with _inspec. The user should *never* do that anyway, but still...
+        if _inspec:
+            options['list_values'] = False
+        
+        self._initialise(options)
+        configspec = options['configspec']
+        self._original_configspec = configspec
+        self._load(infile, configspec)
+        
+        
+    def _load(self, infile, configspec):
+        if isinstance(infile, basestring):
+            self.filename = infile
+            if os.path.isfile(infile):
+                h = open(infile, 'rb')
+                infile = h.read() or []
+                h.close()
+            elif self.file_error:
+                # raise an error if the file doesn't exist
+                raise IOError('Config file not found: "%s".' % self.filename)
+            else:
+                # file doesn't already exist
+                if self.create_empty:
+                    # this is a good test that the filename specified
+                    # isn't impossible - like on a non-existent device
+                    h = open(infile, 'w')
+                    h.write('')
+                    h.close()
+                infile = []
+                
+        elif isinstance(infile, (list, tuple)):
+            infile = list(infile)
+            
+        elif isinstance(infile, dict):
+            # initialise self
+            # the Section class handles creating subsections
+            if isinstance(infile, ConfigObj):
+                # get a copy of our ConfigObj
+                def set_section(in_section, this_section):
+                    for entry in in_section.scalars:
+                        this_section[entry] = in_section[entry]
+                    for section in in_section.sections:
+                        this_section[section] = {}
+                        set_section(in_section[section], this_section[section])
+                set_section(infile, self)
+                
+            else:
+                for entry in infile:
+                    self[entry] = infile[entry]
+            del self._errors
+            
+            if configspec is not None:
+                self._handle_configspec(configspec)
+            else:
+                self.configspec = None
+            return
+        
+        elif getattr(infile, 'read', MISSING) is not MISSING:
+            # This supports file like objects
+            infile = infile.read() or []
+            # needs splitting into lines - but needs doing *after* decoding
+            # in case it's not an 8 bit encoding
+        else:
+            raise TypeError('infile must be a filename, file like object, or list of lines.')
+        
+        if infile:
+            # don't do it for the empty ConfigObj
+            infile = self._handle_bom(infile)
+            # infile is now *always* a list
+            #
+            # Set the newlines attribute (first line ending it finds)
+            # and strip trailing '\n' or '\r' from lines
+            for line in infile:
+                if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
+                    continue
+                for end in ('\r\n', '\n', '\r'):
+                    if line.endswith(end):
+                        self.newlines = end
+                        break
+                break
+
+            infile = [line.rstrip('\r\n') for line in infile]
+            
+        self._parse(infile)
+        # if we had any errors, now is the time to raise them
+        if self._errors:
+            info = "at line %s." % self._errors[0].line_number
+            if len(self._errors) > 1:
+                msg = "Parsing failed with several errors.\nFirst error %s" % info
+                error = ConfigObjError(msg)
+            else:
+                error = self._errors[0]
+            # set the errors attribute; it's a list of tuples:
+            # (error_type, message, line_number)
+            error.errors = self._errors
+            # set the config attribute
+            error.config = self
+            raise error
+        # delete private attributes
+        del self._errors
+        
+        if configspec is None:
+            self.configspec = None
+        else:
+            self._handle_configspec(configspec)
+    
+    
+    def _initialise(self, options=None):
+        if options is None:
+            options = OPTION_DEFAULTS
+            
+        # initialise a few variables
+        self.filename = None
+        self._errors = []
+        self.raise_errors = options['raise_errors']
+        self.interpolation = options['interpolation']
+        self.list_values = options['list_values']
+        self.create_empty = options['create_empty']
+        self.file_error = options['file_error']
+        self.stringify = options['stringify']
+        self.indent_type = options['indent_type']
+        self.encoding = options['encoding']
+        self.default_encoding = options['default_encoding']
+        self.BOM = False
+        self.newlines = None
+        self.write_empty_values = options['write_empty_values']
+        self.unrepr = options['unrepr']
+        
+        self.initial_comment = []
+        self.final_comment = []
+        self.configspec = None
+        
+        if self._inspec:
+            self.list_values = False
+        
+        # Clear section attributes as well
+        Section._initialise(self)
+        
+        
+    def __repr__(self):
+        def _getval(key):
+            try:
+                return self[key]
+            except MissingInterpolationOption:
+                return dict.__getitem__(self, key)
+        return ('ConfigObj({%s})' % 
+                ', '.join([('%s: %s' % (repr(key), repr(_getval(key)))) 
+                for key in (self.scalars + self.sections)]))
+    
+    
+    def _handle_bom(self, infile):
+        """
+        Handle any BOM, and decode if necessary.
+        
+        If an encoding is specified, that *must* be used - but the BOM should
+        still be removed (and the BOM attribute set).
+        
+        (If the encoding is wrongly specified, then a BOM for an alternative
+        encoding won't be discovered or removed.)
+        
+        If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
+        removed. The BOM attribute will be set. UTF16 will be decoded to
+        unicode.
+        
+        NOTE: This method must not be called with an empty ``infile``.
+        
+        Specifying the *wrong* encoding is likely to cause a
+        ``UnicodeDecodeError``.
+        
+        ``infile`` must always be returned as a list of lines, but may be
+        passed in as a single string.
+        """
+        if ((self.encoding is not None) and
+            (self.encoding.lower() not in BOM_LIST)):
+            # No need to check for a BOM
+            # the encoding specified doesn't have one
+            # just decode
+            return self._decode(infile, self.encoding)
+        
+        if isinstance(infile, (list, tuple)):
+            line = infile[0]
+        else:
+            line = infile
+        if self.encoding is not None:
+            # encoding explicitly supplied
+            # And it could have an associated BOM
+            # TODO: if encoding is just UTF16 - we ought to check for both
+            # TODO: big endian and little endian versions.
+            enc = BOM_LIST[self.encoding.lower()]
+            if enc == 'utf_16':
+                # For UTF16 we try big endian and little endian
+                for BOM, (encoding, final_encoding) in BOMS.items():
+                    if not final_encoding:
+                        # skip UTF8
+                        continue
+                    if infile.startswith(BOM):
+                        ### BOM discovered
+                        ##self.BOM = True
+                        # Don't need to remove BOM
+                        return self._decode(infile, encoding)
+                    
+                # If we get this far, will *probably* raise a DecodeError
+                # As it doesn't appear to start with a BOM
+                return self._decode(infile, self.encoding)
+            
+            # Must be UTF8
+            BOM = BOM_SET[enc]
+            if not line.startswith(BOM):
+                return self._decode(infile, self.encoding)
+            
+            newline = line[len(BOM):]
+            
+            # BOM removed
+            if isinstance(infile, (list, tuple)):
+                infile[0] = newline
+            else:
+                infile = newline
+            self.BOM = True
+            return self._decode(infile, self.encoding)
+        
+        # No encoding specified - so we need to check for UTF8/UTF16
+        for BOM, (encoding, final_encoding) in BOMS.items():
+            if not line.startswith(BOM):
+                continue
+            else:
+                # BOM discovered
+                self.encoding = final_encoding
+                if not final_encoding:
+                    self.BOM = True
+                    # UTF8
+                    # remove BOM
+                    newline = line[len(BOM):]
+                    if isinstance(infile, (list, tuple)):
+                        infile[0] = newline
+                    else:
+                        infile = newline
+                    # UTF8 - don't decode
+                    if isinstance(infile, basestring):
+                        return infile.splitlines(True)
+                    else:
+                        return infile
+                # UTF16 - have to decode
+                return self._decode(infile, encoding)
+            
+        # No BOM discovered and no encoding specified, just return
+        if isinstance(infile, basestring):
+            # infile read from a file will be a single string
+            return infile.splitlines(True)
+        return infile
+
+
+    def _a_to_u(self, aString):
+        """Decode ASCII strings to unicode if a self.encoding is specified."""
+        if self.encoding:
+            return aString.decode('ascii')
+        else:
+            return aString
+
+
+    def _decode(self, infile, encoding):
+        """
+        Decode infile to unicode. Using the specified encoding.
+        
+        if is a string, it also needs converting to a list.
+        """
+        if isinstance(infile, basestring):
+            # can't be unicode
+            # NOTE: Could raise a ``UnicodeDecodeError``
+            return infile.decode(encoding).splitlines(True)
+        for i, line in enumerate(infile):
+            if not isinstance(line, unicode):
+                # NOTE: The isinstance test here handles mixed lists of unicode/string
+                # NOTE: But the decode will break on any non-string values
+                # NOTE: Or could raise a ``UnicodeDecodeError``
+                infile[i] = line.decode(encoding)
+        return infile
+
+
+    def _decode_element(self, line):
+        """Decode element to unicode if necessary."""
+        if not self.encoding:
+            return line
+        if isinstance(line, str) and self.default_encoding:
+            return line.decode(self.default_encoding)
+        return line
+
+
+    def _str(self, value):
+        """
+        Used by ``stringify`` within validate, to turn non-string values
+        into strings.
+        """
+        if not isinstance(value, basestring):
+            return str(value)
+        else:
+            return value
+
+
+    def _parse(self, infile):
+        """Actually parse the config file."""
+        temp_list_values = self.list_values
+        if self.unrepr:
+            self.list_values = False
+            
+        comment_list = []
+        done_start = False
+        this_section = self
+        maxline = len(infile) - 1
+        cur_index = -1
+        reset_comment = False
+        
+        while cur_index < maxline:
+            if reset_comment:
+                comment_list = []
+            cur_index += 1
+            line = infile[cur_index]
+            sline = line.strip()
+            # do we have anything on the line ?
+            if not sline or sline.startswith('#'):
+                reset_comment = False
+                comment_list.append(line)
+                continue
+            
+            if not done_start:
+                # preserve initial comment
+                self.initial_comment = comment_list
+                comment_list = []
+                done_start = True
+                
+            reset_comment = True
+            # first we check if it's a section marker
+            mat = self._sectionmarker.match(line)
+            if mat is not None:
+                # is a section line
+                (indent, sect_open, sect_name, sect_close, comment) = mat.groups()
+                if indent and (self.indent_type is None):
+                    self.indent_type = indent
+                cur_depth = sect_open.count('[')
+                if cur_depth != sect_close.count(']'):
+                    self._handle_error("Cannot compute the section depth at line %s.",
+                                       NestingError, infile, cur_index)
+                    continue
+                
+                if cur_depth < this_section.depth:
+                    # the new section is dropping back to a previous level
+                    try:
+                        parent = self._match_depth(this_section,
+                                                   cur_depth).parent
+                    except SyntaxError:
+                        self._handle_error("Cannot compute nesting level at line %s.",
+                                           NestingError, infile, cur_index)
+                        continue
+                elif cur_depth == this_section.depth:
+                    # the new section is a sibling of the current section
+                    parent = this_section.parent
+                elif cur_depth == this_section.depth + 1:
+                    # the new section is a child the current section
+                    parent = this_section
+                else:
+                    self._handle_error("Section too nested at line %s.",
+                                       NestingError, infile, cur_index)
+                    
+                sect_name = self._unquote(sect_name)
+                if sect_name in parent:
+                    self._handle_error('Duplicate section name at line %s.',
+                                       DuplicateError, infile, cur_index)
+                    continue
+                
+                # create the new section
+                this_section = Section(
+                    parent,
+                    cur_depth,
+                    self,
+                    name=sect_name)
+                parent[sect_name] = this_section
+                parent.inline_comments[sect_name] = comment
+                parent.comments[sect_name] = comment_list
+                continue
+            #
+            # it's not a section marker,
+            # so it should be a valid ``key = value`` line
+            mat = self._keyword.match(line)
+            if mat is None:
+                # it neither matched as a keyword
+                # or a section marker
+                self._handle_error(
+                    'Invalid line at line "%s".',
+                    ParseError, infile, cur_index)
+            else:
+                # is a keyword value
+                # value will include any inline comment
+                (indent, key, value) = mat.groups()
+                if indent and (self.indent_type is None):
+                    self.indent_type = indent
+                # check for a multiline value
+                if value[:3] in ['"""', "'''"]:
+                    try:
+                        value, comment, cur_index = self._multiline(
+                            value, infile, cur_index, maxline)
+                    except SyntaxError:
+                        self._handle_error(
+                            'Parse error in value at line %s.',
+                            ParseError, infile, cur_index)
+                        continue
+                    else:
+                        if self.unrepr:
+                            comment = ''
+                            try:
+                                value = unrepr(value)
+                            except Exception, e:
+                                if type(e) == UnknownType:
+                                    msg = 'Unknown name or type in value at line %s.'
+                                else:
+                                    msg = 'Parse error in value at line %s.'
+                                self._handle_error(msg, UnreprError, infile,
+                                    cur_index)
+                                continue
+                else:
+                    if self.unrepr:
+                        comment = ''
+                        try:
+                            value = unrepr(value)
+                        except Exception, e:
+                            if isinstance(e, UnknownType):
+                                msg = 'Unknown name or type in value at line %s.'
+                            else:
+                                msg = 'Parse error in value at line %s.'
+                            self._handle_error(msg, UnreprError, infile,
+                                cur_index)
+                            continue
+                    else:
+                        # extract comment and lists
+                        try:
+                            (value, comment) = self._handle_value(value)
+                        except SyntaxError:
+                            self._handle_error(
+                                'Parse error in value at line %s.',
+                                ParseError, infile, cur_index)
+                            continue
+                #
+                key = self._unquote(key)
+                if key in this_section:
+                    self._handle_error(
+                        'Duplicate keyword name at line %s.',
+                        DuplicateError, infile, cur_index)
+                    continue
+                # add the key.
+                # we set unrepr because if we have got this far we will never
+                # be creating a new section
+                this_section.__setitem__(key, value, unrepr=True)
+                this_section.inline_comments[key] = comment
+                this_section.comments[key] = comment_list
+                continue
+        #
+        if self.indent_type is None:
+            # no indentation used, set the type accordingly
+            self.indent_type = ''
+
+        # preserve the final comment
+        if not self and not self.initial_comment:
+            self.initial_comment = comment_list
+        elif not reset_comment:
+            self.final_comment = comment_list
+        self.list_values = temp_list_values
+
+
+    def _match_depth(self, sect, depth):
+        """
+        Given a section and a depth level, walk back through the sections
+        parents to see if the depth level matches a previous section.
+        
+        Return a reference to the right section,
+        or raise a SyntaxError.
+        """
+        while depth < sect.depth:
+            if sect is sect.parent:
+                # we've reached the top level already
+                raise SyntaxError()
+            sect = sect.parent
+        if sect.depth == depth:
+            return sect
+        # shouldn't get here
+        raise SyntaxError()
+
+
+    def _handle_error(self, text, ErrorClass, infile, cur_index):
+        """
+        Handle an error according to the error settings.
+        
+        Either raise the error or store it.
+        The error will have occured at ``cur_index``
+        """
+        line = infile[cur_index]
+        cur_index += 1
+        message = text % cur_index
+        error = ErrorClass(message, cur_index, line)
+        if self.raise_errors:
+            # raise the error - parsing stops here
+            raise error
+        # store the error
+        # reraise when parsing has finished
+        self._errors.append(error)
+
+
+    def _unquote(self, value):
+        """Return an unquoted version of a value"""
+        if not value:
+            # should only happen during parsing of lists
+            raise SyntaxError
+        if (value[0] == value[-1]) and (value[0] in ('"', "'")):
+            value = value[1:-1]
+        return value
+
+
+    def _quote(self, value, multiline=True):
+        """
+        Return a safely quoted version of a value.
+        
+        Raise a ConfigObjError if the value cannot be safely quoted.
+        If multiline is ``True`` (default) then use triple quotes
+        if necessary.
+        
+        * Don't quote values that don't need it.
+        * Recursively quote members of a list and return a comma joined list.
+        * Multiline is ``False`` for lists.
+        * Obey list syntax for empty and single member lists.
+        
+        If ``list_values=False`` then the value is only quoted if it contains
+        a ``\\n`` (is multiline) or '#'.
+        
+        If ``write_empty_values`` is set, and the value is an empty string, it
+        won't be quoted.
+        """
+        if multiline and self.write_empty_values and value == '':
+            # Only if multiline is set, so that it is used for values not
+            # keys, and not values that are part of a list
+            return ''
+        
+        if multiline and isinstance(value, (list, tuple)):
+            if not value:
+                return ','
+            elif len(value) == 1:
+                return self._quote(value[0], multiline=False) + ','
+            return ', '.join([self._quote(val, multiline=False)
+                for val in value])
+        if not isinstance(value, basestring):
+            if self.stringify:
+                value = str(value)
+            else:
+                raise TypeError('Value "%s" is not a string.' % value)
+
+        if not value:
+            return '""'
+        
+        no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
+        need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
+        hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
+        check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
+        
+        if check_for_single:
+            if not self.list_values:
+                # we don't quote if ``list_values=False``
+                quot = noquot
+            # for normal values either single or double quotes will do
+            elif '\n' in value:
+                # will only happen if multiline is off - e.g. '\n' in key
+                raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
+            elif ((value[0] not in wspace_plus) and
+                    (value[-1] not in wspace_plus) and
+                    (',' not in value)):
+                quot = noquot
+            else:
+                quot = self._get_single_quote(value)
+        else:
+            # if value has '\n' or "'" *and* '"', it will need triple quotes
+            quot = self._get_triple_quote(value)
+        
+        if quot == noquot and '#' in value and self.list_values:
+            quot = self._get_single_quote(value)
+                
+        return quot % value
+    
+    
+    def _get_single_quote(self, value):
+        if ("'" in value) and ('"' in value):
+            raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
+        elif '"' in value:
+            quot = squot
+        else:
+            quot = dquot
+        return quot
+    
+    
+    def _get_triple_quote(self, value):
+        if (value.find('"""') != -1) and (value.find("'''") != -1):
+            raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
+        if value.find('"""') == -1:
+            quot = tdquot
+        else:
+            quot = tsquot 
+        return quot
+
+
+    def _handle_value(self, value):
+        """
+        Given a value string, unquote, remove comment,
+        handle lists. (including empty and single member lists)
+        """
+        if self._inspec:
+            # Parsing a configspec so don't handle comments
+            return (value, '')
+        # do we look for lists in values ?
+        if not self.list_values:
+            mat = self._nolistvalue.match(value)
+            if mat is None:
+                raise SyntaxError()
+            # NOTE: we don't unquote here
+            return mat.groups()
+        #
+        mat = self._valueexp.match(value)
+        if mat is None:
+            # the value is badly constructed, probably badly quoted,
+            # or an invalid list
+            raise SyntaxError()
+        (list_values, single, empty_list, comment) = mat.groups()
+        if (list_values == '') and (single is None):
+            # change this if you want to accept empty values
+            raise SyntaxError()
+        # NOTE: note there is no error handling from here if the regex
+        # is wrong: then incorrect values will slip through
+        if empty_list is not None:
+            # the single comma - meaning an empty list
+            return ([], comment)
+        if single is not None:
+            # handle empty values
+            if list_values and not single:
+                # FIXME: the '' is a workaround because our regex now matches
+                #   '' at the end of a list if it has a trailing comma
+                single = None
+            else:
+                single = single or '""'
+                single = self._unquote(single)
+        if list_values == '':
+            # not a list value
+            return (single, comment)
+        the_list = self._listvalueexp.findall(list_values)
+        the_list = [self._unquote(val) for val in the_list]
+        if single is not None:
+            the_list += [single]
+        return (the_list, comment)
+
+
+    def _multiline(self, value, infile, cur_index, maxline):
+        """Extract the value, where we are in a multiline situation."""
+        quot = value[:3]
+        newvalue = value[3:]
+        single_line = self._triple_quote[quot][0]
+        multi_line = self._triple_quote[quot][1]
+        mat = single_line.match(value)
+        if mat is not None:
+            retval = list(mat.groups())
+            retval.append(cur_index)
+            return retval
+        elif newvalue.find(quot) != -1:
+            # somehow the triple quote is missing
+            raise SyntaxError()
+        #
+        while cur_index < maxline:
+            cur_index += 1
+            newvalue += '\n'
+            line = infile[cur_index]
+            if line.find(quot) == -1:
+                newvalue += line
+            else:
+                # end of multiline, process it
+                break
+        else:
+            # we've got to the end of the config, oops...
+            raise SyntaxError()
+        mat = multi_line.match(line)
+        if mat is None:
+            # a badly formed line
+            raise SyntaxError()
+        (value, comment) = mat.groups()
+        return (newvalue + value, comment, cur_index)
+
+
+    def _handle_configspec(self, configspec):
+        """Parse the configspec."""
+        # FIXME: Should we check that the configspec was created with the 
+        #        correct settings ? (i.e. ``list_values=False``)
+        if not isinstance(configspec, ConfigObj):
+            try:
+                configspec = ConfigObj(configspec,
+                                       raise_errors=True,
+                                       file_error=True,
+                                       _inspec=True)
+            except ConfigObjError, e:
+                # FIXME: Should these errors have a reference
+                #        to the already parsed ConfigObj ?
+                raise ConfigspecError('Parsing configspec failed: %s' % e)
+            except IOError, e:
+                raise IOError('Reading configspec failed: %s' % e)
+        
+        self.configspec = configspec
+            
+
+        
+    def _set_configspec(self, section, copy):
+        """
+        Called by validate. Handles setting the configspec on subsections
+        including sections to be validated by __many__
+        """
+        configspec = section.configspec
+        many = configspec.get('__many__')
+        if isinstance(many, dict):
+            for entry in section.sections:
+                if entry not in configspec:
+                    section[entry].configspec = many
+                    
+        for entry in configspec.sections:
+            if entry == '__many__':
+                continue
+            if entry not in section:
+                section[entry] = {}
+                section[entry]._created = True
+                if copy:
+                    # copy comments
+                    section.comments[entry] = configspec.comments.get(entry, [])
+                    section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
+                
+            # Could be a scalar when we expect a section
+            if isinstance(section[entry], Section):
+                section[entry].configspec = configspec[entry]
+                        
+
+    def _write_line(self, indent_string, entry, this_entry, comment):
+        """Write an individual line, for the write method"""
+        # NOTE: the calls to self._quote here handles non-StringType values.
+        if not self.unrepr:
+            val = self._decode_element(self._quote(this_entry))
+        else:
+            val = repr(this_entry)
+        return '%s%s%s%s%s' % (indent_string,
+                               self._decode_element(self._quote(entry, multiline=False)),
+                               self._a_to_u(' = '),
+                               val,
+                               self._decode_element(comment))
+
+
+    def _write_marker(self, indent_string, depth, entry, comment):
+        """Write a section marker line"""
+        return '%s%s%s%s%s' % (indent_string,
+                               self._a_to_u('[' * depth),
+                               self._quote(self._decode_element(entry), multiline=False),
+                               self._a_to_u(']' * depth),
+                               self._decode_element(comment))
+
+
+    def _handle_comment(self, comment):
+        """Deal with a comment."""
+        if not comment:
+            return ''
+        start = self.indent_type
+        if not comment.startswith('#'):
+            start += self._a_to_u(' # ')
+        return (start + comment)
+
+
+    # Public methods
+
+    def write(self, outfile=None, section=None):
+        """
+        Write the current ConfigObj as a file
+        
+        tekNico: FIXME: use StringIO instead of real files
+        
+        >>> filename = a.filename
+        >>> a.filename = 'test.ini'
+        >>> a.write()
+        >>> a.filename = filename
+        >>> a == ConfigObj('test.ini', raise_errors=True)
+        1
+        >>> import os
+        >>> os.remove('test.ini')
+        """
+        if self.indent_type is None:
+            # this can be true if initialised from a dictionary
+            self.indent_type = DEFAULT_INDENT_TYPE
+            
+        out = []
+        cs = self._a_to_u('#')
+        csp = self._a_to_u('# ')
+        if section is None:
+            int_val = self.interpolation
+            self.interpolation = False
+            section = self
+            for line in self.initial_comment:
+                line = self._decode_element(line)
+                stripped_line = line.strip()
+                if stripped_line and not stripped_line.startswith(cs):
+                    line = csp + line
+                out.append(line)
+                
+        indent_string = self.indent_type * section.depth
+        for entry in (section.scalars + section.sections):
+            if entry in section.defaults:
+                # don't write out default values
+                continue
+            for comment_line in section.comments[entry]:
+                comment_line = self._decode_element(comment_line.lstrip())
+                if comment_line and not comment_line.startswith(cs):
+                    comment_line = csp + comment_line
+                out.append(indent_string + comment_line)
+            this_entry = section[entry]
+            comment = self._handle_comment(section.inline_comments[entry])
+            
+            if isinstance(this_entry, dict):
+                # a section
+                out.append(self._write_marker(
+                    indent_string,
+                    this_entry.depth,
+                    entry,
+                    comment))
+                out.extend(self.write(section=this_entry))
+            else:
+                out.append(self._write_line(
+                    indent_string,
+                    entry,
+                    this_entry,
+                    comment))
+                
+        if section is self:
+            for line in self.final_comment:
+                line = self._decode_element(line)
+                stripped_line = line.strip()
+                if stripped_line and not stripped_line.startswith(cs):
+                    line = csp + line
+                out.append(line)
+            self.interpolation = int_val
+            
+        if section is not self:
+            return out
+        
+        if (self.filename is None) and (outfile is None):
+            # output a list of lines
+            # might need to encode
+            # NOTE: This will *screw* UTF16, each line will start with the BOM
+            if self.encoding:
+                out = [l.encode(self.encoding) for l in out]
+            if (self.BOM and ((self.encoding is None) or
+                (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
+                # Add the UTF8 BOM
+                if not out:
+                    out.append('')
+                out[0] = BOM_UTF8 + out[0]
+            return out
+        
+        # Turn the list to a string, joined with correct newlines
+        newline = self.newlines or os.linesep
+        if (getattr(outfile, 'mode', None) is not None and outfile.mode == 'w'
+            and sys.platform == 'win32' and newline == '\r\n'):
+            # Windows specific hack to avoid writing '\r\r\n'
+            newline = '\n'
+        output = self._a_to_u(newline).join(out)
+        if self.encoding:
+            output = output.encode(self.encoding)
+        if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
+            # Add the UTF8 BOM
+            output = BOM_UTF8 + output
+            
+        if not output.endswith(newline):
+            output += newline
+        if outfile is not None:
+            outfile.write(output)
+        else:
+            h = open(self.filename, 'wb')
+            h.write(output)
+            h.close()
+
+
+    def validate(self, validator, preserve_errors=False, copy=False,
+                 section=None):
+        """
+        Test the ConfigObj against a configspec.
+        
+        It uses the ``validator`` object from *validate.py*.
+        
+        To run ``validate`` on the current ConfigObj, call: ::
+        
+            test = config.validate(validator)
+        
+        (Normally having previously passed in the configspec when the ConfigObj
+        was created - you can dynamically assign a dictionary of checks to the
+        ``configspec`` attribute of a section though).
+        
+        It returns ``True`` if everything passes, or a dictionary of
+        pass/fails (True/False). If every member of a subsection passes, it
+        will just have the value ``True``. (It also returns ``False`` if all
+        members fail).
+        
+        In addition, it converts the values from strings to their native
+        types if their checks pass (and ``stringify`` is set).
+        
+        If ``preserve_errors`` is ``True`` (``False`` is default) then instead
+        of a marking a fail with a ``False``, it will preserve the actual
+        exception object. This can contain info about the reason for failure.
+        For example the ``VdtValueTooSmallError`` indicates that the value
+        supplied was too small. If a value (or section) is missing it will
+        still be marked as ``False``.
+        
+        You must have the validate module to use ``preserve_errors=True``.
+        
+        You can then use the ``flatten_errors`` function to turn your nested
+        results dictionary into a flattened list of failures - useful for
+        displaying meaningful error messages.
+        """
+        if section is None:
+            if self.configspec is None:
+                raise ValueError('No configspec supplied.')
+            if preserve_errors:
+                # We do this once to remove a top level dependency on the validate module
+                # Which makes importing configobj faster
+                from validate import VdtMissingValue
+                self._vdtMissingValue = VdtMissingValue
+                
+            section = self
+
+            if copy:
+                section.initial_comment = section.configspec.initial_comment
+                section.final_comment = section.configspec.final_comment
+                section.encoding = section.configspec.encoding
+                section.BOM = section.configspec.BOM
+                section.newlines = section.configspec.newlines
+                section.indent_type = section.configspec.indent_type
+            
+        #
+        # section.default_values.clear() #??
+        configspec = section.configspec
+        self._set_configspec(section, copy)
+
+        
+        def validate_entry(entry, spec, val, missing, ret_true, ret_false):
+            section.default_values.pop(entry, None)
+                
+            try:
+                section.default_values[entry] = validator.get_default_value(configspec[entry])
+            except (KeyError, AttributeError, validator.baseErrorClass):
+                # No default, bad default or validator has no 'get_default_value'
+                # (e.g. SimpleVal)
+                pass
+            
+            try:
+                check = validator.check(spec,
+                                        val,
+                                        missing=missing
+                                        )
+            except validator.baseErrorClass, e:
+                if not preserve_errors or isinstance(e, self._vdtMissingValue):
+                    out[entry] = False
+                else:
+                    # preserve the error
+                    out[entry] = e
+                    ret_false = False
+                ret_true = False
+            else:
+                ret_false = False
+                out[entry] = True
+                if self.stringify or missing:
+                    # if we are doing type conversion
+                    # or the value is a supplied default
+                    if not self.stringify:
+                        if isinstance(check, (list, tuple)):
+                            # preserve lists
+                            check = [self._str(item) for item in check]
+                        elif missing and check is None:
+                            # convert the None from a default to a ''
+                            check = ''
+                        else:
+                            check = self._str(check)
+                    if (check != val) or missing:
+                        section[entry] = check
+                if not copy and missing and entry not in section.defaults:
+                    section.defaults.append(entry)
+            return ret_true, ret_false
+        
+        #
+        out = {}
+        ret_true = True
+        ret_false = True
+        
+        unvalidated = [k for k in section.scalars if k not in configspec]
+        incorrect_sections = [k for k in configspec.sections if k in section.scalars]        
+        incorrect_scalars = [k for k in configspec.scalars if k in section.sections]
+        
+        for entry in configspec.scalars:
+            if entry in ('__many__', '___many___'):
+                # reserved names
+                continue
+            if (not entry in section.scalars) or (entry in section.defaults):
+                # missing entries
+                # or entries from defaults
+                missing = True
+                val = None
+                if copy and entry not in section.scalars:
+                    # copy comments
+                    section.comments[entry] = (
+                        configspec.comments.get(entry, []))
+                    section.inline_comments[entry] = (
+                        configspec.inline_comments.get(entry, ''))
+                #
+            else:
+                missing = False
+                val = section[entry]
+            
+            ret_true, ret_false = validate_entry(entry, configspec[entry], val, 
+                                                 missing, ret_true, ret_false)
+        
+        many = None
+        if '__many__' in configspec.scalars:
+            many = configspec['__many__']
+        elif '___many___' in configspec.scalars:
+            many = configspec['___many___']
+        
+        if many is not None:
+            for entry in unvalidated:
+                val = section[entry]
+                ret_true, ret_false = validate_entry(entry, many, val, False,
+                                                     ret_true, ret_false)
+            unvalidated = []
+
+        for entry in incorrect_scalars:
+            ret_true = False
+            if not preserve_errors:
+                out[entry] = False
+            else:
+                ret_false = False
+                msg = 'Value %r was provided as a section' % entry
+                out[entry] = validator.baseErrorClass(msg)
+        for entry in incorrect_sections:
+            ret_true = False
+            if not preserve_errors:
+                out[entry] = False
+            else:
+                ret_false = False
+                msg = 'Section %r was provided as a single value' % entry
+                out[entry] = validator.baseErrorClass(msg)
+                
+        # Missing sections will have been created as empty ones when the
+        # configspec was read.
+        for entry in section.sections:
+            # FIXME: this means DEFAULT is not copied in copy mode
+            if section is self and entry == 'DEFAULT':
+                continue
+            if section[entry].configspec is None:
+                unvalidated.append(entry)
+                continue
+            if copy:
+                section.comments[entry] = configspec.comments.get(entry, [])
+                section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
+            check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry])
+            out[entry] = check
+            if check == False:
+                ret_true = False
+            elif check == True:
+                ret_false = False
+            else:
+                ret_true = False
+        
+        section.extra_values = unvalidated
+        if preserve_errors and not section._created:
+            # If the section wasn't created (i.e. it wasn't missing)
+            # then we can't return False, we need to preserve errors
+            ret_false = False
+        #
+        if ret_false and preserve_errors and out:
+            # If we are preserving errors, but all
+            # the failures are from missing sections / values
+            # then we can return False. Otherwise there is a
+            # real failure that we need to preserve.
+            ret_false = not any(out.values())
+        if ret_true:
+            return True
+        elif ret_false:
+            return False
+        return out
+
+
+    def reset(self):
+        """Clear ConfigObj instance and restore to 'freshly created' state."""
+        self.clear()
+        self._initialise()
+        # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
+        #        requires an empty dictionary
+        self.configspec = None
+        # Just to be sure ;-)
+        self._original_configspec = None
+        
+        
+    def reload(self):
+        """
+        Reload a ConfigObj from file.
+        
+        This method raises a ``ReloadError`` if the ConfigObj doesn't have
+        a filename attribute pointing to a file.
+        """
+        if not isinstance(self.filename, basestring):
+            raise ReloadError()
+
+        filename = self.filename
+        current_options = {}
+        for entry in OPTION_DEFAULTS:
+            if entry == 'configspec':
+                continue
+            current_options[entry] = getattr(self, entry)
+            
+        configspec = self._original_configspec
+        current_options['configspec'] = configspec
+            
+        self.clear()
+        self._initialise(current_options)
+        self._load(filename, configspec)
+        
+
+
+class SimpleVal(object):
+    """
+    A simple validator.
+    Can be used to check that all members expected are present.
+    
+    To use it, provide a configspec with all your members in (the value given
+    will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
+    method of your ``ConfigObj``. ``validate`` will return ``True`` if all
+    members are present, or a dictionary with True/False meaning
+    present/missing. (Whole missing sections will be replaced with ``False``)
+    """
+    
+    def __init__(self):
+        self.baseErrorClass = ConfigObjError
+    
+    def check(self, check, member, missing=False):
+        """A dummy check method, always returns the value unchanged."""
+        if missing:
+            raise self.baseErrorClass()
+        return member
+
+
+def flatten_errors(cfg, res, levels=None, results=None):
+    """
+    An example function that will turn a nested dictionary of results
+    (as returned by ``ConfigObj.validate``) into a flat list.
+    
+    ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
+    dictionary returned by ``validate``.
+    
+    (This is a recursive function, so you shouldn't use the ``levels`` or
+    ``results`` arguments - they are used by the function.)
+    
+    Returns a list of keys that failed. Each member of the list is a tuple::
+    
+        ([list of sections...], key, result)
+    
+    If ``validate`` was called with ``preserve_errors=False`` (the default)
+    then ``result`` will always be ``False``.
+
+    *list of sections* is a flattened list of sections that the key was found
+    in.
+    
+    If the section was missing (or a section was expected and a scalar provided
+    - or vice-versa) then key will be ``None``.
+    
+    If the value (or section) was missing then ``result`` will be ``False``.
+    
+    If ``validate`` was called with ``preserve_errors=True`` and a value
+    was present, but failed the check, then ``result`` will be the exception
+    object returned. You can use this as a string that describes the failure.
+    
+    For example *The value "3" is of the wrong type*.
+    """
+    if levels is None:
+        # first time called
+        levels = []
+        results = []
+    if res == True:
+        return results
+    if res == False or isinstance(res, Exception):
+        results.append((levels[:], None, res))
+        if levels:
+            levels.pop()
+        return results
+    for (key, val) in res.items():
+        if val == True:
+            continue
+        if isinstance(cfg.get(key), dict):
+            # Go down one level
+            levels.append(key)
+            flatten_errors(cfg[key], val, levels, results)
+            continue
+        results.append((levels[:], key, val))
+    #
+    # Go up one level
+    if levels:
+        levels.pop()
+    #
+    return results
+
+
+def get_extra_values(conf, _prepend=()):
+    """
+    Find all the values and sections not in the configspec from a validated
+    ConfigObj.
+    
+    ``get_extra_values`` returns a list of tuples where each tuple represents
+    either an extra section, or an extra value.
+    
+    The tuples contain two values, a tuple representing the section the value 
+    is in and the name of the extra values. For extra values in the top level
+    section the first member will be an empty tuple. For values in the 'foo'
+    section the first member will be ``('foo',)``. For members in the 'bar'
+    subsection of the 'foo' section the first member will be ``('foo', 'bar')``.
+    
+    NOTE: If you call ``get_extra_values`` on a ConfigObj instance that hasn't
+    been validated it will return an empty list.
+    """
+    out = []
+    
+    out.extend([(_prepend, name) for name in conf.extra_values])
+    for name in conf.sections:
+        if name not in conf.extra_values:
+            out.extend(get_extra_values(conf[name], _prepend + (name,)))
+    return out
+
+
+"""*A programming language is a medium of expression.* - Paul Graham"""
new file mode 100644
--- /dev/null
+++ b/python/configobj/setup.py
@@ -0,0 +1,83 @@
+# setup.py
+# Install script for ConfigObj
+# Copyright (C) 2005-2010 Michael Foord, Mark Andrews, Nicola Larosa
+# E-mail: fuzzyman AT voidspace DOT org DOT uk
+#         mark AT la-la DOT com
+#         nico AT tekNico DOT net
+
+# This software is licensed under the terms of the BSD license.
+# http://www.voidspace.org.uk/python/license.shtml
+
+import sys
+from distutils.core import setup
+from configobj import __version__ as VERSION
+
+NAME = 'configobj'
+
+MODULES = 'configobj', 'validate'
+
+DESCRIPTION = 'Config file reading, writing and validation.'
+
+URL = 'http://www.voidspace.org.uk/python/configobj.html'
+
+DOWNLOAD_URL = "http://www.voidspace.org.uk/downloads/configobj-%s.zip" % VERSION
+
+LONG_DESCRIPTION = """**ConfigObj** is a simple but powerful config file reader and writer: an *ini
+file round tripper*. Its main feature is that it is very easy to use, with a
+straightforward programmer's interface and a simple syntax for config files.
+It has lots of other features though :
+
+* Nested sections (subsections), to any level
+* List values
+* Multiple line values
+* Full Unicode support
+* String interpolation (substitution)
+* Integrated with a powerful validation system
+
+    - including automatic type checking/conversion
+    - and allowing default values
+    - repeated sections
+
+* All comments in the file are preserved
+* The order of keys/sections is preserved
+* Powerful ``unrepr`` mode for storing/retrieving Python data-types
+
+| Release 4.7.2 fixes several bugs in 4.7.1
+| Release 4.7.1 fixes a bug with the deprecated options keyword in
+| 4.7.0.
+| Release 4.7.0 improves performance adds features for validation and
+| fixes some bugs."""
+
+CLASSIFIERS = [
+    'Development Status :: 6 - Mature',
+    'Intended Audience :: Developers',
+    'License :: OSI Approved :: BSD License',
+    'Programming Language :: Python',
+    'Programming Language :: Python :: 2.3',
+    'Programming Language :: Python :: 2.4',
+    'Programming Language :: Python :: 2.5',
+    'Programming Language :: Python :: 2.6',
+    'Operating System :: OS Independent',
+    'Topic :: Software Development :: Libraries',
+    'Topic :: Software Development :: Libraries :: Python Modules',
+]
+
+AUTHOR = 'Michael Foord & Nicola Larosa'
+
+AUTHOR_EMAIL = 'fuzzyman@voidspace.org.uk'
+
+KEYWORDS = "config, ini, dictionary, application, admin, sysadmin, configuration, validation".split(', ')
+
+
+setup(name=NAME,
+      version=VERSION,
+      description=DESCRIPTION,
+      long_description=LONG_DESCRIPTION,
+      download_url=DOWNLOAD_URL,
+      author=AUTHOR,
+      author_email=AUTHOR_EMAIL,
+      url=URL,
+      py_modules=MODULES,
+      classifiers=CLASSIFIERS,
+      keywords=KEYWORDS
+     )
new file mode 100644
--- /dev/null
+++ b/python/configobj/validate.py
@@ -0,0 +1,1450 @@
+# validate.py
+# A Validator object
+# Copyright (C) 2005-2010 Michael Foord, Mark Andrews, Nicola Larosa
+# E-mail: fuzzyman AT voidspace DOT org DOT uk
+#         mark AT la-la DOT com
+#         nico AT tekNico DOT net
+
+# This software is licensed under the terms of the BSD license.
+# http://www.voidspace.org.uk/python/license.shtml
+# Basically you're free to copy, modify, distribute and relicense it,
+# So long as you keep a copy of the license with it.
+
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
+# For information about bugfixes, updates and support, please join the
+# ConfigObj mailing list:
+# http://lists.sourceforge.net/lists/listinfo/configobj-develop
+# Comments, suggestions and bug reports welcome.
+
+"""
+    The Validator object is used to check that supplied values 
+    conform to a specification.
+    
+    The value can be supplied as a string - e.g. from a config file.
+    In this case the check will also *convert* the value to
+    the required type. This allows you to add validation
+    as a transparent layer to access data stored as strings.
+    The validation checks that the data is correct *and*
+    converts it to the expected type.
+    
+    Some standard checks are provided for basic data types.
+    Additional checks are easy to write. They can be
+    provided when the ``Validator`` is instantiated or
+    added afterwards.
+    
+    The standard functions work with the following basic data types :
+    
+    * integers
+    * floats
+    * booleans
+    * strings
+    * ip_addr
+    
+    plus lists of these datatypes
+    
+    Adding additional checks is done through coding simple functions.
+    
+    The full set of standard checks are : 
+    
+    * 'integer': matches integer values (including negative)
+                 Takes optional 'min' and 'max' arguments : ::
+    
+                   integer()
+                   integer(3, 9)  # any value from 3 to 9
+                   integer(min=0) # any positive value
+                   integer(max=9)
+    
+    * 'float': matches float values
+               Has the same parameters as the integer check.
+    
+    * 'boolean': matches boolean values - ``True`` or ``False``
+                 Acceptable string values for True are :
+                   true, on, yes, 1
+                 Acceptable string values for False are :
+                   false, off, no, 0
+    
+                 Any other value raises an error.
+    
+    * 'ip_addr': matches an Internet Protocol address, v.4, represented
+                 by a dotted-quad string, i.e. '1.2.3.4'.
+    
+    * 'string': matches any string.
+                Takes optional keyword args 'min' and 'max'
+                to specify min and max lengths of the string.
+    
+    * 'list': matches any list.
+              Takes optional keyword args 'min', and 'max' to specify min and
+              max sizes of the list. (Always returns a list.)
+    
+    * 'tuple': matches any tuple.
+              Takes optional keyword args 'min', and 'max' to specify min and
+              max sizes of the tuple. (Always returns a tuple.)
+    
+    * 'int_list': Matches a list of integers.
+                  Takes the same arguments as list.
+    
+    * 'float_list': Matches a list of floats.
+                    Takes the same arguments as list.
+    
+    * 'bool_list': Matches a list of boolean values.
+                   Takes the same arguments as list.
+    
+    * 'ip_addr_list': Matches a list of IP addresses.
+                     Takes the same arguments as list.
+    
+    * 'string_list': Matches a list of strings.
+                     Takes the same arguments as list.
+    
+    * 'mixed_list': Matches a list with different types in 
+                    specific positions. List size must match
+                    the number of arguments.
+    
+                    Each position can be one of :
+                    'integer', 'float', 'ip_addr', 'string', 'boolean'
+    
+                    So to specify a list with two strings followed
+                    by two integers, you write the check as : ::
+    
+                      mixed_list('string', 'string', 'integer', 'integer')
+    
+    * 'pass': This check matches everything ! It never fails
+              and the value is unchanged.
+    
+              It is also the default if no check is specified.
+    
+    * 'option': This check matches any from a list of options.
+                You specify this check with : ::
+    
+                  option('option 1', 'option 2', 'option 3')
+    
+    You can supply a default value (returned if no value is supplied)
+    using the default keyword argument.
+    
+    You specify a list argument for default using a list constructor syntax in
+    the check : ::
+    
+        checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3'))
+    
+    A badly formatted set of arguments will raise a ``VdtParamError``.
+"""
+
+__version__ = '1.0.1'
+
+
+__all__ = (
+    '__version__',
+    'dottedQuadToNum',
+    'numToDottedQuad',
+    'ValidateError',
+    'VdtUnknownCheckError',
+    'VdtParamError',
+    'VdtTypeError',
+    'VdtValueError',
+    'VdtValueTooSmallError',
+    'VdtValueTooBigError',
+    'VdtValueTooShortError',
+    'VdtValueTooLongError',
+    'VdtMissingValue',
+    'Validator',
+    'is_integer',
+    'is_float',
+    'is_boolean',
+    'is_list',
+    'is_tuple',
+    'is_ip_addr',
+    'is_string',
+    'is_int_list',
+    'is_bool_list',
+    'is_float_list',
+    'is_string_list',
+    'is_ip_addr_list',
+    'is_mixed_list',
+    'is_option',
+    '__docformat__',
+)
+
+
+import re
+
+
+_list_arg = re.compile(r'''
+    (?:
+        ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
+            (
+                (?:
+                    \s*
+                    (?:
+                        (?:".*?")|              # double quotes
+                        (?:'.*?')|              # single quotes
+                        (?:[^'",\s\)][^,\)]*?)  # unquoted
+                    )
+                    \s*,\s*
+                )*
+                (?:
+                    (?:".*?")|              # double quotes
+                    (?:'.*?')|              # single quotes
+                    (?:[^'",\s\)][^,\)]*?)  # unquoted
+                )?                          # last one
+            )
+        \)
+    )
+''', re.VERBOSE | re.DOTALL)    # two groups
+
+_list_members = re.compile(r'''
+    (
+        (?:".*?")|              # double quotes
+        (?:'.*?')|              # single quotes
+        (?:[^'",\s=][^,=]*?)       # unquoted
+    )
+    (?:
+    (?:\s*,\s*)|(?:\s*$)            # comma
+    )
+''', re.VERBOSE | re.DOTALL)    # one group
+
+_paramstring = r'''
+    (?:
+        (
+            (?:
+                [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
+                    (?:
+                        \s*
+                        (?:
+                            (?:".*?")|              # double quotes
+                            (?:'.*?')|              # single quotes
+                            (?:[^'",\s\)][^,\)]*?)       # unquoted
+                        )
+                        \s*,\s*
+                    )*
+                    (?:
+                        (?:".*?")|              # double quotes
+                        (?:'.*?')|              # single quotes
+                        (?:[^'",\s\)][^,\)]*?)       # unquoted
+                    )?                              # last one
+                \)
+            )|
+            (?:
+                (?:".*?")|              # double quotes
+                (?:'.*?')|              # single quotes
+                (?:[^'",\s=][^,=]*?)|       # unquoted
+                (?:                         # keyword argument
+                    [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
+                    (?:
+                        (?:".*?")|              # double quotes
+                        (?:'.*?')|              # single quotes
+                        (?:[^'",\s=][^,=]*?)       # unquoted
+                    )
+                )
+            )
+        )
+        (?:
+            (?:\s*,\s*)|(?:\s*$)            # comma
+        )
+    )
+    '''
+
+_matchstring = '^%s*' % _paramstring
+
+# Python pre 2.2.1 doesn't have bool
+try:
+    bool
+except NameError:
+    def bool(val):
+        """Simple boolean equivalent function. """
+        if val:
+            return 1
+        else:
+            return 0
+
+
+def dottedQuadToNum(ip):
+    """
+    Convert decimal dotted quad string to long integer
+    
+    >>> int(dottedQuadToNum('1 '))
+    1
+    >>> int(dottedQuadToNum(' 1.2'))
+    16777218
+    >>> int(dottedQuadToNum(' 1.2.3 '))
+    16908291
+    >>> int(dottedQuadToNum('1.2.3.4'))
+    16909060
+    >>> dottedQuadToNum('255.255.255.255')
+    4294967295L
+    >>> dottedQuadToNum('255.255.255.256')
+    Traceback (most recent call last):
+    ValueError: Not a good dotted-quad IP: 255.255.255.256
+    """
+    
+    # import here to avoid it when ip_addr values are not used
+    import socket, struct
+    
+    try:
+        return struct.unpack('!L',
+            socket.inet_aton(ip.strip()))[0]
+    except socket.error:
+        # bug in inet_aton, corrected in Python 2.4
+        if ip.strip() == '255.255.255.255':
+            return 0xFFFFFFFFL
+        else:
+            raise ValueError('Not a good dotted-quad IP: %s' % ip)
+    return
+
+
+def numToDottedQuad(num):
+    """
+    Convert long int to dotted quad string
+    
+    >>> numToDottedQuad(-1L)
+    Traceback (most recent call last):
+    ValueError: Not a good numeric IP: -1
+    >>> numToDottedQuad(1L)
+    '0.0.0.1'
+    >>> numToDottedQuad(16777218L)
+    '1.0.0.2'
+    >>> numToDottedQuad(16908291L)
+    '1.2.0.3'
+    >>> numToDottedQuad(16909060L)
+    '1.2.3.4'
+    >>> numToDottedQuad(4294967295L)
+    '255.255.255.255'
+    >>> numToDottedQuad(4294967296L)
+    Traceback (most recent call last):
+    ValueError: Not a good numeric IP: 4294967296
+    """
+    
+    # import here to avoid it when ip_addr values are not used
+    import socket, struct
+    
+    # no need to intercept here, 4294967295L is fine
+    if num > 4294967295L or num < 0:
+        raise ValueError('Not a good numeric IP: %s' % num)
+    try:
+        return socket.inet_ntoa(
+            struct.pack('!L', long(num)))
+    except (socket.error, struct.error, OverflowError):
+        raise ValueError('Not a good numeric IP: %s' % num)
+
+
+class ValidateError(Exception):
+    """
+    This error indicates that the check failed.
+    It can be the base class for more specific errors.
+    
+    Any check function that fails ought to raise this error.
+    (or a subclass)
+    
+    >>> raise ValidateError
+    Traceback (most recent call last):
+    ValidateError
+    """
+
+
+class VdtMissingValue(ValidateError):
+    """No value was supplied to a check that needed one."""
+
+
+class VdtUnknownCheckError(ValidateError):
+    """An unknown check function was requested"""
+
+    def __init__(self, value):
+        """
+        >>> raise VdtUnknownCheckError('yoda')
+        Traceback (most recent call last):
+        VdtUnknownCheckError: the check "yoda" is unknown.
+        """
+        ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,))
+
+
+class VdtParamError(SyntaxError):
+    """An incorrect parameter was passed"""
+
+    def __init__(self, name, value):
+        """
+        >>> raise VdtParamError('yoda', 'jedi')
+        Traceback (most recent call last):
+        VdtParamError: passed an incorrect value "jedi" for parameter "yoda".
+        """
+        SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name))
+
+
+class VdtTypeError(ValidateError):
+    """The value supplied was of the wrong type"""
+
+    def __init__(self, value):
+        """
+        >>> raise VdtTypeError('jedi')
+        Traceback (most recent call last):
+        VdtTypeError: the value "jedi" is of the wrong type.
+        """
+        ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,))
+
+
+class VdtValueError(ValidateError):
+    """The value supplied was of the correct type, but was not an allowed value."""
+    
+    def __init__(self, value):
+        """
+        >>> raise VdtValueError('jedi')
+        Traceback (most recent call last):
+        VdtValueError: the value "jedi" is unacceptable.
+        """
+        ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,))
+
+
+class VdtValueTooSmallError(VdtValueError):
+    """The value supplied was of the correct type, but was too small."""
+
+    def __init__(self, value):
+        """
+        >>> raise VdtValueTooSmallError('0')
+        Traceback (most recent call last):
+        VdtValueTooSmallError: the value "0" is too small.
+        """
+        ValidateError.__init__(self, 'the value "%s" is too small.' % (value,))
+
+
+class VdtValueTooBigError(VdtValueError):
+    """The value supplied was of the correct type, but was too big."""
+
+    def __init__(self, value):
+        """
+        >>> raise VdtValueTooBigError('1')
+        Traceback (most recent call last):
+        VdtValueTooBigError: the value "1" is too big.
+        """
+        ValidateError.__init__(self, 'the value "%s" is too big.' % (value,))
+
+
+class VdtValueTooShortError(VdtValueError):
+    """The value supplied was of the correct type, but was too short."""
+
+    def __init__(self, value):
+        """
+        >>> raise VdtValueTooShortError('jed')
+        Traceback (most recent call last):
+        VdtValueTooShortError: the value "jed" is too short.
+        """
+        ValidateError.__init__(
+            self,
+            'the value "%s" is too short.' % (value,))
+
+
+class VdtValueTooLongError(VdtValueError):
+    """The value supplied was of the correct type, but was too long."""
+
+    def __init__(self, value):
+        """
+        >>> raise VdtValueTooLongError('jedie')
+        Traceback (most recent call last):
+        VdtValueTooLongError: the value "jedie" is too long.
+        """
+        ValidateError.__init__(self, 'the value "%s" is too long.' % (value,))
+
+
+class Validator(object):
+    """
+    Validator is an object that allows you to register a set of 'checks'.
+    These checks take input and test that it conforms to the check.
+    
+    This can also involve converting the value from a string into
+    the correct datatype.
+    
+    The ``check`` method takes an input string which configures which
+    check is to be used and applies that check to a supplied value.
+    
+    An example input string would be:
+    'int_range(param1, param2)'
+    
+    You would then provide something like:
+    
+    >>> def int_range_check(value, min, max):
+    ...     # turn min and max from strings to integers
+    ...     min = int(min)
+    ...     max = int(max)
+    ...     # check that value is of the correct type.
+    ...     # possible valid inputs are integers or strings
+    ...     # that represent integers
+    ...     if not isinstance(value, (int, long, basestring)):
+    ...         raise VdtTypeError(value)
+    ...     elif isinstance(value, basestring):
+    ...         # if we are given a string
+    ...         # attempt to convert to an integer
+    ...         try:
+    ...             value = int(value)
+    ...         except ValueError:
+    ...             raise VdtValueError(value)
+    ...     # check the value is between our constraints
+    ...     if not min <= value:
+    ...          raise VdtValueTooSmallError(value)
+    ...     if not value <= max:
+    ...          raise VdtValueTooBigError(value)
+    ...     return value
+    
+    >>> fdict = {'int_range': int_range_check}
+    >>> vtr1 = Validator(fdict)
+    >>> vtr1.check('int_range(20, 40)', '30')
+    30
+    >>> vtr1.check('int_range(20, 40)', '60')
+    Traceback (most recent call last):
+    VdtValueTooBigError: the value "60" is too big.
+    
+    New functions can be added with : ::
+    
+    >>> vtr2 = Validator()       
+    >>> vtr2.functions['int_range'] = int_range_check
+    
+    Or by passing in a dictionary of functions when Validator 
+    is instantiated.
+    
+    Your functions *can* use keyword arguments,
+    but the first argument should always be 'value'.
+    
+    If the function doesn't take additional arguments,
+    the parentheses are optional in the check.
+    It can be written with either of : ::
+    
+        keyword = function_name
+        keyword = function_name()
+    
+    The first program to utilise Validator() was Michael Foord's
+    ConfigObj, an alternative to ConfigParser which supports lists and
+    can validate a config file using a config schema.
+    For more details on using Validator with ConfigObj see:
+    http://www.voidspace.org.uk/python/configobj.html
+    """
+
+    # this regex does the initial parsing of the checks
+    _func_re = re.compile(r'(.+?)\((.*)\)', re.DOTALL)
+
+    # this regex takes apart keyword arguments
+    _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$',  re.DOTALL)
+
+
+    # this regex finds keyword=list(....) type values
+    _list_arg = _list_arg
+
+    # this regex takes individual values out of lists - in one pass
+    _list_members = _list_members
+
+    # These regexes check a set of arguments for validity
+    # and then pull the members out
+    _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL)
+    _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL)
+
+
+    def __init__(self, functions=None):
+        """
+        >>> vtri = Validator()
+        """
+        self.functions = {
+            '': self._pass,
+            'integer': is_integer,
+            'float': is_float,
+            'boolean': is_boolean,
+            'ip_addr': is_ip_addr,
+            'string': is_string,
+            'list': is_list,
+            'tuple': is_tuple,
+            'int_list': is_int_list,
+            'float_list': is_float_list,
+            'bool_list': is_bool_list,
+            'ip_addr_list': is_ip_addr_list,
+            'string_list': is_string_list,
+            'mixed_list': is_mixed_list,
+            'pass': self._pass,
+            'option': is_option,
+            'force_list': force_list,
+        }
+        if functions is not None:
+            self.functions.update(functions)
+        # tekNico: for use by ConfigObj
+        self.baseErrorClass = ValidateError
+        self._cache = {}
+
+
+    def check(self, check, value, missing=False):
+        """
+        Usage: check(check, value)
+        
+        Arguments:
+            check: string representing check to apply (including arguments)
+            value: object to be checked
+        Returns value, converted to correct type if necessary
+        
+        If the check fails, raises a ``ValidateError`` subclass.
+        
+        >>> vtor.check('yoda', '')
+        Traceback (most recent call last):
+        VdtUnknownCheckError: the check "yoda" is unknown.
+        >>> vtor.check('yoda()', '')
+        Traceback (most recent call last):
+        VdtUnknownCheckError: the check "yoda" is unknown.
+        
+        >>> vtor.check('string(default="")', '', missing=True)
+        ''
+        """
+        fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
+            
+        if missing:
+            if default is None:
+                # no information needed here - to be handled by caller
+                raise VdtMissingValue()
+            value = self._handle_none(default)
+        
+        if value is None:
+            return None
+