Merge m-c to b2g-inbound
authorWes Kocher <wkocher@mozilla.com>
Mon, 19 May 2014 15:49:19 -0700
changeset 183859 14a651ca3480cd9c9f1d47b26cfd7a96f434ebd2
parent 183858 ca34519f2aca284fa5beb673c735a4a74b04c06f (current diff)
parent 183785 cb9f34f73ebe17cb03365a2b42d6998cb6b5ba18 (diff)
child 183860 c0654235ddd3777ae9410b64195719289feb8641
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
milestone32.0a1
Merge m-c to b2g-inbound
browser/themes/shared/devtools/images/option-icon.png
browser/themes/shared/devtools/images/profiler-stopwatch.png
security/build/b2g-app-root-cert.der
security/build/b2g-certdata.mk
security/build/b2g-certdata.txt
--- a/accessible/src/generic/Accessible.h
+++ b/accessible/src/generic/Accessible.h
@@ -85,16 +85,36 @@ struct GroupPos
 {
   GroupPos() : level(0), posInSet(0), setSize(0) { }
 
   int32_t level;
   int32_t posInSet;
   int32_t setSize;
 };
 
+/**
+ * An index type. Assert if out of range value was attempted to be used.
+ */
+class index_t
+{
+public:
+  index_t(int32_t aVal) : mVal(aVal) {}
+
+  operator uint32_t() const
+  {
+    MOZ_ASSERT(mVal >= 0, "Attempt to use wrong index!");
+    return mVal;
+  }
+
+  bool IsValid() const { return mVal >= 0; }
+
+private:
+  int32_t mVal;
+};
+
 typedef nsRefPtrHashtable<nsPtrHashKey<const void>, Accessible>
   AccessibleHashtable;
 
 
 #define NS_ACCESSIBLE_IMPL_IID                          \
 {  /* 133c8bf4-4913-4355-bd50-426bd1d6e1ad */           \
   0x133c8bf4,                                           \
   0x4913,                                               \
--- a/accessible/src/generic/HyperTextAccessible-inl.h
+++ b/accessible/src/generic/HyperTextAccessible-inl.h
@@ -16,25 +16,27 @@
 #include "nsIPlaintextEditor.h"
 
 namespace mozilla {
 namespace a11y {
 
 inline bool
 HyperTextAccessible::IsValidOffset(int32_t aOffset)
 {
-  return ConvertMagicOffset(aOffset) <= CharacterCount();
+  index_t offset = ConvertMagicOffset(aOffset);
+  return offset.IsValid() && offset <= CharacterCount();
 }
 
 inline bool
 HyperTextAccessible::IsValidRange(int32_t aStartOffset, int32_t aEndOffset)
 {
-  uint32_t endOffset = ConvertMagicOffset(aEndOffset);
-  return ConvertMagicOffset(aStartOffset) <= endOffset &&
-    endOffset <= CharacterCount();
+  index_t startOffset = ConvertMagicOffset(aStartOffset);
+  index_t endOffset = ConvertMagicOffset(aEndOffset);
+  return startOffset.IsValid() && endOffset.IsValid() &&
+    startOffset <= endOffset && endOffset <= CharacterCount();
 }
 
 inline void
 HyperTextAccessible::SetCaretOffset(int32_t aOffset)
 {
   SetSelectionRange(aOffset, aOffset);
   // XXX: Force cache refresh until a good solution for AT emulation of user
   // input is implemented (AccessFu caret movement).
@@ -105,26 +107,26 @@ HyperTextAccessible::PasteText(int32_t a
 {
   nsCOMPtr<nsIEditor> editor = GetEditor();
   if (editor) {
     SetSelectionRange(aPosition, aPosition);
     editor->Paste(nsIClipboard::kGlobalClipboard);
   }
 }
 
-inline uint32_t
+inline index_t
 HyperTextAccessible::ConvertMagicOffset(int32_t aOffset) const
 {
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT)
     return CharacterCount();
 
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
     return CaretOffset();
 
-  return aOffset < 0 ? std::numeric_limits<uint32_t>::max() : aOffset;
+  return aOffset;
 }
 
 inline uint32_t
 HyperTextAccessible::AdjustCaretOffset(uint32_t aOffset) const
 {
   // It is the same character offset when the caret is visually at the very
   // end of a line or the start of a new line (soft line break). Getting text
   // at the line should provide the line with the visual caret, otherwise
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -186,18 +186,23 @@ HyperTextAccessible::GetBoundsInFrame(ns
 }
 
 void
 HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
                                    nsAString& aText)
 {
   aText.Truncate();
 
-  uint32_t startOffset = ConvertMagicOffset(aStartOffset);
-  uint32_t endOffset = ConvertMagicOffset(aEndOffset);
+  index_t startOffset = ConvertMagicOffset(aStartOffset);
+  index_t endOffset = ConvertMagicOffset(aEndOffset);
+  if (!startOffset.IsValid() || !endOffset.IsValid() ||
+      startOffset > endOffset || endOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset");
+    return;
+  }
 
   int32_t startChildIdx = GetChildIndexAtOffset(startOffset);
   if (startChildIdx == -1)
     return;
 
   int32_t endChildIdx = GetChildIndexAtOffset(endOffset);
   if (endChildIdx == -1)
     return;
@@ -603,19 +608,19 @@ void
 HyperTextAccessible::TextBeforeOffset(int32_t aOffset,
                                       AccessibleTextBoundary aBoundaryType,
                                       int32_t* aStartOffset, int32_t* aEndOffset,
                                       nsAString& aText)
 {
   *aStartOffset = *aEndOffset = 0;
   aText.Truncate();
 
-  uint32_t convertedOffset = ConvertMagicOffset(aOffset);
-  if (convertedOffset == std::numeric_limits<uint32_t>::max()) {
-    NS_ERROR("Wrong given offset!");
+  index_t convertedOffset = ConvertMagicOffset(aOffset);
+  if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset!");
     return;
   }
 
   uint32_t adjustedOffset = convertedOffset;
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
     adjustedOffset = AdjustCaretOffset(adjustedOffset);
 
   switch (aBoundaryType) {
@@ -739,19 +744,19 @@ void
 HyperTextAccessible::TextAfterOffset(int32_t aOffset,
                                      AccessibleTextBoundary aBoundaryType,
                                      int32_t* aStartOffset, int32_t* aEndOffset,
                                      nsAString& aText)
 {
   *aStartOffset = *aEndOffset = 0;
   aText.Truncate();
 
-  uint32_t convertedOffset = ConvertMagicOffset(aOffset);
-  if (convertedOffset == std::numeric_limits<uint32_t>::max()) {
-    NS_ERROR("Wrong given offset!");
+  index_t convertedOffset = ConvertMagicOffset(aOffset);
+  if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset!");
     return;
   }
 
   uint32_t adjustedOffset = convertedOffset;
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
     adjustedOffset = AdjustCaretOffset(adjustedOffset);
 
   switch (aBoundaryType) {
@@ -809,20 +814,25 @@ HyperTextAccessible::TextAttributes(bool
                                        int32_t* aEndOffset)
 {
   // 1. Get each attribute and its ranges one after another.
   // 2. As we get each new attribute, we pass the current start and end offsets
   //    as in/out parameters. In other words, as attributes are collected,
   //    the attribute range itself can only stay the same or get smaller.
 
   *aStartOffset = *aEndOffset = 0;
+  index_t offset = ConvertMagicOffset(aOffset);
+  if (!offset.IsValid() || offset > CharacterCount()) {
+    NS_ERROR("Wrong in offset!");
+    return nullptr;
+  }
+
   nsCOMPtr<nsIPersistentProperties> attributes =
     do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
 
-  uint32_t offset = ConvertMagicOffset(aOffset);
   Accessible* accAtOffset = GetChildAtOffset(offset);
   if (!accAtOffset) {
     // Offset 0 is correct offset when accessible has empty text. Include
     // default attributes if they were requested, otherwise return empty set.
     if (offset == 0) {
       if (aIncludeDefAttrs) {
         TextAttrsMgr textAttrsMgr(this);
         textAttrsMgr.GetAttributes(attributes);
@@ -1024,21 +1034,24 @@ HyperTextAccessible::OffsetAtPoint(int32
 
   return -1; // Not found
 }
 
 nsIntRect
 HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset,
                                 uint32_t aCoordType)
 {
-  uint32_t startOffset = ConvertMagicOffset(aStartOffset);
-  uint32_t endOffset = ConvertMagicOffset(aEndOffset);
-  NS_ASSERTION(startOffset < endOffset &&
-               endOffset != std::numeric_limits<uint32_t>::max(),
-               "Wrong bad in!");
+  index_t startOffset = ConvertMagicOffset(aStartOffset);
+  index_t endOffset = ConvertMagicOffset(aEndOffset);
+  if (!startOffset.IsValid() || !endOffset.IsValid() ||
+      startOffset > endOffset || endOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset");
+    return nsIntRect();
+  }
+
 
   int32_t childIdx = GetChildIndexAtOffset(startOffset);
   if (childIdx == -1)
     return nsIntRect();
 
   nsIntRect bounds;
   int32_t prevOffset = GetChildOffset(childIdx);
   int32_t offset1 = startOffset - prevOffset;
@@ -1412,18 +1425,23 @@ HyperTextAccessible::SelectionBoundsAt(i
   return true;
 }
 
 bool
 HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
                                           int32_t aStartOffset,
                                           int32_t aEndOffset)
 {
-  uint32_t startOffset = ConvertMagicOffset(aStartOffset);
-  uint32_t endOffset = ConvertMagicOffset(aEndOffset);
+  index_t startOffset = ConvertMagicOffset(aStartOffset);
+  index_t endOffset = ConvertMagicOffset(aEndOffset);
+  if (!startOffset.IsValid() || !endOffset.IsValid() ||
+      startOffset > endOffset || endOffset > CharacterCount()) {
+    NS_ERROR("Wrong in offset");
+    return false;
+  }
 
   dom::Selection* domSel = DOMSelection();
   if (!domSel)
     return false;
 
   nsRefPtr<nsRange> range;
   uint32_t rangeCount = domSel->GetRangeCount();
   if (aSelectionNum == static_cast<int32_t>(rangeCount))
@@ -1874,17 +1892,17 @@ HyperTextAccessible::GetSpellTextAttr(ns
   dom::Selection* domSel = fs->GetSelection(nsISelectionController::SELECTION_SPELLCHECK);
   if (!domSel)
     return;
 
   int32_t rangeCount = domSel->GetRangeCount();
   if (rangeCount <= 0)
     return;
 
-  int32_t startOffset = 0, endOffset = 0;
+  uint32_t startOffset = 0, endOffset = 0;
   for (int32_t idx = 0; idx < rangeCount; idx++) {
     nsRange* range = domSel->GetRangeAt(idx);
     if (range->Collapsed())
       continue;
 
     // See if the point comes after the range in which case we must continue in
     // case there is another range after this one.
     nsINode* endNode = range->GetEndParent();
--- a/accessible/src/generic/HyperTextAccessible.h
+++ b/accessible/src/generic/HyperTextAccessible.h
@@ -291,17 +291,21 @@ public:
   nsIntRect TextBounds(int32_t aStartOffset, int32_t aEndOffset,
                        uint32_t aCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
 
   /**
    * Return a rect for character at given offset relative given coordinate
    * system.
    */
   nsIntRect CharBounds(int32_t aOffset, uint32_t aCoordType)
-    { return TextBounds(aOffset, aOffset + 1, aCoordType); }
+  {
+    int32_t endOffset = aOffset == static_cast<int32_t>(CharacterCount()) ?
+      aOffset : aOffset + 1;
+    return TextBounds(aOffset, endOffset, aCoordType);
+  }
 
   /**
    * Get/set caret offset, if no caret then -1.
    */
   int32_t CaretOffset() const;
   void SetCaretOffset(int32_t aOffset);
 
   /**
@@ -416,17 +420,17 @@ protected:
   virtual ENameValueFlag NativeName(nsString& aName) MOZ_OVERRIDE;
   virtual void CacheChildren() MOZ_OVERRIDE;
 
   // HyperTextAccessible
 
   /**
    * Transform magic offset into text offset.
    */
-  uint32_t ConvertMagicOffset(int32_t aOffset) const;
+  index_t ConvertMagicOffset(int32_t aOffset) const;
 
   /**
    * Adjust an offset the caret stays at to get a text by line boundary.
    */
   uint32_t AdjustCaretOffset(uint32_t aOffset) const;
 
   /**
    * Return true if caret is at end of line.
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -296,16 +296,17 @@ pref("layers.offmainthreadcomposition.en
 pref("dom.ipc.tabs.disabled", true);
 pref("layers.offmainthreadcomposition.async-animations", false);
 pref("layers.async-video.enabled", false);
 #else
 pref("dom.ipc.tabs.disabled", false);
 pref("layers.acceleration.disabled", false);
 pref("layers.offmainthreadcomposition.async-animations", true);
 pref("layers.async-video.enabled", true);
+pref("layers.async-video-oop.enabled",true);
 pref("layers.async-pan-zoom.enabled", true);
 pref("gfx.content.azure.backends", "cairo");
 #endif
 
 // Web Notifications
 pref("notification.feature.enabled", true);
 
 // IndexedDB
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -46,17 +46,16 @@ fi
 
 MOZ_MEDIA_NAVIGATOR=1
 
 MOZ_APP_ID={3c2e2abc-06d4-11e1-ac3b-374f68613e61}
 MOZ_EXTENSION_MANAGER=1
 
 MOZ_TIME_MANAGER=1
 
-MOZ_B2G_CERTDATA=1
 MOZ_PAY=1
 MOZ_TOOLKIT_SEARCH=
 MOZ_PLACES=
 MOZ_B2G=1
 
 if test "$OS_TARGET" = "Android"; then
 MOZ_NUWA_PROCESS=1
 fi
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -806,29 +806,25 @@ pref("plugin.state.npmcffplg", 2);
 #ifdef XP_MACOSX
 pref("plugin.state.f5 ssl vpn plugin", 2);
 pref("plugin.state.f5 sam inspection host plugin", 2);
 #endif
 
 // display door hanger if flash not installed
 pref("plugins.notifyMissingFlash", true);
 
-#ifdef XP_WIN
-pref("browser.preferences.instantApply", false);
-#else
 pref("browser.preferences.instantApply", true);
-#endif
 #ifdef XP_MACOSX
 pref("browser.preferences.animateFadeIn", true);
 #else
 pref("browser.preferences.animateFadeIn", false);
 #endif
 
 // Toggles between the two Preferences implementations, pop-up window and in-content
-pref("browser.preferences.inContent", false);
+pref("browser.preferences.inContent", true);
 
 pref("browser.download.show_plugins_in_list", true);
 pref("browser.download.hide_plugins_without_extensions", true);
 
 // Backspace and Shift+Backspace behavior
 // 0 goes Back/Forward
 // 1 act like PgUp/PgDown
 // 2 and other values, nothing
--- a/browser/base/content/test/general/browser_datareporting_notification.js
+++ b/browser/base/content/test/general/browser_datareporting_notification.js
@@ -94,51 +94,52 @@ function test_multiple_windows() {
   // results in dismiss on every window.
   let window2 = OpenBrowserWindow();
   whenDelayedStartupFinished(window2, function onWindow() {
     let notification1 = document.getElementById("global-notificationbox");
     let notification2 = window2.document.getElementById("global-notificationbox");
     ok(notification2, "2nd window has a global notification box.");
 
     let policy;
-
     let displayCount = 0;
-    let prefPaneClosed = false;
-    let childWindowClosed = false;
+    let prefWindowClosed = false;
+    let mutationObserversRemoved = false;
 
     function onAlertDisplayed() {
       displayCount++;
 
       if (displayCount != 2) {
         return;
       }
 
       ok(true, "Data reporting info bar displayed on all open windows.");
 
       // We register two independent observers and we need both to clean up
       // properly. This handles gating for test completion.
       function maybeFinish() {
-        if (!prefPaneClosed) {
+        if (!prefWindowClosed) {
           dump("Not finishing test yet because pref pane isn't closed.\n");
           return;
         }
 
-        if (!childWindowClosed) {
-          dump("Not finishing test yet because child window isn't closed.\n");
+        if (!mutationObserversRemoved) {
+          dump("Not finishing test yet because mutation observers haven't been removed yet.\n");
           return;
         }
 
+        window2.close();
+
         dump("Finishing multiple window test.\n");
         rootLogger.removeAppender(dumpAppender);
         delete dumpAppender;
         delete rootLogger;
         finish();
       }
+      let closeCount = 0;
 
-      let closeCount = 0;
       function onAlertClose() {
         closeCount++;
 
         if (closeCount != 2) {
           return;
         }
 
         ok(true, "Closing info bar on one window closed them on all.");
@@ -146,18 +147,17 @@ function test_multiple_windows() {
         is(policy.notifyState, policy.STATE_NOTIFY_COMPLETE,
            "Closing info bar with multiple windows completes notification.");
         ok(policy.dataSubmissionPolicyAccepted, "Data submission policy accepted.");
         is(policy.dataSubmissionPolicyResponseType, "accepted-info-bar-button-pressed",
            "Policy records reason for acceptance was button press.");
         is(notification1.allNotifications.length, 0, "No notifications remain on main window.");
         is(notification2.allNotifications.length, 0, "No notifications remain on 2nd window.");
 
-        window2.close();
-        childWindowClosed = true;
+        mutationObserversRemoved = true;
         maybeFinish();
       }
 
       waitForNotificationClose(notification1.currentNotification, onAlertClose);
       waitForNotificationClose(notification2.currentNotification, onAlertClose);
 
       // While we're here, we dual purpose this test to check that pressing the
       // button does the right thing.
@@ -165,21 +165,21 @@ function test_multiple_windows() {
       is(buttons.length, 1, "There is 1 button in the data reporting notification.");
       let button = buttons[0];
 
       // Automatically close preferences window when it is opened as part of
       // button press.
       Services.obs.addObserver(function observer(prefWin, topic, data) {
         Services.obs.removeObserver(observer, "advanced-pane-loaded");
 
-        ok(true, "Pref pane opened on info bar button press.");
+        ok(true, "Advanced preferences opened on info bar button press.");
         executeSoon(function soon() {
-          dump("Closing pref pane.\n");
+          dump("Closing preferences.\n");
           prefWin.close();
-          prefPaneClosed = true;
+          prefWindowClosed = true;
           maybeFinish();
         });
       }, "advanced-pane-loaded", false);
 
       button.click();
     }
 
     notification1.addEventListener("AlertActive", function active1() {
--- a/browser/base/content/test/general/browser_offlineQuotaNotification.js
+++ b/browser/base/content/test/general/browser_offlineQuotaNotification.js
@@ -33,40 +33,40 @@ function checkPreferences(prefsWin) {
       // all good, we are done.
       prefsWin.close();
       finish();
     });
   });
 }
 
 function test() {
+  if (Services.prefs.getBoolPref("browser.preferences.inContent")) {
+    // Bug 881576 - ensure this works with inContent prefs.
+    todo(false, "Bug 881576 - this test needs to be updated for inContent prefs");
+    return;
+  }
   waitForExplicitFinish();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     gBrowser.selectedBrowser.contentWindow.applicationCache.oncached = function() {
       executeSoon(function() {
         // We got cached - now we should have provoked the quota warning.
         let notification = PopupNotifications.getNotification('offline-app-usage');
         ok(notification, "have offline-app-usage notification");
         // select the default action - this should cause the preferences
         // window to open - which we track either via a window watcher (for
         // the window-based prefs) or via an "Initialized" event (for
         // in-content prefs.)
-        if (Services.prefs.getBoolPref("browser.preferences.inContent")) {
-          // Bug 881576 - ensure this works with inContent prefs.
-          todo(false, "Bug 881576 - this test needs to be updated for inContent prefs");
-        } else {
-          Services.ww.registerNotification(function wwobserver(aSubject, aTopic, aData) {
-            if (aTopic != "domwindowopened")
-              return;
-            Services.ww.unregisterNotification(wwobserver);
-            checkPreferences(aSubject);
-          });
-          PopupNotifications.panel.firstElementChild.button.click();
-        }
+        Services.ww.registerNotification(function wwobserver(aSubject, aTopic, aData) {
+          if (aTopic != "domwindowopened")
+            return;
+          Services.ww.unregisterNotification(wwobserver);
+          checkPreferences(aSubject);
+        });
+        PopupNotifications.panel.firstElementChild.button.click();
       });
     };
     Services.prefs.setIntPref("offline-apps.quota.warn", 1);
 
     // Click the notification panel's "Allow" button.  This should kick
     // off updates which will call our oncached handler above.
     PopupNotifications.panel.firstElementChild.button.click();
   }, true);
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -500,20 +500,23 @@ function openPreferences(paneID, extraAr
     function switchToPane() {
       if (paneID) {
         browser.contentWindow.selectCategory(paneID);
       }
       switchToAdvancedSubPane(browser.contentDocument);
     }
 
     if (newLoad) {
-      browser.addEventListener("load", function onload() {
-        browser.removeEventListener("load", onload, true);
+      Services.obs.addObserver(function advancedPaneLoadedObs(prefWin, topic, data) {
+        if (prefWin != browser.contentWindow) {
+          return;
+        }
+        Services.obs.removeObserver(advancedPaneLoadedObs, "advanced-pane-loaded");
         switchToPane();
-      }, true);
+      }, "advanced-pane-loaded", false);
     } else {
       switchToPane();
     }
   } else {
     var instantApply = getBoolPref("browser.preferences.instantApply", false);
     var features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal");
 
     var win = Services.wm.getMostRecentWindow("Browser:Preferences");
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -767,25 +767,26 @@ let CustomizableUIInternal = {
 
     for (let areaNode of areaNodes) {
       let window = areaNode.ownerDocument.defaultView;
       if (!showInPrivateBrowsing &&
           PrivateBrowsingUtils.isWindowPrivate(window)) {
         continue;
       }
 
+      let container = areaNode.customizationTarget;
       let widgetNode = window.document.getElementById(aWidgetId);
-      if (!widgetNode) {
+      if (widgetNode && isOverflowable) {
+        container = areaNode.overflowable.getContainerFor(widgetNode);
+      }
+
+      if (!widgetNode || !container.contains(widgetNode)) {
         INFO("Widget not found, unable to remove");
         continue;
       }
-      let container = areaNode.customizationTarget;
-      if (isOverflowable) {
-        container = areaNode.overflowable.getContainerFor(widgetNode);
-      }
 
       this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null, container, true);
 
       // We remove location attributes here to make sure they're gone too when a
       // widget is removed from a toolbar to the palette. See bug 930950.
       this.removeLocationAttributes(widgetNode);
       // We also need to remove the panel context menu if it's there:
       this.ensureButtonContextMenu(widgetNode);
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -103,12 +103,13 @@ skip-if = os == "linux"
 [browser_987177_destroyWidget_xul.js]
 [browser_987177_xul_wrapper_updating.js]
 [browser_987492_window_api.js]
 [browser_987640_charEncoding.js]
 [browser_992747_toggle_noncustomizable_toolbar.js]
 [browser_993322_widget_notoolbar.js]
 [browser_995164_registerArea_during_customize_mode.js]
 [browser_996364_registerArea_different_properties.js]
+[browser_996635_remove_non_widgets.js]
 [browser_1003588_no_specials_in_panel.js]
 [browser_1008559_anchor_undo_restore.js]
 [browser_bootstrapped_custom_toolbar.js]
 [browser_panel_toggle.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_996635_remove_non_widgets.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// NB: This is testing what happens if something that /isn't/ a customizable
+// widget gets used in CustomizableUI APIs. Don't use this as an example of
+// what should happen in a "normal" case or how you should use the API.
+function test() {
+  // First create a button that isn't customizable, and add it in the nav-bar,
+  // but not in the customizable part of it (the customization target) but
+  // next to the main (hamburger) menu button.
+  const buttonID = "Test-non-widget-non-removable-button";
+  let btn = document.createElement("toolbarbutton");
+  btn.id = buttonID;
+  btn.label = "Hi";
+  btn.setAttribute("style", "width: 20px; height: 20px; background-color: red");
+  document.getElementById("nav-bar").appendChild(btn);
+  registerCleanupFunction(function() {
+    btn.remove();
+  });
+
+  // Now try to add this non-customizable button to the tabstrip. This will
+  // update the internal bookkeeping (ie placements) information, but shouldn't
+  // move the node.
+  CustomizableUI.addWidgetToArea(buttonID, CustomizableUI.AREA_TABSTRIP);
+  let placement = CustomizableUI.getPlacementOfWidget(buttonID);
+  // Check our bookkeeping
+  ok(placement, "Button should be placed");
+  is(placement && placement.area, CustomizableUI.AREA_TABSTRIP, "Should be placed on tabstrip.");
+  // Check we didn't move the node.
+  is(btn.parentNode && btn.parentNode.id, "nav-bar", "Actual button should still be on navbar.");
+
+  // Now remove the node again. This should remove the bookkeeping, but again
+  // not affect the actual node.
+  CustomizableUI.removeWidgetFromArea(buttonID);
+  placement = CustomizableUI.getPlacementOfWidget(buttonID);
+  // Check our bookkeeping:
+  ok(!placement, "Button should no longer have a placement.");
+  // Check our node.
+  is(btn.parentNode && btn.parentNode.id, "nav-bar", "Actual button should still be on navbar.");
+}
+
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -66,16 +66,19 @@ var gAdvancedPane = {
     this.initSubmitCrashes();
 #endif
     this.initTelemetry();
 #ifdef MOZ_SERVICES_HEALTHREPORT
     this.initSubmitHealthReport();
 #endif
     this.updateActualCacheSize();
     this.updateActualAppCacheSize();
+
+    // Notify observers that the UI is now ready
+    Services.obs.notifyObservers(window, "advanced-pane-loaded", null);
   },
 
   /**
    * Stores the identity of the current tab in preferences so that the selected
    * tab can be persisted between openings of the preferences window.
    */
   tabSelectionChanged: function ()
   {
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -39,16 +39,17 @@ function init_all() {
     history.replaceState("paneGeneral", document.title);
   }
 }
 
 function selectCategory(name) {
   let categories = document.getElementById("categories");
   let item = categories.querySelector(".category[value=" + name + "]");
   categories.selectedItem = item;
+  gotoPref(name);
 }
 
 function gotoPref(page) {
   window.history.replaceState(page, document.title);
   search(page, "data-category");
 }
 
 function search(aQuery, aAttribute) {
--- a/browser/devtools/shared/test/browser_telemetry_button_paintflashing.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_paintflashing.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   };
--- a/browser/devtools/shared/test/browser_telemetry_button_responsive.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_responsive.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   };
--- a/browser/devtools/shared/test/browser_telemetry_button_scratchpad.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_scratchpad.js
@@ -14,16 +14,20 @@ let require = Cu.import("resource://gre/
 let Telemetry = require("devtools/shared/telemetry");
 
 let numScratchpads = 0;
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   };
--- a/browser/devtools/shared/test/browser_telemetry_button_tilt.js
+++ b/browser/devtools/shared/test/browser_telemetry_button_tilt.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   };
--- a/browser/devtools/shared/test/browser_telemetry_sidebar.js
+++ b/browser/devtools/shared/test/browser_telemetry_sidebar.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   };
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_inspector.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_inspector.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   }
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   }
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   }
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   }
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_options.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_options.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   }
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   }
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_webconsole.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_webconsole.js
@@ -12,16 +12,20 @@ let {Services} = Cu.import("resource://g
 
 let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 function init() {
   Telemetry.prototype.telemetryInfo = {};
   Telemetry.prototype._oldlog = Telemetry.prototype.log;
   Telemetry.prototype.log = function(histogramId, value) {
+    if (!this.telemetryInfo) {
+      // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10)
+      return;
+    }
     if (histogramId) {
       if (!this.telemetryInfo[histogramId]) {
         this.telemetryInfo[histogramId] = [];
       }
 
       this.telemetryInfo[histogramId].push(value);
     }
   }
--- a/browser/devtools/sourceeditor/codemirror/mozilla.css
+++ b/browser/devtools/sourceeditor/codemirror/mozilla.css
@@ -22,30 +22,50 @@
   opacity: 0.75;
 }
 
 .breakpoint {
   background-image: url("chrome://browser/skin/devtools/editor-breakpoint.png");
   position: relative;
 }
 
+@media (min-resolution: 2dppx) {
+  .breakpoint {
+    background-image: url("chrome://browser/skin/devtools/editor-breakpoint@2x.png");
+  }
+}
+
 .breakpoint[adding] {
   transition: transform .25s;
 }
 
 .debugLocation {
   background-image: url("chrome://browser/skin/devtools/editor-debug-location.png");
 }
 
+@media (min-resolution: 2dppx) {
+  .debugLocation {
+    background-image: url("chrome://browser/skin/devtools/editor-debug-location@2x.png");
+  }
+}
+
 .breakpoint.debugLocation {
   background-image:
     url("chrome://browser/skin/devtools/editor-debug-location.png"),
     url("chrome://browser/skin/devtools/editor-breakpoint.png");
 }
 
+@media (min-resolution: 2dppx) {
+  .breakpoint.debugLocation {
+    background-image:
+      url("chrome://browser/skin/devtools/editor-debug-location@2x.png"),
+      url("chrome://browser/skin/devtools/editor-breakpoint@2x.png");
+  }
+}
+
 .CodeMirror {
   cursor: text;
 }
 
 .CodeMirror-gutters {
   cursor: default;
 }
 
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -1,9 +1,5 @@
 /* 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 ../../shared/devtools/debugger.inc.css
-
-.devtools-sidebar-tabs > tabs > tab {
-  min-height: 24px !important;
-}
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -212,16 +212,17 @@ browser.jar:
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (../shared/devtools/images/webconsole.png)
   skin/classic/browser/devtools/webconsole@2x.png               (../shared/devtools/images/webconsole@2x.png)
   skin/classic/browser/devtools/commandline.css              (devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css       (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png       (../shared/devtools/images/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png  (../shared/devtools/images/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-debug-location.png (../shared/devtools/images/editor-debug-location.png)
+  skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png)
   skin/classic/browser/devtools/breadcrumbs-divider@2x.png      (../shared/devtools/images/breadcrumbs-divider@2x.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
 * skin/classic/browser/devtools/canvasdebugger.css    (devtools/canvasdebugger.css)
 * skin/classic/browser/devtools/debugger.css          (devtools/debugger.css)
   skin/classic/browser/devtools/eyedropper.css        (../shared/devtools/eyedropper.css)
 * skin/classic/browser/devtools/netmonitor.css        (devtools/netmonitor.css)
 * skin/classic/browser/devtools/profiler.css          (devtools/profiler.css)
@@ -240,38 +241,48 @@ browser.jar:
   skin/classic/browser/devtools/itemArrow-dark-ltr.png (../shared/devtools/images/itemArrow-dark-ltr.png)
   skin/classic/browser/devtools/itemArrow-rtl.svg      (../shared/devtools/images/itemArrow-rtl.svg)
   skin/classic/browser/devtools/itemArrow-ltr.svg      (../shared/devtools/images/itemArrow-ltr.svg)
   skin/classic/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
   skin/classic/browser/devtools/noise.png             (../shared/devtools/images/noise.png)
   skin/classic/browser/devtools/dropmarker.png        (../shared/devtools/images/dropmarker.png)
   skin/classic/browser/devtools/layoutview.css         (../shared/devtools/layoutview.css)
   skin/classic/browser/devtools/debugger-collapse.png  (../shared/devtools/images/debugger-collapse.png)
+  skin/classic/browser/devtools/debugger-collapse@2x.png  (../shared/devtools/images/debugger-collapse@2x.png)
   skin/classic/browser/devtools/debugger-expand.png    (../shared/devtools/images/debugger-expand.png)
+  skin/classic/browser/devtools/debugger-expand@2x.png    (../shared/devtools/images/debugger-expand@2x.png)
   skin/classic/browser/devtools/debugger-pause.png     (../shared/devtools/images/debugger-pause.png)
+  skin/classic/browser/devtools/debugger-pause@2x.png     (../shared/devtools/images/debugger-pause@2x.png)
   skin/classic/browser/devtools/debugger-play.png      (../shared/devtools/images/debugger-play.png)
+  skin/classic/browser/devtools/debugger-play@2x.png      (../shared/devtools/images/debugger-play@2x.png)
   skin/classic/browser/devtools/debugger-step-in.png   (../shared/devtools/images/debugger-step-in.png)
+  skin/classic/browser/devtools/debugger-step-in@2x.png   (../shared/devtools/images/debugger-step-in@2x.png)
   skin/classic/browser/devtools/debugger-step-out.png  (../shared/devtools/images/debugger-step-out.png)
+  skin/classic/browser/devtools/debugger-step-out@2x.png  (../shared/devtools/images/debugger-step-out@2x.png)
   skin/classic/browser/devtools/debugger-step-over.png (../shared/devtools/images/debugger-step-over.png)
+  skin/classic/browser/devtools/debugger-step-over@2x.png (../shared/devtools/images/debugger-step-over@2x.png)
   skin/classic/browser/devtools/debugger-blackbox-eye.png  (../shared/devtools/images/debugger-blackbox-eye.png)
   skin/classic/browser/devtools/debugger-blackbox.png  (../shared/devtools/images/debugger-blackbox.png)
+  skin/classic/browser/devtools/debugger-blackbox@2x.png  (../shared/devtools/images/debugger-blackbox@2x.png)
   skin/classic/browser/devtools/debugger-toggleBreakpoints.png (../shared/devtools/images/debugger-toggleBreakpoints.png)
+  skin/classic/browser/devtools/debugger-toggleBreakpoints@2x.png (../shared/devtools/images/debugger-toggleBreakpoints@2x.png)
   skin/classic/browser/devtools/tracer-icon.png        (../shared/devtools/images/tracer-icon.png)
   skin/classic/browser/devtools/tracer-icon@2x.png     (../shared/devtools/images/tracer-icon@2x.png)
   skin/classic/browser/devtools/responsive-se-resizer.png (../shared/devtools/images/responsive-se-resizer.png)
   skin/classic/browser/devtools/responsive-vertical-resizer.png (../shared/devtools/images/responsive-vertical-resizer.png)
   skin/classic/browser/devtools/responsive-horizontal-resizer.png (../shared/devtools/images/responsive-horizontal-resizer.png)
   skin/classic/browser/devtools/responsive-background.png (../shared/devtools/images/responsive-background.png)
   skin/classic/browser/devtools/toggle-tools.png          (../shared/devtools/images/toggle-tools.png)
   skin/classic/browser/devtools/dock-bottom@2x.png        (../shared/devtools/images/dock-bottom@2x.png)
   skin/classic/browser/devtools/dock-side@2x.png          (../shared/devtools/images/dock-side@2x.png)
   skin/classic/browser/devtools/floating-scrollbars.css   (devtools/floating-scrollbars.css)
   skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
   skin/classic/browser/devtools/inspector.css               (devtools/inspector.css)
-  skin/classic/browser/devtools/profiler-stopwatch.png      (../shared/devtools/images/profiler-stopwatch.png)
+  skin/classic/browser/devtools/profiler-stopwatch.svg      (../shared/devtools/images/profiler-stopwatch.svg)
+  skin/classic/browser/devtools/profiler-stopwatch-checked.svg      (../shared/devtools/images/profiler-stopwatch-checked.svg)
   skin/classic/browser/devtools/tool-options.svg            (../shared/devtools/images/tool-options.svg)
   skin/classic/browser/devtools/tool-webconsole.svg         (../shared/devtools/images/tool-webconsole.svg)
   skin/classic/browser/devtools/tool-debugger.svg           (../shared/devtools/images/tool-debugger.svg)
   skin/classic/browser/devtools/tool-debugger-paused.svg    (../shared/devtools/images/tool-debugger-paused.svg)
   skin/classic/browser/devtools/tool-inspector.svg          (../shared/devtools/images/tool-inspector.svg)
   skin/classic/browser/devtools/tool-inspector.svg          (../shared/devtools/images/tool-inspector.svg)
   skin/classic/browser/devtools/tool-styleeditor.svg        (../shared/devtools/images/tool-styleeditor.svg)
   skin/classic/browser/devtools/tool-profiler.svg           (../shared/devtools/images/tool-profiler.svg)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -328,17 +328,19 @@ browser.jar:
   skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
   skin/classic/browser/devtools/alerticon-warning.png         (../shared/devtools/images/alerticon-warning.png)
   skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
 * skin/classic/browser/devtools/ruleview.css                (../shared/devtools/ruleview.css)
   skin/classic/browser/devtools/commandline.css             (devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css             (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png             (../shared/devtools/images/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png        (../shared/devtools/images/editor-breakpoint.png)
+  skin/classic/browser/devtools/editor-breakpoint@2x.png        (../shared/devtools/images/editor-breakpoint@2x.png)
   skin/classic/browser/devtools/editor-debug-location.png    (../shared/devtools/images/editor-debug-location.png)
+  skin/classic/browser/devtools/editor-debug-location@2x.png    (../shared/devtools/images/editor-debug-location@2x.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (../shared/devtools/images/webconsole.png)
   skin/classic/browser/devtools/webconsole@2x.png               (../shared/devtools/images/webconsole@2x.png)
   skin/classic/browser/devtools/breadcrumbs-divider@2x.png      (../shared/devtools/images/breadcrumbs-divider@2x.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
 * skin/classic/browser/devtools/canvasdebugger.css          (devtools/canvasdebugger.css)
@@ -361,38 +363,48 @@ browser.jar:
   skin/classic/browser/devtools/itemArrow-dark-ltr.png      (../shared/devtools/images/itemArrow-dark-ltr.png)
   skin/classic/browser/devtools/itemArrow-rtl.svg           (../shared/devtools/images/itemArrow-rtl.svg)
   skin/classic/browser/devtools/itemArrow-ltr.svg           (../shared/devtools/images/itemArrow-ltr.svg)
   skin/classic/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
   skin/classic/browser/devtools/noise.png                   (../shared/devtools/images/noise.png)
   skin/classic/browser/devtools/dropmarker.png              (../shared/devtools/images/dropmarker.png)
   skin/classic/browser/devtools/layoutview.css              (../shared/devtools/layoutview.css)
   skin/classic/browser/devtools/debugger-collapse.png       (../shared/devtools/images/debugger-collapse.png)
+  skin/classic/browser/devtools/debugger-collapse@2x.png    (../shared/devtools/images/debugger-collapse@2x.png)
   skin/classic/browser/devtools/debugger-expand.png         (../shared/devtools/images/debugger-expand.png)
+  skin/classic/browser/devtools/debugger-expand@2x.png      (../shared/devtools/images/debugger-expand@2x.png)
   skin/classic/browser/devtools/debugger-pause.png          (../shared/devtools/images/debugger-pause.png)
+  skin/classic/browser/devtools/debugger-pause@2x.png       (../shared/devtools/images/debugger-pause@2x.png)
   skin/classic/browser/devtools/debugger-play.png           (../shared/devtools/images/debugger-play.png)
+  skin/classic/browser/devtools/debugger-play@2x.png        (../shared/devtools/images/debugger-play@2x.png)
   skin/classic/browser/devtools/debugger-step-in.png        (../shared/devtools/images/debugger-step-in.png)
+  skin/classic/browser/devtools/debugger-step-in@2x.png     (../shared/devtools/images/debugger-step-in@2x.png)
   skin/classic/browser/devtools/debugger-step-out.png       (../shared/devtools/images/debugger-step-out.png)
+  skin/classic/browser/devtools/debugger-step-out@2x.png    (../shared/devtools/images/debugger-step-out@2x.png)
   skin/classic/browser/devtools/debugger-step-over.png      (../shared/devtools/images/debugger-step-over.png)
+  skin/classic/browser/devtools/debugger-step-over@2x.png   (../shared/devtools/images/debugger-step-over@2x.png)
+  skin/classic/browser/devtools/debugger-blackbox-eye.png   (../shared/devtools/images/debugger-blackbox-eye.png)
   skin/classic/browser/devtools/debugger-blackbox.png       (../shared/devtools/images/debugger-blackbox.png)
-  skin/classic/browser/devtools/debugger-blackbox-eye.png (../shared/devtools/images/debugger-blackbox-eye.png)
+  skin/classic/browser/devtools/debugger-blackbox@2x.png    (../shared/devtools/images/debugger-blackbox@2x.png)
   skin/classic/browser/devtools/debugger-toggleBreakpoints.png (../shared/devtools/images/debugger-toggleBreakpoints.png)
+  skin/classic/browser/devtools/debugger-toggleBreakpoints@2x.png (../shared/devtools/images/debugger-toggleBreakpoints@2x.png)
   skin/classic/browser/devtools/tracer-icon.png             (../shared/devtools/images/tracer-icon.png)
   skin/classic/browser/devtools/tracer-icon@2x.png          (../shared/devtools/images/tracer-icon@2x.png)
   skin/classic/browser/devtools/floating-scrollbars.css     (devtools/floating-scrollbars.css)
   skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
   skin/classic/browser/devtools/responsive-se-resizer.png   (../shared/devtools/images/responsive-se-resizer.png)
   skin/classic/browser/devtools/responsive-vertical-resizer.png (../shared/devtools/images/responsive-vertical-resizer.png)
   skin/classic/browser/devtools/responsive-horizontal-resizer.png (../shared/devtools/images/responsive-horizontal-resizer.png)
   skin/classic/browser/devtools/responsive-background.png   (../shared/devtools/images/responsive-background.png)
   skin/classic/browser/devtools/toggle-tools.png            (../shared/devtools/images/toggle-tools.png)
   skin/classic/browser/devtools/dock-bottom@2x.png          (../shared/devtools/images/dock-bottom@2x.png)
   skin/classic/browser/devtools/dock-side@2x.png          (../shared/devtools/images/dock-side@2x.png)
 * skin/classic/browser/devtools/inspector.css               (devtools/inspector.css)
-  skin/classic/browser/devtools/profiler-stopwatch.png      (../shared/devtools/images/profiler-stopwatch.png)
+  skin/classic/browser/devtools/profiler-stopwatch.svg      (../shared/devtools/images/profiler-stopwatch.svg)
+  skin/classic/browser/devtools/profiler-stopwatch-checked.svg      (../shared/devtools/images/profiler-stopwatch-checked.svg)
   skin/classic/browser/devtools/tool-options.svg            (../shared/devtools/images/tool-options.svg)
   skin/classic/browser/devtools/tool-webconsole.svg         (../shared/devtools/images/tool-webconsole.svg)
   skin/classic/browser/devtools/tool-debugger.svg           (../shared/devtools/images/tool-debugger.svg)
   skin/classic/browser/devtools/tool-debugger-paused.svg    (../shared/devtools/images/tool-debugger-paused.svg)
   skin/classic/browser/devtools/tool-inspector.svg          (../shared/devtools/images/tool-inspector.svg)
   skin/classic/browser/devtools/tool-inspector.svg          (../shared/devtools/images/tool-inspector.svg)
   skin/classic/browser/devtools/tool-styleeditor.svg        (../shared/devtools/images/tool-styleeditor.svg)
   skin/classic/browser/devtools/tool-profiler.svg           (../shared/devtools/images/tool-profiler.svg)
--- a/browser/themes/shared/devtools/canvasdebugger.inc.css
+++ b/browser/themes/shared/devtools/canvasdebugger.inc.css
@@ -30,18 +30,17 @@
 #reload-notice > button {
   min-height: 2em;
 }
 
 #empty-notice > button {
   min-width: 30px;
   min-height: 28px;
   margin: 0;
-  list-style-image: url(profiler-stopwatch.png);
-  -moz-image-region: rect(0px,16px,16px,0px);
+  list-style-image: url(profiler-stopwatch.svg);
 }
 
 #empty-notice > button .button-text {
   display: none;
 }
 
 .theme-dark #import-notice {
   font-size: 250%;
@@ -69,22 +68,21 @@
 }
 
 .theme-light #snapshots-pane > tabs,
 .theme-light #snapshots-pane .devtools-toolbar {
   -moz-border-end-color: #aaa; /* Match the splitter color. */
 }
 
 #record-snapshot {
-  list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch.png");
-  -moz-image-region: rect(0px,16px,16px,0px);
+  list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch.svg");
 }
 
 #record-snapshot[checked] {
-  -moz-image-region: rect(0px,32px,16px,16px);
+  list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch-checked.svg");
 }
 
 /* Snapshots items */
 
 .snapshot-item-thumbnail {
   image-rendering: -moz-crisp-edges;
   background-image: @checkerboardPattern@;
   background-size: 12px 12px, 12px 12px;
--- a/browser/themes/shared/devtools/debugger.inc.css
+++ b/browser/themes/shared/devtools/debugger.inc.css
@@ -56,49 +56,70 @@
 #sources-controls > .devtools-toolbarbutton {
   min-width: 32px;
 }
 
 #black-box {
   list-style-image: url(debugger-blackbox.png);
 }
 
+@media (min-resolution: 2dppx) {
+  #black-box {
+    list-style-image: url(debugger-blackbox@2x.png);
+  }
+}
+
 #pretty-print {
   font-weight: bold;
 }
 
 #toggle-breakpoints {
   list-style-image: url(debugger-toggleBreakpoints.png);
 }
 
+@media (min-resolution: 2dppx) {
+  #toggle-breakpoints {
+    list-style-image: url(debugger-toggleBreakpoints@2x.png);
+  }
+}
+
 #sources-toolbar .devtools-toolbarbutton:not([label]) {
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #sources-toolbar .devtools-toolbarbutton:not([label])[checked] {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
 
+@media (min-resolution: 2dppx) {
+  #sources-toolbar .devtools-toolbarbutton:not([label]) {
+    -moz-image-region: rect(0px,32px,32px,0px);
+  }
+
+  #sources-toolbar .devtools-toolbarbutton:not([label])[checked] {
+    -moz-image-region: rect(0px,64px,32px,32px);
+  }
+}
+
 #sources .black-boxed {
   color: rgba(128,128,128,0.4);
 }
 
 #sources .selected > .black-boxed {
   color: rgba(255,255,255,0.4);
 }
 
 #sources .black-boxed > .dbg-breakpoint {
   display: none;
 }
 
 /* Black box message and source progress meter */
 
 #black-boxed-message,
 #source-progress-container {
-  background: url(background-noise-toolbar.png);
   /* Prevent the container deck from aquiring the size from this message. */
   min-width: 1px;
   min-height: 1px;
 }
 
 #source-progress {
   min-height: 2em;
   min-width: 40em;
@@ -179,16 +200,27 @@
   list-style-image: url(tracer-icon.png);
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #trace[checked] {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
 
+@media (min-resolution: 2dppx) {
+  #trace {
+    list-style-image: url(tracer-icon@2x.png);
+    -moz-image-region: rect(0px,32px,32px,0px);
+  }
+
+  #trace[checked] {
+    -moz-image-region: rect(0px,64px,32px,32px);
+  }
+}
+
 #clear-tracer {
   /* Make this button as narrow as the text inside it. */
   min-width: 1px;
 }
 
 .trace-name {
   -moz-padding-start: 4px;
 }
@@ -275,23 +307,31 @@
   max-height: 125px;
 }
 
 .dbg-expression {
   height: 20px;
 }
 
 .dbg-expression-arrow {
-  background: url(commandline-icon.png);
+  background-image: url(commandline-icon.png);
   background-position: 16px;
+  background-repeat: no-repeat;
+  background-size: 32px 16px;
   width: 16px;
   height: 16px;
   margin: 2px;
 }
 
+@media (min-resolution: 2dppx) {
+  .dbg-expression-arrow {
+    background-image: url(commandline-icon@2x.png);
+  }
+}
+
 .dbg-expression-input {
   color: inherit;
 }
 
 .dbg-expression-button {
   -moz-appearance: none;
   border: none;
   background: none;
@@ -537,32 +577,50 @@
 
 .theme-light .dbg-results-line-contents-string[match=true] {
   color: #18191a; /* Dark foreground text */
 }
 
 /* Toolbar controls */
 
 .devtools-sidebar-tabs > tabs > tab {
-  min-height: 1em !important;
+  min-height: 24px !important;
   padding: 0 !important;
 }
 
+#debugger-toolbar .devtools-toolbarbutton:not([label]) > .toolbarbutton-icon,
+#sources-toolbar .devtools-toolbarbutton:not([label]) > .toolbarbutton-icon {
+  width: 16px;
+  height: 16px;
+}
+
 #resume {
   list-style-image: url(debugger-pause.png);
   -moz-image-region: rect(0px,16px,16px,0px);
   transition: background 0.15s ease-in-out;
 }
 
 #resume[checked] {
   background: none;
   list-style-image: url(debugger-play.png);
   -moz-image-region: rect(0px,32px,16px,16px);
 }
 
+@media (min-resolution: 2dppx) {
+  #resume {
+    list-style-image: url(debugger-pause@2x.png);
+    -moz-image-region: rect(0px,32px,32px,0px);
+  }
+
+  #resume[checked] {
+    list-style-image: url(debugger-play@2x.png);
+    -moz-image-region: rect(0px,64px,32px,32px);
+  }
+}
+
 #resume ~ toolbarbutton {
   transition: opacity 0.15s ease-in-out;
 }
 
 #resume:not([checked]) ~ toolbarbutton {
   opacity: 0.5;
 }
 
@@ -573,32 +631,61 @@
 #step-in {
   list-style-image: url(debugger-step-in.png);
 }
 
 #step-out {
   list-style-image: url(debugger-step-out.png);
 }
 
+@media (min-resolution: 2dppx) {
+  #step-over {
+    list-style-image: url(debugger-step-over@2x.png);
+  }
+
+  #step-in {
+    list-style-image: url(debugger-step-in@2x.png);
+  }
+
+  #step-out {
+    list-style-image: url(debugger-step-out@2x.png);
+  }
+}
+
 #instruments-pane-toggle {
   background: none;
   box-shadow: none;
   border: none;
   list-style-image: url(debugger-collapse.png);
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #instruments-pane-toggle[pane-collapsed] {
   list-style-image: url(debugger-expand.png);
 }
 
-#instruments-pane-toggle:active {
+#instruments-pane-toggle:hover {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
 
+@media (min-resolution: 2dppx) {
+  #instruments-pane-toggle {
+    list-style-image: url(debugger-collapse@2x.png);
+    -moz-image-region: rect(0px,32px,32px,0px);
+  }
+
+  #instruments-pane-toggle[pane-collapsed] {
+    list-style-image: url(debugger-expand@2x.png);
+  }
+
+  #instruments-pane-toggle:hover {
+    -moz-image-region: rect(0px,64px,32px,32px);
+  }
+}
+
 /* Horizontal vs. vertical layout */
 
 #vertical-layout-panes-container {
   min-height: 35vh;
   max-height: 80vh;
 }
 
 #body[layout=vertical] #sources-pane > tabs {
new file mode 100644
index 0000000000000000000000000000000000000000..2dd33d2fa0337ff2e6c1056dd548e498d65044ba
GIT binary patch
literal 988
zc$@*;10(#2P)<h;3K|Lk000e1NJLTq002M$001Be1^@s6qMd$(000A{Nkl<Zc-rk)
zOHUL*5MEFO6(1l(qehAGAd3$$3WoR~(HME?Kk$(dFCHZ(ni#>8#tSzu#tY$M@P+;W
zeh3F{goK0a&U9CI&n9gZ!OYmxL(k4G2W2X$q|=Y8{;I#K?wLX^x#W^dF8{Gesm}=^
zPH$WAOo+RrbqRvjdKVh)VWKhuP<g;WJtoEWBgVv-@2g&%)8aIg-T#hqxqJu!x;#&I
zODTIuv0VhvT{zycu%LIE;MbL@i?u|u2OxOPxcq|pb5j+;SCGL=U#Z@D0VmK1;1UI%
zz&Lc&1Gr&=$KW(%iFg}>VA2NE=W>>d;2kNoZF6XCN$d8kz?YVmS~yn&HlRWTx61%m
zNvUm&*ACv3vdBTrn4l2wG*LZjl-krsW0pY80O*s|`Y)AqyDvlxm<Mp3sBZfAJg-xT
z%5Yu4`%<1qDu0fH5zn-y9J33JVGt%_K?5}l04J)Te210pY8_Ccqu^P=Q~gNgd`*Z`
z2x885@=@@PAWX;8L4EwC^}!VD_cJC&Y$u-;JlFb|4{#IvnqAHh&_=B}@BdxYIOoUF
z(YO!lKDDD`G}mT>>IM2}O7g$BxY&%ezh%p1qP?-#Rs>$;O7*+IBb&x(jOv9Yh#^Hd
z$Tvv^kBmmaV}Fz-bJQ2jNm^sKB_?cnIW;xa$ayfBRPYQsw}$!d25+QMpACG2nY-r6
zMd#DWn{%iyI)~OI+4GosViqylybb)HlQSq^bb{w0cvC55%m6ep2wtZR0MWjd05Fh~
z<TDz85?5E!2Eavo5?YgFkJiUnxVpN!kAeEI)hU2J-Y+lfJr))=QK53AfTQ4r{*&GY
z0KpIyYR3(bvIv42Cv_IVFp!^UZnZZ6Dc$o<@S&ZKgh1Y*`Vn(%<o9LV?0kIlD)lu;
zb1eb*p5{I7`~G2T?m8biVLAf@*W%zSqd3-??jb@3xcp|R1ryGP5D%VOz|VUCLxq&W
zm_EvI!v=^{;Bl<a61*|G5en4^8KOM4Fd^|NoN)4UT&aPopW>eG4D;BX^F;fElOIM_
z9VujywY9Z2AA%Q51V6HT3}OFU+!#8~CiF$=y6QIj7~(-NxU#acw@|ClxAq3LV+GQh
z>mXktBQDhbGsFGJvE@4Fs=rJx$9fB0RrDjm$7<?>dKmp7f}6QVCx&x&XZMi)!=^0d
z8Qja+4K30QMmI%RqyAy>r0kA?-)=%d*p}eU>R!ktmt1n$Lir61?3fPKG%e5o0000<
KMNUMnLSTYLw%RfP
new file mode 100644
index 0000000000000000000000000000000000000000..12f7223bcba6404f5fceeeae090537d9dabf11c3
GIT binary patch
literal 338
zc$@)J0j>UtP)<h;3K|Lk000e1NJLTq002M$001Be1^@s6qMd$(0003QNkl<Zc-rll
zF-`+95Jjh@;0hF^q#@iO_kfxkBo|1J6p4ZpB%FaGP*UX#+aR&yvB!nMMb_zdH?o=L
zKj}BS#mZ0mUXKX?001cTF05Jftwmgj>(TFv?*DRZlmm^?X7MEx1RPstvH<VXf@bpV
z{i2otW__TUu<Ej!0J@M`0_Z}T2@uhzl+u-z0G7Dzy6)Ocfb;Q%C2S?78yf-o$r3+8
z2zPb@@;j%mKO!p&0r@$Le)SR_%>)eb`kX8A-vUlyr@LGU0|6yNB@6`QluDQhD3!1f
za9#-rKn1V~;x~wc5RSq`7=~&I$fGUI#Cv~ROF$lNXD?p$eoO!^M8<F-HoFq*UEE9%
kaA+pLg?9n~000Jl0lAU&+5tvoxc~qF07*qoM6N<$f;|3+?*IS*
new file mode 100644
index 0000000000000000000000000000000000000000..091601f5c65085589f02f859b3d10251011b010e
GIT binary patch
literal 350
zc%17D@N?(olHy`uVBq!ia0vp^4nVBH!3HE3&8=%;U|>}Bba4!+xb=31VIGsAfNQLB
zVYi5a)Co%qr*#wT*gaTdZ^<ZJShDa5hh}hL+gc&xZRs&NWvU&m8fD@)oj=e2B7OVP
zDgWg4xy(Q#Rr>nEpHze^=^Y7u{(WzaiKWP?V<mxUpEPY*WW1JbXGohjt1;sA<rt=p
zCl`5IT*Pj&%#c2*df-mStcG3d-1Sa;-pdjaYN{q5|6b{VdJFsO!_~J!N>~j(_te~(
zJYBz((IwGSv2I(k3G0%&Iwk%4ZzWV2S3cR>um8Nx708&oXYOxN)+LkjZ+``oqO38~
z_pEDpJ*mAxErMxzf^bgL{RCwOc14+1{e!6*(;2ec<J;?Js_$f(a3bl$6W3?4Odk|3
uwQ_HKFj-=KHOCDR;RhAjKwmY~|6@3_L`Zv<#>E#vE`z75pUXO@geCyJjf|}T
new file mode 100644
index 0000000000000000000000000000000000000000..53ed141a2cb726197443d51c669bbde60067ae5e
GIT binary patch
literal 189
zc%17D@N?(olHy`uVBq!ia0vp^4nVBH!VDw>HYaZfQqloFA+A9B>(^fvs~>;){_D%P
zpI^WIv|9VjYRxmtHP22Q_z(q@<|qmB3uZ9(oWE%Eqh|qTj_-kDo}Mm_Ar}5`PZ=^c
z7%&_;(6yrdoJYb6rV3_{gjGxxH)ZX#xFqwuG*5?Yb<Wz?xXaJuu>J%Uk8Ka-_gR$4
kPH2-~u!=Y6GwW&-M)8TsbJMSN&SU_Y>gwmR&MBb@0BNsC761SM
new file mode 100644
index 0000000000000000000000000000000000000000..5fe68e1d1dc71e9c53cc70391649edf409bebafa
GIT binary patch
literal 494
zc$@+50TKR*P)<h;3K|Lk000e1NJLTq002M$001Be1^@s6qMd$(0005CNkl<Zc-rll
zO)ErE7{^JCNX=FxHj*sZ&AnGBZ#7M77OX6+WhFaSSoi=eWJzXp=Uy+PBx_&cMu~;F
zbEhmu+;h*{ojQ!@DA}H;IKTRTfb;A0yftVv8jVKtJA}mJ5vyfA&r37_U^>CI<plR;
z!Jh*WfC$S)n4L$2?;*s800f9{xlB2wUd00VQC+}S5T+|ah$N5zVL4@Ly1|BNms%7H
zkimrK5RIpW5RJ+N{LF)8`xy`cQUu$=7<WMkkUt_gwcMavwE!6!A0xu%Wsy`UKqfK@
z{_243B~%NLp?nu1J_sT}=Ef}e6Y2%X-%!MqFITJ8q<R4|mRrdW=N~&13y`U|PDJS>
zhya-v!|@j&1js}z3nD<a%x7Q$H1jWi04Dq(B=Hzz4KVFeCqxY>MwBl-3DE}C1yXi-
z3aAV4oiJqmP;-Fg_-pBlyJm<%$ibM+3L%<6CPBAyW}H-dU?xGr))7MOP`iNPlxIM8
z0fgE9D~U&-wn57cHq~!~vdAq8AzC5U;rEAMDZd9`!Vf=-+%~1O59lIv3w99-2^pod
kmi{Pm8jVJy(fldi0DbebsQl21%m4rY07*qoM6N<$f@tB-I{*Lx
new file mode 100644
index 0000000000000000000000000000000000000000..c0b45008e1d28eb7e29d5a7d06ac6bb2a7af38b8
GIT binary patch
literal 355
zc$@)a0i6DcP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80003hNkl<Zc-rli
z!Ab)$5Qaz4`T`=JdsJ_}ga>c-J=FJ*K7c(43ZjS7N}pxlLriv(nF=ujJ=md3+LG+W
zd@!fXKmX5cb{8rYP22L7MS5wG79BM;&QC1*hcEAkkQ-&5s!!{e*S!bOA-oU6f{!3w
z==lyx6GAvJA)Fed+h{sl3*mSIeS%1%^$jeipeVhKGyNF)4sg_a0L{M%AnP8qj-RaK
z_rIkB=yTCT?QxdZ&muo6!(@p@w2*`E5KhqX>$c|7D;mg&A-asi=UW7rOK+Kr=YMWe
z0i*{?Vv+B=%;gM9(=^Q>8jUxYkL3(XqA#VsbVYx|awa{n!Wg=+NFUMs%@CdMJV&EM
zonrNkIF#CZ&dx%|&q|hSPI`4B+OWF)=Ts_{6~FyWjT3t-aQ6TJ002ovPDHLkV1h~P
BpE&>k
new file mode 100644
index 0000000000000000000000000000000000000000..1c19679354bcde539334d1e8b72ac52ec34014bc
GIT binary patch
literal 339
zc$@)K0j&OsP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80003RNkl<Zc-rlh
zF;2rk5Jd@*XedycG$<!PR8(Ao6XX;rQy^}Tmd+AF5m2GzUM}!@#<Pp`qfIlCg%vXx
zZ?j(-$<lcKKJWhZSytA63BV*NfMZ{<gf)Q6BmgzZ{#cEFCc+{KfI@_iFADc*02E^9
zSe7vxDFC!s9{-#RB3#FS#jREIex<pfg?VvD@~(!)VqX9_|207Tay7D-W7(PjbldGk
z>|aH2GfcQxo&e1HhLD=Dsj6x|Sh{ub(*Q(a1>j=P>(%o|^jSCG5853IK+pSDJ0k%&
z_HT~7p7+=QWm#SUn8xP>00t!ksB3@cNM5?`ZS2K&x&Vwms@72Fa2J5V#{j^-XpPx@
l`X7M~{jha*pRBB`mJXmYhfw$+bMXKG002ovPDHLkV1hJ@l1l&p
new file mode 100644
index 0000000000000000000000000000000000000000..452f0d459d50ab45eff3c8854bf1ad105d1e285b
GIT binary patch
literal 472
zc$@*)0Vn>6P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80004>Nkl<Zc-rk#
z&nrYx6dpDr7FL8|BjqoUl~RhGV#Cf3v9Mr)r6{71nguIAN|{XKf0~6^$vr>rIa61+
zsF^qKjd?RB(fjK3>UQ7xzVDpxrV=Db@MlmcxUP57DP-9|)?*;KHY7KIas_1f5$z%o
zjr=&VbFLRy)`0a1(7t?E2Ice(apFhF4eKnDJo?6Q%TMLix$Z_?w<F7@Z`4$=WDs#E
z*x#?K7I_=8Y(+bh%sa@sZvwV7Z%9^<>=Cf$!tNu<oflC~?$09ype-#_JJCF%?$iod
zu~_UeWS2u%#*%T)b$*dY3P5yeK>HkU@QA9DG1k`)EEm<&kW2rqDF8_K0zYQ0wc`zB
zs4GYqtPuA&=to_CiCJeFenH0dc>{F|Fld)5M$dZg1DR?9WOcW00YG|K>Ap1A2x&0w
z^-qB8L-_?F8m<xvFyM3YIoceM9x%JnDDt=&NE7Kau&f%CGyiNVcWU~P<UXWXmf4NP
zA@>zORLI*=&5-R$s?|V}+ek;OdFNcmzZ3BLD~K1u)+72ECP<K=348!2Po;B5Te9W=
O0000<MNUMnLSTY27S)^p
new file mode 100644
index 0000000000000000000000000000000000000000..b8bbadd170b630a1050023af06bf8fff6f395306
GIT binary patch
literal 1676
zc$@)@26Op|P)<h;3K|Lk000e1NJLTq002M$001Be1^@s6qMd$(000J4Nkl<ZXa((A
zOK%)S5bo-k-C27dke84V4oZ-01Bu{-z(j#l_zRri#)&HzI3R)$e}EImocIfHBA_G^
zLP!YmFp)SUh8U5=#PND}W@oziYQ3}DGt;v>PAo2Xq}BA)v#Ywgy1G`WgXh2l|7#D>
zUaar+dM6U4UniwsAjOMJ)D(VV-eyJXtjx8T`0|sD#_xNPTa}iXJ7K66A`>6cTD3?e
zHQ{BY2F7^Xb?x5^0(FOowrrUj8{C}l#4oky{GWj9+qg;&co>GKJXY_rQ7blc*%%YL
z&Yrr^4uW6HX!Zi%+TtU%TKv4B<WLnpGT!|-_HT4LbWe<LwZc{MRYSAaZ8ZGLwiBj0
z03NUR!oho3-E+#AskDWOY0Zd|Z{~v9mrxiL@D%{i?{&L_Q$*&t@+vUcB#BbBn!X)H
z=9m~SD2Ur>HvAtkp9d*vsZmhg?)C;B!-(aS_)*?)-dc06_6d|e+zbCzIR9inex>TF
z&yqbd{u1Ufdke_Q!}!l({L(DJRq!Jxd<f;0B0*NZAIGnmq*}<&nnRiK!lzx#688IL
zyvUa*5}7VtE|`S<J{zCe_qI;o{*dwZc6fTY=Owq6JfwAG60?T%e}72AbXa?OnT@HJ
zus|A}gY1i~R`5$1jlj2<kI+tdd>V~XzR}DN5GlimLS)qq(GyC=nFSrKycpjrlUHzn
z_SHKu#GM<E*w`c&$*3!&C{##u<Hx7~$K$K4)tk<Q37H}r^dF3Q9D`aP*}g2{A+aH7
zH;B9q!#l$-fv}u|o;O!hKJryO<aQYkhXrs(C_L$t@z_88{>jp#4#sOXUlHqXAt)n6
zgb574i$bG7n<}v1B3pFWFnbLCPHEDExyC2d7lS|8GG6#2t?Rz>o||F(T({ppBRXuI
z83*`Gt2WXeW97fcBCgpm*H~x}A>X-h0-L5oikvb&BK?<+<IOx?jIWWugrX=AoUHuj
z-HlAx!)P&n6{|m=*O766RCsv>qwh#7?@|e_0FKq*3V?9|0l*X;N-8`Tk4lumseUJq
za~Y3w{?g*&;&{*7#tVO5N6rDfm{%g-*kfxDR0w+)t-w6?`$&||Xu*l#)FFIQ-pV`w
zXzd~4P4>Lt%Z$e=kI(tM@xqtak#PVRoD)E{|67>%@))C>Jf%7y9Eo*U+voKdkH<y{
zu8Q$DZ{7^ju(##qbrenko7ftc<z<T`JA|hdlHR^N6svJotztZ!EG6^`O6d7n6UamW
z!e`sXpo!dj(?vXi9h(d%qcR;r!HNeH%E<^k!vt5|`0i2m64V97C$A&p02C{?i!8$&
zu#`^1h0nu;7PZ839=`j{>uxxXIis8lU`)~(p7XhEF9{F2#@l0e<-13<Zq~J-%yF0M
z)HxkF2bgOqVeVZ=(Oh~&=4hqB7Fy@_Lg{q1KtBS=ecp-ll$IjLKI+PhheNPk2Mf48
zT2<OaY8;#0>U9LlI6#^i6VmJmmCaeyHzlh&p%ogToj|gqaEcCTW@M@DuS|vKTzN6x
z`2Hc3t54aS_PE`y|7m|`93Y(>9ADRMg7Fv?j$!pSlX)Ydc(^CWI1B_vq|OSBvlIpm
zX?0^-w+IGoCCr6lC)HyWIgYT`bu<8;M$_X&S?>V2P0^9clXoFZ<T6CQ{;bT15p#rV
zz#0mK-Ebm0Osph2N-slF9{c2f6a+N$GUV-qt9c__wY?m53k%P#cmcIdgrtY9GMZoU
zg;F|6qAQ(F=dO(`I6#m){pfu*{FY7lxMR}v^?alLMHM+w6upcl-?(6M8IOdA@-{|!
zo6J1wL?_4kR;yM2Ij`U8F==DrMEEUF{HWiMwI(vF@JkO*+E^*=ei$#I-)z>L{Yj?-
zuur0>zkOaNo=)UZ-y#FDJ{6g>WY`T%q1`Xz(Fpwk`mqU$k8=y8WC*kWv?;o^Gg$R>
zGAz1A#^<wr^{|Jz%=(tBe3|iL=TrxzjtE0Oop6LkNwv1PWP6Y57iJ~ARDSw~!yA3<
zYs{S-=R9ir_)L{OFCjk|^w1X1CcIRS{jTJIltG#qa-YY(c@cAb1`}=z4jz68f3KtT
zzxud;jEEB;QaU%|)^(~MxZxio2G?eTo1;IvJjE!XrNYxCe~hI32k!$99C)DMfqwx6
WH$#lU7u*Q|0000<MNUMnLSTa5gf`p&
index ddd344eee0166575530d7cad90bf9aab4b277b09..401f26b3f05589e8b65a160ea47371085c751ed7
GIT binary patch
literal 430
zc%17D@N?(olHy`uVBq!ia0vp^d?3uh3?wzC-F*zCwgmWuxB_X0&<2LwNel@M><K;W
ziM=2)z8gd)^s>eGuz|R}EY{&HHsP!`;cO1E?2fVQF3B88eVo4eoJswh$^E<;(|9wd
z@nuaHh;0^(Zxc%G6H4tD&YUcgJxwHMhG_n5v4VM$>3y=f6J+xy$Tn`4Yuc>XzD2QP
ztKRHO`g1NB&c9~7_=@?8YvwB-TC99zx%!snnn#vvo>{HAWwrL1_4;Qv8=l*3e(JF8
zh0~5_E_+_3-CZ>o<gt<<zhDM7c8-k9+`RmPW5+-DW=bmpCFXd#IEHY@vL3wcci2F{
z_2SzfJfcby7O8}){`-HqVx?vtr;W*D?rctuWsPBbnjJ3xGWoQ7d9`E4-wn@f_V>(R
zsp0cHkzMLz%(_iiv+n8@)*YQDe^M*D?)A*%sU{LjJb7MruwGO$WJzjh+vWc3_KEvk
z_nxv&D|l#Pz4X-nHv!Xy*M2y$u5SC&4a_z{>lVel-p!-`^BKpV>h8vS-?;ueD0@2}
Sdg^Kc3V2UfKbLh*2~7a<udY7;
new file mode 100644
index 0000000000000000000000000000000000000000..7253aeee23f8a49de747dafc56519eafed432e4b
GIT binary patch
literal 773
zc$@(R1N!`lP)<h;3K|Lk000e1NJLTq000~S000~a0{{R3Mbj^)0003sP)t-s00008
zV2=l2jR|6m2Vji{VT}l1j0azg2Vjf_V2cM}i~wet249T_V2l7@p9WxzI<4j?lEw{b
zmpQEFIjrRmYnTH$R6DNb4^eL&fUP~T=L}0|G^E@ad#n|1nm@JZ6ls)0y6G;P$su{9
zH>u$YNM{vroh+BlII80?p3etEVGC1tLAU4*YM3L6xC%*UH>lk_tJx1-gcNI*5n_u8
zN^2g1wH0rj2~2JcPH#cB<vz3IKC|FBsp1rEnKPf&6>OL}tmHwq<~_3K5@Cr$y67*Q
z$_{InEt$?VqtOm(mqfhk4pw>}gtk7j=n-R#Lb>S)PH-cMwj_(V7I2;iMr9y{wmq=q
zIjiI+fTcXI<2bA2BZ{{OMrI*+q$Yr;4o_|lP;VfHw<dq3IH}<$fu=yU=&Q|2(EtDd
z5Oh*bQve{jJd}y1<N0^^3VGxRcpVk;I{*LyZ%IT!RCr#E(#3M)KoEdoYlL>}-4TP4
znVBKX%)I#ikB~{Lq;iyt|J&48eVDHHtrz7-d?3IhKlP1hPQ5yq?CeYqu2hcxYOK1O
zj9WU!x)o3E?y)QMgE2R^WVfwNli6^QxvXG$C0u!C9AnM@vZuJo+D7k<<!_Bu8jwq2
zfN6TDX~Xms5T`i!pK5{4v!&zh?c-8*4r-|sL4VDjt9KKzem|Dz*8hUmeiB4Q!1T=`
zcTLJIUS0u<h#=np{M8}`+Ik0U@B{-(K&?#xYHNT?fuL}2<IKUCjZdG*M*xkEgB=4f
z!V^UMfNui_2Y&$f5kdbg;9a<ETRHp)_&Z3Dsw%LOX87VP7CXBLH(_B)m4fw&FshIK
z8JcE1jC6R4oW1zN3@AQ5KRP-;#bB93+6S>wMfRjb-D6;S9#wjb&uN2+W>zf|g)?zt
zCD9#LM2YpDrgV~z6bh02i9}A*8x=$$K?tEB<y)%{@%~5;jlr(u00000NkvXXu0mjf
D8aq^8
index a00649b7704af40bba94d5a781cba32285d9dbae..d590573a8f0a1c84077c5087ec0389a23ddf55b6
GIT binary patch
literal 266
zc$@(W0rmcgP)<h;3K|Lk000e1NJLTq000gE000gM1^@s6A4o0H0002dNkl<ZD9>YH
zAQ!Nwi@#^j5dV({e`n7SOUKa2kuC9GWS9Pb(ftOPc%Q+4frT3X*|Ws|uw_bwqH4^Q
z{4cmx`@i5C>{w`%?tg)K>Oh+%esW}pJE3Y^qm35}Z`S?KKV22%><_G|q6S231Z&=^
z`=7T@38eWsN2a(E(Hg;;x9R=oD3Anc-b|84o-ReObJ^0wb&1x<+Yff;SI!JEYoh(g
zH$?>;s_f|!UWfz$j7NbLTDUb$1!~F^|HqLo7K55{GQ>X;OgZUd84Lps0I?qB8A)j>
Q&Hw-a07*qoM6N<$f)NCA_5c6?
new file mode 100644
index 0000000000000000000000000000000000000000..b1dd7ee0b20beab27be41386b758ed254c1cb63a
GIT binary patch
literal 462
zc$@*w0WtoGP)<h;3K|Lk000e1NJLTq000~S000~a1^@s6at+^<0004%Nkl<ZNXKJf
z81{f8L;N&*hWLMqaHjY@woHjoqAaA?G6ep^mLV2MoQ0X<|Ap3R{}<V%Pa58-_g`?i
z=6}v2DVU|-IMOBU2wKRIEBRk&gU)}!wb~?Op^ZBKg@JlG@+D!Geqc)z*ClA7;2LeR
zvCw**|3a&^{&QqYz$|^jku51tGYi3%f<kDiCOCQ^mWtir$(0bLnT23WH|hQtn5Pa;
zNWdI$k|9lsi)I#ru<&Nx|NPTbkg|-#c7{-GHkw%owsfoRf8IVNxTU~6x)5j;Bh4%X
zTe?l}KToSXvQK+yY9R=VY}fnGSuTz6X@*!dO)Ugrt|}RXFViHlX=)*Nn*wr*T?URv
zT3N``ql8q>O6-8AR9aZbJ4qQSg^Hg*$&1vskZ-ywQfVW8jW<n7gk~9^e~#LJc6fOz
z_Lwt8T83st8~+0J{~S5+!uvH_mZT=l3U7hM8pzetSI#tXbAlBx|9o}QEnB7apKpfh
ze~v<U1^g2jl<xFuV*F!E7muV*BMO*6fR=?bjDisX032wF|1Y@&$p8QV07*qoM6N<$
Ef;`2~vH$=8
deleted file mode 100644
index 9fd78175dfd5449b43034602ed854907ea82d885..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/devtools/images/profiler-stopwatch-checked.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 16 16">
+<g fill="#3BACE5" fill-rule="evenodd">
+<path d="m8,1c-3.9,0-7,3.1-7,7s3.1,7 7,7c3.9,0 7-3.1 7-7s-3.1-7-7-7zm-.1,12c-2.8,0-5-2.2-5-5 0-2.8 2.2-5 5-5s5,2.2 5,5c0,2.8-2.2,5-5,5z"/>
+<path d="m8,6.9c.6,0 1.1,.5 1.1,1.1 0,.6-.5,1.1-1.1,1.1-.6,0-1.1-.5-1.1-1.1 0-.6 .5-1.1 1.1-1.1z"/>
+<path d="m11.3,4.6l-3.9,2.5 1.5,1.4 2.4-3.9z"/>
+<path opacity=".4" d="m4.6,10c.7,1.2 2,2 3.4,2 1.5,0 2.7-.8 3.4-2h-6.8z"/>
+<g opacity=".3">
+<path d="m7.1,5.1l-.6-1.3-.9,.4 .7,1.3c.2-.1 .5-.3 .8-.4z"/>
+<path d="m9.8,5.6l.7-1.4-.9-.4-.7,1.3c.3,.2 .6,.3 .9,.5z"/>
+<path d="m10.8,7c.1,.3 .2,.7 .2,1h2v-1h-2.2z"/>
+<path d="m5,8c0-.3 .1-.7 .2-1h-2.2l-.1,1h2.1z"/>
+</g>
+</g>
+</svg>
deleted file mode 100644
index bed5c77b7f0b788926be7dc0c7ba2cb8446af78f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/devtools/images/profiler-stopwatch.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 16 16">
+<g fill="#edf0f1" fill-rule="evenodd">
+<path d="m8,1c-3.9,0-7,3.1-7,7s3.1,7 7,7c3.9,0 7-3.1 7-7s-3.1-7-7-7zm-.1,12c-2.8,0-5-2.2-5-5 0-2.8 2.2-5 5-5s5,2.2 5,5c0,2.8-2.2,5-5,5z"/>
+<path d="m8,6.9c.6,0 1.1,.5 1.1,1.1 0,.6-.5,1.1-1.1,1.1-.6,0-1.1-.5-1.1-1.1 0-.6 .5-1.1 1.1-1.1z"/>
+<path d="m11.3,4.6l-3.9,2.5 1.5,1.4 2.4-3.9z"/>
+<path opacity=".4" d="m4.6,10c.7,1.2 2,2 3.4,2 1.5,0 2.7-.8 3.4-2h-6.8z"/>
+<g opacity=".3">
+<path d="m7.1,5.1l-.6-1.3-.9,.4 .7,1.3c.2-.1 .5-.3 .8-.4z"/>
+<path d="m9.8,5.6l.7-1.4-.9-.4-.7,1.3c.3,.2 .6,.3 .9,.5z"/>
+<path d="m10.8,7c.1,.3 .2,.7 .2,1h2v-1h-2.2z"/>
+<path d="m5,8c0-.3 .1-.7 .2-1h-2.2l-.1,1h2.1z"/>
+</g>
+</g>
+</svg>
--- a/browser/themes/shared/devtools/netmonitor.inc.css
+++ b/browser/themes/shared/devtools/netmonitor.inc.css
@@ -16,18 +16,17 @@
 .theme-light #requests-menu-empty-notice {
   color: #585959; /* Grey foreground text */
 }
 
 #requests-menu-perf-notice-button {
   min-width: 30px;
   min-height: 28px;
   margin: 0;
-  list-style-image: url(profiler-stopwatch.png);
-  -moz-image-region: rect(0px,16px,16px,0px);
+  list-style-image: url(profiler-stopwatch.svg);
 }
 
 #requests-menu-perf-notice-button .button-text {
   display: none;
 }
 
 %filter substitution
 %define table_itemDarkStartBorder rgba(0,0,0,0.2)
@@ -608,18 +607,17 @@ label.requests-menu-status-code {
 }
 
 /* Performance analysis buttons */
 
 #requests-menu-network-summary-button {
   background: none;
   box-shadow: none;
   border-color: transparent;
-  list-style-image: url(profiler-stopwatch.png);
-  -moz-image-region: rect(0px,16px,16px,0px);
+  list-style-image: url(profiler-stopwatch.svg);
   -moz-padding-end: 0;
   cursor: pointer;
 }
 
 #requests-menu-network-summary-label {
   -moz-padding-start: 0;
   cursor: pointer;
 }
--- a/browser/themes/shared/devtools/profiler.inc.css
+++ b/browser/themes/shared/devtools/profiler.inc.css
@@ -65,15 +65,14 @@
   color: #b6babf;
 }
 
 .theme-light .selected .profiler-sidebar-item > hbox {
   color: #ebeced;
 }
 
 #profiler-start {
-  list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch.png");
-  -moz-image-region: rect(0px,16px,16px,0px);
+  list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch.svg");
 }
 
 #profiler-start[checked] {
-  -moz-image-region: rect(0px,32px,16px,16px);
-}
+  list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch-checked.svg");
+}
\ No newline at end of file
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -787,17 +787,19 @@
   filter: url(filters.svg#invert);
 }
 
 /* Since selected backgrounds are blue, we want to use the normal
  * (light) icons. */
 .theme-light .command-button-invertable[checked=true]:not(:active) > image,
 .theme-light .devtools-tab[icon-invertable][selected] > image,
 .theme-light .devtools-tab[icon-invertable][highlighted] > image,
-.theme-light #resume[checked] > image {
+.theme-light #resume[checked] > image,
+.theme-light #record-snapshot[checked] > image,
+.theme-light #profiler-start[checked] > image {
   filter: none !important;
 }
 
 .theme-light .command-button:hover {
   background-color: inherit;
 }
 
 .theme-light .command-button:hover:active,
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -1,17 +1,5 @@
 /* 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 ../../shared/devtools/debugger.inc.css
-
-.devtools-sidebar-tabs > tabs > tab {
-  min-height: 22px !important;
-}
-
-#instruments-pane-toggle:hover {
-  -moz-image-region: rect(0px,32px,16px,16px);
-}
-
-#instruments-pane-toggle:hover:active {
-  -moz-image-region: rect(0px,48px,16px,32px);
-}
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -244,17 +244,19 @@ browser.jar:
         skin/classic/browser/devtools/command-pick@2x.png           (../shared/devtools/images/command-pick@2x.png)
         skin/classic/browser/devtools/command-console.png           (../shared/devtools/images/command-console.png)
         skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
         skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
         skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
         skin/classic/browser/devtools/markup-view.css               (../shared/devtools/markup-view.css)
         skin/classic/browser/devtools/editor-error.png              (../shared/devtools/images/editor-error.png)
         skin/classic/browser/devtools/editor-breakpoint.png         (../shared/devtools/images/editor-breakpoint.png)
+        skin/classic/browser/devtools/editor-breakpoint@2x.png         (../shared/devtools/images/editor-breakpoint@2x.png)
         skin/classic/browser/devtools/editor-debug-location.png     (../shared/devtools/images/editor-debug-location.png)
+        skin/classic/browser/devtools/editor-debug-location@2x.png     (../shared/devtools/images/editor-debug-location@2x.png)
 *       skin/classic/browser/devtools/webconsole.css                (devtools/webconsole.css)
         skin/classic/browser/devtools/webconsole_networkpanel.css   (devtools/webconsole_networkpanel.css)
         skin/classic/browser/devtools/webconsole.png                (../shared/devtools/images/webconsole.png)
         skin/classic/browser/devtools/webconsole@2x.png             (../shared/devtools/images/webconsole@2x.png)
         skin/classic/browser/devtools/breadcrumbs-divider@2x.png    (../shared/devtools/images/breadcrumbs-divider@2x.png)
         skin/classic/browser/devtools/breadcrumbs-scrollbutton.png  (../shared/devtools/images/breadcrumbs-scrollbutton.png)
         skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
         skin/classic/browser/devtools/eyedropper.css                (../shared/devtools/eyedropper.css)
@@ -277,38 +279,48 @@ browser.jar:
         skin/classic/browser/devtools/itemArrow-dark-ltr.png        (../shared/devtools/images/itemArrow-dark-ltr.png)
         skin/classic/browser/devtools/itemArrow-rtl.svg             (../shared/devtools/images/itemArrow-rtl.svg)
         skin/classic/browser/devtools/itemArrow-ltr.svg             (../shared/devtools/images/itemArrow-ltr.svg)
         skin/classic/browser/devtools/background-noise-toolbar.png  (../shared/devtools/images/background-noise-toolbar.png)
         skin/classic/browser/devtools/noise.png                     (../shared/devtools/images/noise.png)
         skin/classic/browser/devtools/dropmarker.png                (../shared/devtools/images/dropmarker.png)
         skin/classic/browser/devtools/layoutview.css                (../shared/devtools/layoutview.css)
         skin/classic/browser/devtools/debugger-collapse.png         (../shared/devtools/images/debugger-collapse.png)
+        skin/classic/browser/devtools/debugger-collapse@2x.png      (../shared/devtools/images/debugger-collapse@2x.png)
         skin/classic/browser/devtools/debugger-expand.png           (../shared/devtools/images/debugger-expand.png)
+        skin/classic/browser/devtools/debugger-expand@2x.png        (../shared/devtools/images/debugger-expand@2x.png)
         skin/classic/browser/devtools/debugger-pause.png            (../shared/devtools/images/debugger-pause.png)
+        skin/classic/browser/devtools/debugger-pause@2x.png         (../shared/devtools/images/debugger-pause@2x.png)
         skin/classic/browser/devtools/debugger-play.png             (../shared/devtools/images/debugger-play.png)
+        skin/classic/browser/devtools/debugger-play@2x.png          (../shared/devtools/images/debugger-play@2x.png)
         skin/classic/browser/devtools/debugger-step-in.png          (../shared/devtools/images/debugger-step-in.png)
+        skin/classic/browser/devtools/debugger-step-in@2x.png       (../shared/devtools/images/debugger-step-in@2x.png)
         skin/classic/browser/devtools/debugger-step-out.png         (../shared/devtools/images/debugger-step-out.png)
+        skin/classic/browser/devtools/debugger-step-out@2x.png      (../shared/devtools/images/debugger-step-out@2x.png)
         skin/classic/browser/devtools/debugger-step-over.png        (../shared/devtools/images/debugger-step-over.png)
+        skin/classic/browser/devtools/debugger-step-over@2x.png     (../shared/devtools/images/debugger-step-over@2x.png)
+        skin/classic/browser/devtools/debugger-blackbox-eye.png     (../shared/devtools/images/debugger-blackbox-eye.png)
         skin/classic/browser/devtools/debugger-blackbox.png         (../shared/devtools/images/debugger-blackbox.png)
-        skin/classic/browser/devtools/debugger-blackbox-eye.png (../shared/devtools/images/debugger-blackbox-eye.png)
-        skin/classic/browser/devtools/debugger-toggleBreakpoints.png (../shared/devtools/images/debugger-toggleBreakpoints.png)
+        skin/classic/browser/devtools/debugger-blackbox@2x.png      (../shared/devtools/images/debugger-blackbox@2x.png)
+        skin/classic/browser/devtools/debugger-toggleBreakpoints.png  (../shared/devtools/images/debugger-toggleBreakpoints.png)
+        skin/classic/browser/devtools/debugger-toggleBreakpoints@2x.png  (../shared/devtools/images/debugger-toggleBreakpoints@2x.png)
         skin/classic/browser/devtools/tracer-icon.png               (../shared/devtools/images/tracer-icon.png)
         skin/classic/browser/devtools/tracer-icon@2x.png            (../shared/devtools/images/tracer-icon@2x.png)
         skin/classic/browser/devtools/responsive-se-resizer.png     (../shared/devtools/images/responsive-se-resizer.png)
         skin/classic/browser/devtools/responsive-vertical-resizer.png (../shared/devtools/images/responsive-vertical-resizer.png)
         skin/classic/browser/devtools/responsive-horizontal-resizer.png (../shared/devtools/images/responsive-horizontal-resizer.png)
         skin/classic/browser/devtools/responsive-background.png     (../shared/devtools/images/responsive-background.png)
         skin/classic/browser/devtools/toggle-tools.png              (../shared/devtools/images/toggle-tools.png)
         skin/classic/browser/devtools/dock-bottom@2x.png            (../shared/devtools/images/dock-bottom@2x.png)
         skin/classic/browser/devtools/dock-side@2x.png              (../shared/devtools/images/dock-side@2x.png)
         skin/classic/browser/devtools/floating-scrollbars.css       (devtools/floating-scrollbars.css)
         skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
         skin/classic/browser/devtools/inspector.css                 (devtools/inspector.css)
-        skin/classic/browser/devtools/profiler-stopwatch.png        (../shared/devtools/images/profiler-stopwatch.png)
+        skin/classic/browser/devtools/profiler-stopwatch.svg        (../shared/devtools/images/profiler-stopwatch.svg)
+        skin/classic/browser/devtools/profiler-stopwatch-checked.svg  (../shared/devtools/images/profiler-stopwatch-checked.svg)
         skin/classic/browser/devtools/tool-options.svg              (../shared/devtools/images/tool-options.svg)
         skin/classic/browser/devtools/tool-webconsole.svg           (../shared/devtools/images/tool-webconsole.svg)
         skin/classic/browser/devtools/tool-debugger.svg             (../shared/devtools/images/tool-debugger.svg)
         skin/classic/browser/devtools/tool-debugger-paused.svg      (../shared/devtools/images/tool-debugger-paused.svg)
         skin/classic/browser/devtools/tool-inspector.svg            (../shared/devtools/images/tool-inspector.svg)
         skin/classic/browser/devtools/tool-styleeditor.svg          (../shared/devtools/images/tool-styleeditor.svg)
         skin/classic/browser/devtools/tool-profiler.svg             (../shared/devtools/images/tool-profiler.svg)
         skin/classic/browser/devtools/tool-network.svg              (../shared/devtools/images/tool-network.svg)
@@ -619,17 +631,19 @@ browser.jar:
         skin/classic/aero/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
         skin/classic/aero/browser/devtools/alerticon-warning.png     (../shared/devtools/images/alerticon-warning.png)
         skin/classic/aero/browser/devtools/alerticon-warning@2x.png  (../shared/devtools/images/alerticon-warning@2x.png)
 *       skin/classic/aero/browser/devtools/ruleview.css              (../shared/devtools/ruleview.css)
         skin/classic/aero/browser/devtools/commandline.css           (devtools/commandline.css)
         skin/classic/aero/browser/devtools/markup-view.css           (../shared/devtools/markup-view.css)
         skin/classic/aero/browser/devtools/editor-error.png           (../shared/devtools/images/editor-error.png)
         skin/classic/aero/browser/devtools/editor-breakpoint.png      (../shared/devtools/images/editor-breakpoint.png)
+        skin/classic/aero/browser/devtools/editor-breakpoint@2x.png      (../shared/devtools/images/editor-breakpoint@2x.png)
         skin/classic/aero/browser/devtools/editor-debug-location.png  (../shared/devtools/images/editor-debug-location.png)
+        skin/classic/aero/browser/devtools/editor-debug-location@2x.png  (../shared/devtools/images/editor-debug-location@2x.png)
 *       skin/classic/aero/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/aero/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
         skin/classic/aero/browser/devtools/webconsole.png                  (../shared/devtools/images/webconsole.png)
         skin/classic/aero/browser/devtools/webconsole@2x.png                  (../shared/devtools/images/webconsole@2x.png)
         skin/classic/aero/browser/devtools/breadcrumbs-divider@2x.png      (../shared/devtools/images/breadcrumbs-divider@2x.png)
         skin/classic/aero/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
         skin/classic/aero/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
 *       skin/classic/aero/browser/devtools/canvasdebugger.css        (devtools/canvasdebugger.css)
@@ -651,38 +665,48 @@ browser.jar:
         skin/classic/aero/browser/devtools/itemArrow-dark-ltr.png    (../shared/devtools/images/itemArrow-dark-ltr.png)
         skin/classic/aero/browser/devtools/itemArrow-rtl.svg         (../shared/devtools/images/itemArrow-rtl.svg)
         skin/classic/aero/browser/devtools/itemArrow-ltr.svg         (../shared/devtools/images/itemArrow-ltr.svg)
         skin/classic/aero/browser/devtools/background-noise-toolbar.png (../shared/devtools/images/background-noise-toolbar.png)
         skin/classic/aero/browser/devtools/noise.png                 (../shared/devtools/images/noise.png)
         skin/classic/aero/browser/devtools/dropmarker.png            (../shared/devtools/images/dropmarker.png)
         skin/classic/aero/browser/devtools/layoutview.css            (../shared/devtools/layoutview.css)
         skin/classic/aero/browser/devtools/debugger-collapse.png     (../shared/devtools/images/debugger-collapse.png)
+        skin/classic/aero/browser/devtools/debugger-collapse@2x.png  (../shared/devtools/images/debugger-collapse@2x.png)
         skin/classic/aero/browser/devtools/debugger-expand.png       (../shared/devtools/images/debugger-expand.png)
+        skin/classic/aero/browser/devtools/debugger-expand@2x.png    (../shared/devtools/images/debugger-expand@2x.png)
         skin/classic/aero/browser/devtools/debugger-pause.png        (../shared/devtools/images/debugger-pause.png)
+        skin/classic/aero/browser/devtools/debugger-pause@2x.png     (../shared/devtools/images/debugger-pause@2x.png)
         skin/classic/aero/browser/devtools/debugger-play.png         (../shared/devtools/images/debugger-play.png)
+        skin/classic/aero/browser/devtools/debugger-play@2x.png      (../shared/devtools/images/debugger-play@2x.png)
         skin/classic/aero/browser/devtools/debugger-step-in.png      (../shared/devtools/images/debugger-step-in.png)
+        skin/classic/aero/browser/devtools/debugger-step-in@2x.png   (../shared/devtools/images/debugger-step-in@2x.png)
         skin/classic/aero/browser/devtools/debugger-step-out.png     (../shared/devtools/images/debugger-step-out.png)
+        skin/classic/aero/browser/devtools/debugger-step-out@2x.png  (../shared/devtools/images/debugger-step-out@2x.png)
         skin/classic/aero/browser/devtools/debugger-step-over.png    (../shared/devtools/images/debugger-step-over.png)
-        skin/classic/aero/browser/devtools/debugger-blackbox.png     (../shared/devtools/images/debugger-blackbox.png)
-        skin/classic/aero/browser/devtools/debugger-blackbox-eye.png (../shared/devtools/images/debugger-blackbox-eye.png)
+        skin/classic/aero/browser/devtools/debugger-step-over@2x.png  (../shared/devtools/images/debugger-step-over@2x.png)
+        skin/classic/aero/browser/devtools/debugger-blackbox-eye.png  (../shared/devtools/images/debugger-blackbox-eye.png)
+        skin/classic/aero/browser/devtools/debugger-blackbox.png      (../shared/devtools/images/debugger-blackbox.png)
+        skin/classic/aero/browser/devtools/debugger-blackbox@2x.png   (../shared/devtools/images/debugger-blackbox@2x.png)
         skin/classic/aero/browser/devtools/debugger-toggleBreakpoints.png (../shared/devtools/images/debugger-toggleBreakpoints.png)
+        skin/classic/aero/browser/devtools/debugger-toggleBreakpoints@2x.png (../shared/devtools/images/debugger-toggleBreakpoints@2x.png)
         skin/classic/aero/browser/devtools/tracer-icon.png           (../shared/devtools/images/tracer-icon.png)
         skin/classic/aero/browser/devtools/tracer-icon@2x.png        (../shared/devtools/images/tracer-icon@2x.png)
         skin/classic/aero/browser/devtools/responsive-se-resizer.png (../shared/devtools/images/responsive-se-resizer.png)
         skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (../shared/devtools/images/responsive-vertical-resizer.png)
         skin/classic/aero/browser/devtools/responsive-horizontal-resizer.png (../shared/devtools/images/responsive-horizontal-resizer.png)
         skin/classic/aero/browser/devtools/responsive-background.png (../shared/devtools/images/responsive-background.png)
         skin/classic/aero/browser/devtools/toggle-tools.png          (../shared/devtools/images/toggle-tools.png)
         skin/classic/aero/browser/devtools/dock-bottom@2x.png        (../shared/devtools/images/dock-bottom@2x.png)
         skin/classic/aero/browser/devtools/dock-side@2x.png          (../shared/devtools/images/dock-side@2x.png)
         skin/classic/aero/browser/devtools/floating-scrollbars.css   (devtools/floating-scrollbars.css)
         skin/classic/aero/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
         skin/classic/aero/browser/devtools/inspector.css             (devtools/inspector.css)
-        skin/classic/aero/browser/devtools/profiler-stopwatch.png    (../shared/devtools/images/profiler-stopwatch.png)
+        skin/classic/aero/browser/devtools/profiler-stopwatch.svg    (../shared/devtools/images/profiler-stopwatch.svg)
+        skin/classic/aero/browser/devtools/profiler-stopwatch-checked.svg      (../shared/devtools/images/profiler-stopwatch-checked.svg)
         skin/classic/aero/browser/devtools/tool-options.svg          (../shared/devtools/images/tool-options.svg)
         skin/classic/aero/browser/devtools/tool-webconsole.svg       (../shared/devtools/images/tool-webconsole.svg)
         skin/classic/aero/browser/devtools/tool-debugger.svg         (../shared/devtools/images/tool-debugger.svg)
         skin/classic/aero/browser/devtools/tool-debugger-paused.svg  (../shared/devtools/images/tool-debugger-paused.svg)
         skin/classic/aero/browser/devtools/tool-inspector.svg        (../shared/devtools/images/tool-inspector.svg)
         skin/classic/aero/browser/devtools/tool-styleeditor.svg      (../shared/devtools/images/tool-styleeditor.svg)
         skin/classic/aero/browser/devtools/tool-profiler.svg         (../shared/devtools/images/tool-profiler.svg)
         skin/classic/aero/browser/devtools/tool-network.svg          (../shared/devtools/images/tool-network.svg)
--- a/caps/idl/nsIScriptSecurityManager.idl
+++ b/caps/idl/nsIScriptSecurityManager.idl
@@ -5,18 +5,19 @@
 
 #include "nsISupports.idl"
 #include "nsIPrincipal.idl"
 #include "nsIXPCSecurityManager.idl"
 interface nsIURI;
 interface nsIChannel;
 interface nsIDocShell;
 interface nsIDomainPolicy;
+interface nsILoadContext;
 
-[scriptable, uuid(2565769a-eaec-47a1-a076-605f5294d286)]
+[scriptable, uuid(9875f4b2-f9cd-41d1-a461-fe14956823ac)]
 interface nsIScriptSecurityManager : nsIXPCSecurityManager
 {
     /**
      * Check that the script currently running in context "cx" can load "uri".
      *
      * Will return error code NS_ERROR_DOM_BAD_URI if the load request
      * should be denied.
      *
@@ -118,16 +119,24 @@ interface nsIScriptSecurityManager : nsI
      * @param inMozBrowser is true if the principal has to be considered as
      * inside a mozbrowser frame.
      */
     nsIPrincipal getAppCodebasePrincipal(in nsIURI uri,
                                          in unsigned long appId,
                                          in boolean inMozBrowser);
 
     /**
+     * Returns a principal that has the appId and inMozBrowser of the load
+     * context.
+     * @param loadContext to get appId/inMozBrowser from.
+     */
+    nsIPrincipal getLoadContextCodebasePrincipal(in nsIURI uri,
+                                                 in nsILoadContext loadContext);
+
+    /**
      * Returns a principal that has the appId and inMozBrowser of the docshell
      * inside a mozbrowser frame.
      * @param docShell to get appId/inMozBrowser from.
      */
     nsIPrincipal getDocShellCodebasePrincipal(in nsIURI uri,
                                               in nsIDocShell docShell);
 
     /**
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -6,16 +6,17 @@
 
 #include "nsScriptSecurityManager.h"
 
 #include "mozilla/ArrayUtils.h"
 
 #include "js/OldDebugAPI.h"
 #include "xpcprivate.h"
 #include "XPCWrapper.h"
+#include "nsILoadContext.h"
 #include "nsIServiceManager.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsIURL.h"
 #include "nsINestedURI.h"
 #include "nspr.h"
 #include "nsJSPrincipals.h"
 #include "nsSystemPrincipal.h"
@@ -290,21 +291,22 @@ nsScriptSecurityManager::GetChannelPrinc
     }
 
     // OK, get the principal from the URI.  Make sure this does the same thing
     // as nsDocument::Reset and XULDocument::StartDocumentLoad.
     nsCOMPtr<nsIURI> uri;
     nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsIDocShell> docShell;
-    NS_QueryNotificationCallbacks(aChannel, docShell);
 
-    if (docShell) {
-        return GetDocShellCodebasePrincipal(uri, docShell, aPrincipal);
+    nsCOMPtr<nsILoadContext> loadContext;
+    NS_QueryNotificationCallbacks(aChannel, loadContext);
+
+    if (loadContext) {
+        return GetLoadContextCodebasePrincipal(uri, loadContext, aPrincipal);
     }
 
     return GetCodebasePrincipalInternal(uri, UNKNOWN_APP_ID,
         /* isInBrowserElement */ false, aPrincipal);
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::IsSystemPrincipal(nsIPrincipal* aPrincipal,
@@ -965,16 +967,32 @@ nsScriptSecurityManager::GetAppCodebaseP
 {
   NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
                  NS_ERROR_INVALID_ARG);
 
   return GetCodebasePrincipalInternal(aURI, aAppId, aInMozBrowser, aPrincipal);
 }
 
 NS_IMETHODIMP
+nsScriptSecurityManager::
+  GetLoadContextCodebasePrincipal(nsIURI* aURI,
+                                  nsILoadContext* aLoadContext,
+                                  nsIPrincipal** aPrincipal)
+{
+  uint32_t appId;
+  aLoadContext->GetAppId(&appId);
+  bool isInBrowserElement;
+  aLoadContext->GetIsInBrowserElement(&isInBrowserElement);
+  return GetCodebasePrincipalInternal(aURI,
+                                      appId,
+                                      isInBrowserElement,
+                                      aPrincipal);
+}
+
+NS_IMETHODIMP
 nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI,
                                                       nsIDocShell* aDocShell,
                                                       nsIPrincipal** aPrincipal)
 {
   return GetCodebasePrincipalInternal(aURI,
                                       aDocShell->GetAppId(),
                                       aDocShell->GetIsInBrowserElement(),
                                       aPrincipal);
--- a/configure.in
+++ b/configure.in
@@ -7518,25 +7518,16 @@ MOZ_ARG_ENABLE_BOOL(b2g-camera,
     MOZ_B2G_CAMERA=1,
     MOZ_B2G_CAMERA= )
 if test -n "$MOZ_B2G_CAMERA"; then
    AC_DEFINE(MOZ_B2G_CAMERA)
 fi
 AC_SUBST(MOZ_B2G_CAMERA)
 
 dnl ========================================================
-dnl = Enable Support B2G-specific changes to the NSS
-dnl = certificate trust database.
-dnl ========================================================
-if test -n "$MOZ_B2G_CERTDATA"; then
-    AC_DEFINE(MOZ_B2G_CERTDATA)
-fi
-AC_SUBST(MOZ_B2G_CERTDATA)
-
-dnl ========================================================
 dnl = Enable Support for Payment API
 dnl ========================================================
 if test -n "$MOZ_PAY"; then
     AC_DEFINE(MOZ_PAY)
 fi
 AC_SUBST(MOZ_PAY)
 
 dnl ========================================================
--- a/content/base/public/nsContentCID.h
+++ b/content/base/public/nsContentCID.h
@@ -170,9 +170,13 @@
 // {f96f5ec9-755b-447e-b1f3-717d1a84bb41}
 #define NS_PLUGINDOCUMENT_CID \
 { 0xf96f5ec9, 0x755b, 0x447e, { 0xb1, 0xf3, 0x71, 0x7d, 0x1a, 0x84, 0xbb, 0x41 } }
 
 // {08c6cc8b-cfb0-421d-b1f7-683ff2989681}
 #define THIRDPARTYUTIL_CID \
  {0x08c6cc8b, 0xcfb0, 0x421d, {0xb1, 0xf7, 0x68, 0x3f, 0xf2, 0x98, 0x96, 0x81}}
 
+// {7B121F7E-EBE4-43AB-9410-DC9087A1DBA6}
+#define GECKO_MEDIA_PLUGIN_SERVICE_CID \
+ {0x7B121F7E, 0xEBE4, 0x43AB, {0x94, 0x10, 0xDC, 0x90, 0x87, 0xA1, 0xDB, 0xA6}}
+
 #endif /* nsContentCID_h__ */
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -23,16 +23,17 @@
 #endif
 #include "prlog.h"
 #include "plstr.h"
 #include "prprf.h"
 
 #include "mozilla/Telemetry.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
 #include "nsUnicharUtils.h"
 #include "nsContentList.h"
 #include "nsIObserver.h"
 #include "nsIBaseWindow.h"
 #include "mozilla/css/Loader.h"
 #include "mozilla/css/ImageLoader.h"
 #include "nsDocShell.h"
 #include "nsIDocShellTreeItem.h"
@@ -2332,31 +2333,31 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
 
   // Now get our new principal
   if (aPrincipal) {
     SetPrincipal(aPrincipal);
   } else {
     nsIScriptSecurityManager *securityManager =
       nsContentUtils::GetSecurityManager();
     if (securityManager) {
-      nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
-
-      if (!docShell && aLoadGroup) {
+      nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
+
+      if (!loadContext && aLoadGroup) {
         nsCOMPtr<nsIInterfaceRequestor> cbs;
         aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
-        docShell = do_GetInterface(cbs);
+        loadContext = do_GetInterface(cbs);
       }
 
-      MOZ_ASSERT(docShell,
-                 "must be in a docshell or pass in an explicit principal");
+      MOZ_ASSERT(loadContext,
+                 "must have a load context or pass in an explicit principal");
 
       nsCOMPtr<nsIPrincipal> principal;
       nsresult rv = securityManager->
-        GetDocShellCodebasePrincipal(mDocumentURI, docShell,
-                                     getter_AddRefs(principal));
+        GetLoadContextCodebasePrincipal(mDocumentURI, loadContext,
+                                        getter_AddRefs(principal));
       if (NS_SUCCEEDED(rv)) {
         SetPrincipal(principal);
       }
     }
   }
 
   // Refresh the principal on the compartment.
   nsPIDOMWindow* win = GetInnerWindow();
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -49,16 +49,17 @@
 #include "nsCrossSiteListenerProxy.h"
 #include "nsSandboxFlags.h"
 #include "nsContentTypeParser.h"
 #include "nsINetworkSeer.h"
 #include "mozilla/dom/EncodingUtils.h"
 
 #include "mozilla/CORSMode.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/unused.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gCspPRLog;
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -81,19 +82,17 @@ public:
       mJSVersion(aVersion),
       mLineNo(1),
       mCORSMode(aCORSMode)
   {
   }
 
   ~nsScriptLoadRequest()
   {
-    if (mScriptTextBuf) {
-      js_free(mScriptTextBuf);
-    }
+    js_free(mScriptTextBuf);
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   void FireScriptAvailable(nsresult aResult)
   {
     mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
   }
@@ -428,17 +427,17 @@ ParseTypeAttribute(const nsAString& aTyp
     *aVersion = nsContentUtils::ParseJavascriptVersion(versionName);
   } else if (rv != NS_ERROR_INVALID_ARG) {
     return false;
   }
 
   return true;
 }
 
-bool
+static bool
 CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument)
 {
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
   NS_ENSURE_SUCCESS(rv, false);
 
   if (!csp) {
     // no CSP --> allow
@@ -535,16 +534,53 @@ CSPAllowsInlineScript(nsIScriptElement *
   if (!allowInlineScript) {
     NS_ASSERTION(!violations.IsEmpty(),
         "CSP blocked inline script but is not reporting a violation");
    return false;
   }
   return true;
 }
 
+static void
+AccumulateJavaScriptVersionTelemetry(nsIScriptElement* aElement,
+                                     JSVersion aVersion)
+{
+  uint32_t minorVersion;
+  switch (aVersion) {
+    case JSVERSION_DEFAULT: minorVersion = 5; break;
+    case JSVERSION_1_6:     minorVersion = 6; break;
+    case JSVERSION_1_7:     minorVersion = 7; break;
+    case JSVERSION_1_8:     minorVersion = 8; break;
+    default:                MOZ_ASSERT_UNREACHABLE("Unexpected JSVersion");
+    case JSVERSION_UNKNOWN: minorVersion = 0; break;
+  }
+
+  // Only report SpiderMonkey's nonstandard JS versions: 1.6, 1.7, and 1.8.
+  if (minorVersion < 6) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
+  if (!scriptURI) {
+    return;
+  }
+
+  // We only care about web content, not chrome or add-on JS versions.
+  bool chrome = false;
+  scriptURI->SchemeIs("chrome", &chrome);
+  if (!chrome) {
+    scriptURI->SchemeIs("resource", &chrome);
+  }
+  if (chrome) {
+    return;
+  }
+
+  Telemetry::Accumulate(Telemetry::JS_MINOR_VERSION, minorVersion);
+}
+
 bool
 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
 {
   // We need a document to evaluate scripts.
   NS_ENSURE_TRUE(mDocument, false);
 
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
@@ -563,16 +599,17 @@ nsScriptLoader::ProcessScriptElement(nsI
   JSVersion version = JSVERSION_DEFAULT;
 
   // Check the type attribute to determine language and version.
   // If type exists, it trumps the deprecated 'language='
   nsAutoString type;
   aElement->GetScriptType(type);
   if (!type.IsEmpty()) {
     NS_ENSURE_TRUE(ParseTypeAttribute(type, &version), false);
+    AccumulateJavaScriptVersionTelemetry(aElement, version);
   } else {
     // no 'type=' element
     // "language" is a deprecated attribute of HTML, so we check it only for
     // HTML script elements.
     if (scriptContent->IsHTML()) {
       nsAutoString language;
       scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::language, language);
       if (!language.IsEmpty()) {
--- a/content/base/test/chrome/cpows_child.js
+++ b/content/base/test/chrome/cpows_child.js
@@ -1,21 +1,33 @@
 dump('loaded child cpow test\n');
 
 content.document.title = "Hello, Kitty";
 
+var done_count = 0;
+var is_remote;
+
 (function start() {
+    [is_remote] = sendSyncMessage("cpows:is_remote");
+    parent_test();
+    dom_test();
+    xray_test();
     sync_test();
     async_test();
     rpc_test();
     nested_sync_test();
     // The sync-ness of this call is important, because otherwise
     // we tear down the child's document while we are
     // still in the async test in the parent.
-    sendSyncMessage("cpows:done", {});
+    // This test races with itself to be the final test.
+    lifetime_test(function() {
+      done_count++;
+      if (done_count == 2)
+        sendSyncMessage("cpows:done", {});
+    });
   }
 )();
 
 function ok(condition, message) {
   dump('condition: ' + condition  + ', ' + message + '\n');
   if (!condition) {
     sendAsyncMessage("cpows:fail", { message: message });
     throw 'failed check: ' + message;
@@ -51,16 +63,54 @@ function make_object()
          };
 }
 
 function make_json()
 {
   return { check: "ok" };
 }
 
+function parent_test()
+{
+  function f(check_func) {
+    let result = check_func(10);
+    ok(result == 20, "calling function in parent worked");
+    return result;
+  }
+
+  addMessageListener("cpows:from_parent", (msg) => {
+    let obj = msg.objects.obj;
+    ok(obj.a == 1, "correct value from parent");
+    done_count++;
+    if (done_count == 2)
+      sendSyncMessage("cpows:done", {});
+  });
+  sendSyncMessage("cpows:parent_test", {}, {func: f});
+}
+
+function dom_test()
+{
+  let element = content.document.createElement("div");
+  element.id = "it_works";
+  content.document.body.appendChild(element);
+
+  sendAsyncMessage("cpows:dom_test", {}, {element: element});
+  Components.utils.schedulePreciseGC(function() {
+    sendSyncMessage("cpows:dom_test_after_gc");
+  });
+}
+
+function xray_test()
+{
+  let element = content.document.createElement("div");
+  element.wrappedJSObject.foo = "hello";
+
+  sendSyncMessage("cpows:xray_test", {}, {element: element});
+}
+
 function sync_test()
 {
   dump('beginning cpow sync test\n');
   sync_obj = make_object();
   sendSyncMessage("cpows:sync",
     make_json(),
     make_object());
 }
@@ -101,8 +151,33 @@ function nested_sync_test()
     if (!ok(caught, "should not allow nested sync"))
       return "fail";
     return "ok";
   }
   sendSyncMessage("cpows:nested_sync",
     make_json(),
     rpc_obj);
 }
+
+function lifetime_test(finish)
+{
+  if (!is_remote) {
+    // Only run this test when running out-of-process. Otherwise it
+    // will fail, since local CPOWs don't follow the same ownership
+    // rules.
+    finish();
+    return;
+  }
+
+  dump("beginning lifetime test\n");
+  var obj = {"will_die": {"f": 1}};
+  let [result] = sendSyncMessage("cpows:lifetime_test_1", {}, {obj: obj});
+  ok(result == 10, "got sync result");
+  ok(obj.wont_die.f == 2, "got reverse CPOW");
+  obj.will_die = null;
+  Components.utils.schedulePreciseGC(function() {
+    addMessageListener("cpows:lifetime_test_3", (msg) => {
+      ok(obj.wont_die.f == 2, "reverse CPOW still works");
+      finish();
+    });
+    sendSyncMessage("cpows:lifetime_test_2");
+  });
+}
--- a/content/base/test/chrome/cpows_parent.xul
+++ b/content/base/test/chrome/cpows_parent.xul
@@ -8,21 +8,30 @@
 
   <!-- test results are displayed in the html:body -->
   <label value="CPOWs"/>
 
   <script type="application/javascript"><![CDATA[
     var test_state = "remote";
     var test_node = null;
     var reentered = false;
+    var savedMM = null;
+
+    function info(message) {
+      return opener.wrappedJSObject.info(message);
+    }
 
     function ok(condition, message) {
       return opener.wrappedJSObject.ok(condition, message);
     }
 
+    function is(v1, v2, message) {
+      return opener.wrappedJSObject.is(v1, v2, message);
+    }
+
     // Make sure that an error in this file actually causes the test to fail.
     window.onerror = function (msg, url, line) {
       ok(false, "Error while executing: \n" + msg + "\n" + url + ":" + line);
     };
 
     function testCpowMessage(message) {
       ok(message.json.check == "ok", "correct json");
 
@@ -140,32 +149,97 @@
         test_node.parentNode.removeChild(test_node);
         run_tests("inprocess");
         return;
       }
 
       finish();
     }
 
+    function recvParentTest(message) {
+      let func = message.objects.func;
+      let result = func(n => 2*n);
+      ok(result == 20, "result == 20");
+      let obj = {a:1};
+      savedMM.sendAsyncMessage("cpows:from_parent", {}, {obj: obj});
+    }
+
+    let savedElement = null;
+    function recvDomTest(message) {
+      savedElement = message.objects.element;
+    }
+
+    function recvDomTestAfterGC(message) {
+      let id;
+      try {
+        id = savedElement.id;
+      } catch (e) {
+        ok(false, "Got exception using DOM element");
+      }
+      is(id, "it_works", "DOM element has expected ID");
+    }
+
+    function recvXrayTest(message) {
+      let element = message.objects.element;
+      is(element.foo, undefined, "DOM element does not expose content properties");
+    }
+
+    let savedWilldieObj;
+    let wontDie = {f:2};
+    function recvLifetimeTest1(message) {
+      let obj = message.objects.obj;
+      savedWilldieObj = obj.will_die;
+      ok(savedWilldieObj.f == 1, "limited-lifetime CPOW works at first");
+      obj.wont_die = wontDie;
+      obj = null;
+      return 10;
+    }
+    function recvLifetimeTest2(message) {
+      let threw = false;
+      try {
+        savedWilldieObj.f;
+      } catch (e) {
+        threw = true;
+      }
+      ok(threw, "limited-lifetime CPOW stopped working");
+      wontDie = null;
+      Components.utils.schedulePreciseGC(function() {
+        savedMM.sendAsyncMessage("cpows:lifetime_test_3");
+      });
+    }
+
     function run_tests(type) {
+      info("Running tests: " + type);
       var node = document.getElementById('cpowbrowser_' + type);
 
       test_state = type;
       test_node = node;
 
+      function recvIsRemote(message) {
+        return type == "remote";
+      }
+
       var mm = node.messageManager;
+      savedMM = mm;
+      mm.addMessageListener("cpows:is_remote", recvIsRemote);
       mm.addMessageListener("cpows:async", recvAsyncMessage);
       mm.addMessageListener("cpows:sync", recvSyncMessage);
       mm.addMessageListener("cpows:rpc", recvRpcMessage);
       mm.addMessageListener("cpows:reenter", recvReenterMessage);
       mm.addMessageListener("cpows:reenter", recvReenterMessage);
       mm.addMessageListener("cpows:nested_sync", recvNestedSyncMessage);
       mm.addMessageListener("cpows:reenter_sync", recvReenterSyncMessage);
       mm.addMessageListener("cpows:done", recvDoneMessage);
       mm.addMessageListener("cpows:fail", recvFailMessage);
+      mm.addMessageListener("cpows:parent_test", recvParentTest);
+      mm.addMessageListener("cpows:dom_test", recvDomTest);
+      mm.addMessageListener("cpows:dom_test_after_gc", recvDomTestAfterGC);
+      mm.addMessageListener("cpows:xray_test", recvXrayTest);
+      mm.addMessageListener("cpows:lifetime_test_1", recvLifetimeTest1);
+      mm.addMessageListener("cpows:lifetime_test_2", recvLifetimeTest2);
       mm.loadFrameScript("chrome://mochitests/content/chrome/content/base/test/chrome/cpows_child.js", true);
     }
 
     function start() {
       run_tests('remote');
     }
 
     function finish() {
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -5191,16 +5191,18 @@ HTMLInputElement::SetRangeText(const nsA
 
       if ((uint32_t)aSelectionEnd > aEnd) {
         aSelectionEnd += delta;
       } else if ((uint32_t)aSelectionEnd > aStart) {
         aSelectionEnd = newEnd;
       }
     }
     break;
+    default:
+      MOZ_CRASH("Unknown mode!");
   }
 
   Optional<nsAString> direction;
   SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
 }
 
 int32_t
 HTMLInputElement::GetSelectionStart(ErrorResult& aRv)
--- a/content/html/content/src/HTMLTextAreaElement.cpp
+++ b/content/html/content/src/HTMLTextAreaElement.cpp
@@ -980,16 +980,18 @@ HTMLTextAreaElement::SetRangeText(const 
 
       if ((uint32_t)aSelectionEnd > aEnd) {
         aSelectionEnd += delta;
       } else if ((uint32_t)aSelectionEnd > aStart) {
         aSelectionEnd = newEnd;
       }
     }
     break;
+    default:
+      MOZ_CRASH("Unknown mode!");
   }
 
   Optional<nsAString> direction;
   SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
 }
 
 nsresult
 HTMLTextAreaElement::Reset()
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -72,16 +72,17 @@ MediaStreamGraphImpl::GetDesiredBufferEn
 
 void
 MediaStreamGraphImpl::FinishStream(MediaStream* aStream)
 {
   if (aStream->mFinished)
     return;
   STREAM_LOG(PR_LOG_DEBUG, ("MediaStream %p will finish", aStream));
   aStream->mFinished = true;
+  aStream->mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX);
   // Force at least one more iteration of the control loop, since we rely
   // on UpdateCurrentTime to notify our listeners once the stream end
   // has been reached.
   EnsureNextIteration();
 
   SetStreamOrderDirty();
 }
 
@@ -201,17 +202,19 @@ MediaStreamGraphImpl::ExtractPendingInpu
                                     int64_t(dest->GetDuration() + data->mData->GetDuration())));
         dest->AppendFrom(data->mData);
       }
       if (data->mCommands & SourceMediaStream::TRACK_END) {
         aStream->mBuffer.FindTrack(data->mID)->SetEnded();
         aStream->mUpdateTracks.RemoveElementAt(i);
       }
     }
-    aStream->mBuffer.AdvanceKnownTracksTime(aStream->mUpdateKnownTracksTime);
+    if (!aStream->mFinished) {
+      aStream->mBuffer.AdvanceKnownTracksTime(aStream->mUpdateKnownTracksTime);
+    }
   }
   if (aStream->mBuffer.GetEnd() > 0) {
     aStream->mHasCurrentData = true;
   }
   if (finished) {
     FinishStream(aStream);
   }
 }
@@ -2404,16 +2407,17 @@ SourceMediaStream::EndTrack(TrackID aID)
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 void
 SourceMediaStream::AdvanceKnownTracksTime(StreamTime aKnownTime)
 {
   MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(aKnownTime >= mUpdateKnownTracksTime);
   mUpdateKnownTracksTime = aKnownTime;
   if (!mDestroyed) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 void
 SourceMediaStream::FinishWithLockHeld()
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -732,18 +732,17 @@ public:
   /**
    * Indicate that no tracks will be added starting before time aKnownTime.
    * aKnownTime must be >= its value at the last call to AdvanceKnownTracksTime.
    */
   void AdvanceKnownTracksTime(StreamTime aKnownTime);
   /**
    * Indicate that this stream should enter the "finished" state. All tracks
    * must have been ended via EndTrack. The finish time of the stream is
-   * when all tracks have ended and when latest time sent to
-   * AdvanceKnownTracksTime() has been reached.
+   * when all tracks have ended.
    */
   void FinishWithLockHeld();
   void Finish()
     {
       MutexAutoLock lock(mMutex);
       FinishWithLockHeld();
     }
 
--- a/content/media/StreamBuffer.cpp
+++ b/content/media/StreamBuffer.cpp
@@ -44,29 +44,28 @@ StreamBuffer::GetEnd() const
     }
   }
   return t;
 }
 
 StreamTime
 StreamBuffer::GetAllTracksEnd() const
 {
+  if (mTracksKnownTime < STREAM_TIME_MAX) {
+    // A track might be added.
+    return STREAM_TIME_MAX;
+  }
   StreamTime t = 0;
   for (uint32_t i = 0; i < mTracks.Length(); ++i) {
     Track* track = mTracks[i];
     if (!track->IsEnded()) {
       return STREAM_TIME_MAX;
     }
     t = std::max(t, track->GetEndTimeRoundDown());
   }
-  if (t > mTracksKnownTime) {
-    // It can't be later then mTracksKnownTime, since a track might be added
-    // after that.
-    return STREAM_TIME_MAX;
-  }
   return t;
 }
 
 StreamBuffer::Track*
 StreamBuffer::FindTrack(TrackID aID)
 {
   if (aID == TRACK_NONE)
     return nullptr;
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -101,18 +101,19 @@ public:
         mTrackMap.RemoveElementAt(i);
       }
     }
     if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
       // All streams have finished and won't add any more tracks, and
       // all our tracks have actually finished and been removed from our map,
       // so we're finished now.
       FinishOnGraphThread();
+    } else {
+      mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
     }
-    mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
     if (allHaveCurrentData) {
       // We can make progress if we're not blocked
       mHasCurrentData = true;
     }
   }
 
   // Consumers may specify a filtering callback to apply to every input track.
   // Returns true to allow the track to act as an input; false to reject it entirely.
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPChild.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPChild.h"
+#include "GMPVideoDecoderChild.h"
+#include "GMPVideoEncoderChild.h"
+#include "GMPVideoHost.h"
+#include "nsIFile.h"
+#include "nsXULAppAPI.h"
+#include <stdlib.h>
+#include "gmp-video-decode.h"
+#include "gmp-video-encode.h"
+#include "GMPPlatform.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPChild::GMPChild()
+  : mLib(nullptr)
+  , mGetAPIFunc(nullptr)
+  , mGMPMessageLoop(MessageLoop::current())
+{
+}
+
+GMPChild::~GMPChild()
+{
+}
+
+bool
+GMPChild::Init(const std::string& aPluginPath,
+               base::ProcessHandle aParentProcessHandle,
+               MessageLoop* aIOLoop,
+               IPC::Channel* aChannel)
+{
+  return LoadPluginLibrary(aPluginPath) &&
+         Open(aChannel, aParentProcessHandle, aIOLoop);
+}
+
+bool
+GMPChild::LoadPluginLibrary(const std::string& aPluginPath)
+{
+  nsDependentCString pluginPath(aPluginPath.c_str());
+
+  nsCOMPtr<nsIFile> libFile;
+  nsresult rv = NS_NewNativeLocalFile(pluginPath, true, getter_AddRefs(libFile));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  nsAutoString leafName;
+  if (NS_FAILED(libFile->GetLeafName(leafName))) {
+    return false;
+  }
+  nsAutoString baseName(Substring(leafName, 4, leafName.Length() - 1));
+
+#if defined(XP_MACOSX)
+  nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".dylib");
+#elif defined(OS_POSIX)
+  nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".so");
+#elif defined(XP_WIN)
+  nsAutoString binaryName =                            baseName + NS_LITERAL_STRING(".dll");
+#else
+#error not defined
+#endif
+  libFile->AppendRelativePath(binaryName);
+
+  nsAutoCString nativePath;
+  libFile->GetNativePath(nativePath);
+  mLib = PR_LoadLibrary(nativePath.get());
+  if (!mLib) {
+    return false;
+  }
+
+  GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
+  if (!initFunc) {
+    return false;
+  }
+
+  auto platformAPI = new GMPPlatformAPI();
+  InitPlatformAPI(*platformAPI);
+
+  if (initFunc(platformAPI) != GMPNoErr) {
+    return false;
+  }
+
+  mGetAPIFunc = reinterpret_cast<GMPGetAPIFunc>(PR_FindFunctionSymbol(mLib, "GMPGetAPI"));
+  if (!mGetAPIFunc) {
+    return false;
+  }
+
+  return true;
+}
+
+MessageLoop*
+GMPChild::GMPMessageLoop()
+{
+  return mGMPMessageLoop;
+}
+
+void
+GMPChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  if (mLib) {
+    GMPShutdownFunc shutdownFunc = reinterpret_cast<GMPShutdownFunc>(PR_FindFunctionSymbol(mLib, "GMPShutdown"));
+    if (shutdownFunc) {
+      shutdownFunc();
+    }
+  }
+
+  if (AbnormalShutdown == aWhy) {
+    NS_WARNING("Abnormal shutdown of GMP process!");
+    _exit(0);
+  }
+
+  XRE_ShutdownChildProcess();
+}
+
+void
+GMPChild::ProcessingError(Result aWhat)
+{
+  switch (aWhat) {
+    case MsgDropped:
+      _exit(0); // Don't trigger a crash report.
+    case MsgNotKnown:
+      MOZ_CRASH("aborting because of MsgNotKnown");
+    case MsgNotAllowed:
+      MOZ_CRASH("aborting because of MsgNotAllowed");
+    case MsgPayloadError:
+      MOZ_CRASH("aborting because of MsgPayloadError");
+    case MsgProcessingError:
+      MOZ_CRASH("aborting because of MsgProcessingError");
+    case MsgRouteError:
+      MOZ_CRASH("aborting because of MsgRouteError");
+    case MsgValueError:
+      MOZ_CRASH("aborting because of MsgValueError");
+    default:
+      MOZ_CRASH("not reached");
+  }
+}
+
+PGMPVideoDecoderChild*
+GMPChild::AllocPGMPVideoDecoderChild()
+{
+  return new GMPVideoDecoderChild(this);
+}
+
+bool
+GMPChild::DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor)
+{
+  delete aActor;
+  return true;
+}
+
+PGMPVideoEncoderChild*
+GMPChild::AllocPGMPVideoEncoderChild()
+{
+  return new GMPVideoEncoderChild(this);
+}
+
+bool
+GMPChild::DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor)
+{
+  delete aActor;
+  return true;
+}
+
+bool
+GMPChild::RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor)
+{
+  auto vdc = static_cast<GMPVideoDecoderChild*>(aActor);
+
+  void* vd = nullptr;
+  GMPErr err = mGetAPIFunc("decode-video", &vdc->Host(), &vd);
+  if (err != GMPNoErr || !vd) {
+    return false;
+  }
+
+  vdc->Init(static_cast<GMPVideoDecoder*>(vd));
+
+  return true;
+}
+
+bool
+GMPChild::RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor)
+{
+  auto vec = static_cast<GMPVideoEncoderChild*>(aActor);
+
+  void* ve = nullptr;
+  GMPErr err = mGetAPIFunc("encode-video", &vec->Host(), &ve);
+  if (err != GMPNoErr || !ve) {
+    return false;
+  }
+
+  vec->Init(static_cast<GMPVideoEncoder*>(ve));
+
+  return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPChild.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPChild_h_
+#define GMPChild_h_
+
+#include "mozilla/gmp/PGMPChild.h"
+#include "gmp-entrypoints.h"
+#include "prlink.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild : public PGMPChild
+{
+public:
+  GMPChild();
+  virtual ~GMPChild();
+
+  bool Init(const std::string& aPluginPath,
+            base::ProcessHandle aParentProcessHandle,
+            MessageLoop* aIOLoop,
+            IPC::Channel* aChannel);
+  bool LoadPluginLibrary(const std::string& aPluginPath);
+  MessageLoop* GMPMessageLoop();
+
+private:
+  virtual PGMPVideoDecoderChild* AllocPGMPVideoDecoderChild() MOZ_OVERRIDE;
+  virtual bool DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor) MOZ_OVERRIDE;
+  virtual PGMPVideoEncoderChild* AllocPGMPVideoEncoderChild() MOZ_OVERRIDE;
+  virtual bool DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor) MOZ_OVERRIDE;
+  virtual bool RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor) MOZ_OVERRIDE;
+  virtual bool RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor) MOZ_OVERRIDE;
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+  virtual void ProcessingError(Result aWhat) MOZ_OVERRIDE;
+
+  PRLibrary* mLib;
+  GMPGetAPIFunc mGetAPIFunc;
+  MessageLoop* mGMPMessageLoop;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPChild_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPMessageUtils.h
@@ -0,0 +1,329 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPMessageUtils_h_
+#define GMPMessageUtils_h_
+
+#include "gmp-video-codec.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<GMPVideoCodecComplexity>
+: public ContiguousEnumSerializer<GMPVideoCodecComplexity,
+                                  kGMPComplexityNormal,
+                                  kGMPComplexityInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVP8ResilienceMode>
+: public ContiguousEnumSerializer<GMPVP8ResilienceMode,
+                                  kResilienceOff,
+                                  kResilienceInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecType>
+: public ContiguousEnumSerializer<GMPVideoCodecType,
+                                  kGMPVideoCodecVP8,
+                                  kGMPVideoCodecInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecMode>
+: public ContiguousEnumSerializer<GMPVideoCodecMode,
+                                  kGMPRealtimeVideo,
+                                  kGMPCodecModeInvalid>
+{};
+
+template <>
+struct ParamTraits<GMPVideoCodecVP8>
+{
+  typedef GMPVideoCodecVP8 paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mPictureLossIndicationOn);
+    WriteParam(aMsg, aParam.mFeedbackModeOn);
+    WriteParam(aMsg, aParam.mComplexity);
+    WriteParam(aMsg, aParam.mResilience);
+    WriteParam(aMsg, aParam.mNumberOfTemporalLayers);
+    WriteParam(aMsg, aParam.mDenoisingOn);
+    WriteParam(aMsg, aParam.mErrorConcealmentOn);
+    WriteParam(aMsg, aParam.mAutomaticResizeOn);
+    WriteParam(aMsg, aParam.mFrameDroppingOn);
+    WriteParam(aMsg, aParam.mKeyFrameInterval);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (ReadParam(aMsg, aIter, &(aResult->mPictureLossIndicationOn)) &&
+        ReadParam(aMsg, aIter, &(aResult->mFeedbackModeOn)) &&
+        ReadParam(aMsg, aIter, &(aResult->mComplexity)) &&
+        ReadParam(aMsg, aIter, &(aResult->mResilience)) &&
+        ReadParam(aMsg, aIter, &(aResult->mNumberOfTemporalLayers)) &&
+        ReadParam(aMsg, aIter, &(aResult->mDenoisingOn)) &&
+        ReadParam(aMsg, aIter, &(aResult->mErrorConcealmentOn)) &&
+        ReadParam(aMsg, aIter, &(aResult->mAutomaticResizeOn)) &&
+        ReadParam(aMsg, aIter, &(aResult->mFrameDroppingOn)) &&
+        ReadParam(aMsg, aIter, &(aResult->mKeyFrameInterval))) {
+      return true;
+    }
+
+    return false;
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog)
+  {
+    aLog->append(StringPrintf(L"[%d, %d, %d, %d, %u, %d, %d, %d, %d, %d]",
+                              aParam.mPictureLossIndicationOn,
+                              aParam.mFeedbackModeOn,
+                              aParam.mComplexity,
+                              aParam.mResilience,
+                              aParam.mNumberOfTemporalLayers,
+                              aParam.mDenoisingOn,
+                              aParam.mErrorConcealmentOn,
+                              aParam.mAutomaticResizeOn,
+                              aParam.mFrameDroppingOn,
+                              aParam.mKeyFrameInterval));
+  }
+};
+
+template <>
+struct ParamTraits<GMPSimulcastStream>
+{
+  typedef GMPSimulcastStream paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mWidth);
+    WriteParam(aMsg, aParam.mHeight);
+    WriteParam(aMsg, aParam.mNumberOfTemporalLayers);
+    WriteParam(aMsg, aParam.mMaxBitrate);
+    WriteParam(aMsg, aParam.mTargetBitrate);
+    WriteParam(aMsg, aParam.mMinBitrate);
+    WriteParam(aMsg, aParam.mQPMax);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (ReadParam(aMsg, aIter, &(aResult->mWidth)) &&
+        ReadParam(aMsg, aIter, &(aResult->mHeight)) &&
+        ReadParam(aMsg, aIter, &(aResult->mNumberOfTemporalLayers)) &&
+        ReadParam(aMsg, aIter, &(aResult->mMaxBitrate)) &&
+        ReadParam(aMsg, aIter, &(aResult->mTargetBitrate)) &&
+        ReadParam(aMsg, aIter, &(aResult->mMinBitrate)) &&
+        ReadParam(aMsg, aIter, &(aResult->mQPMax))) {
+      return true;
+    }
+    return false;
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog)
+  {
+    aLog->append(StringPrintf(L"[%u, %u, %u, %u, %u, %u, %u]", aParam.mWidth, aParam.mHeight,
+                              aParam.mNumberOfTemporalLayers, aParam.mMaxBitrate,
+                              aParam.mTargetBitrate, aParam.mMinBitrate, aParam.mQPMax));
+  }
+};
+
+template <>
+struct ParamTraits<GMPVideoCodec>
+{
+  typedef GMPVideoCodec paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mCodecType);
+    WriteParam(aMsg, nsAutoCString(aParam.mPLName));
+    WriteParam(aMsg, aParam.mPLType);
+    WriteParam(aMsg, aParam.mWidth);
+    WriteParam(aMsg, aParam.mHeight);
+    WriteParam(aMsg, aParam.mStartBitrate);
+    WriteParam(aMsg, aParam.mMaxBitrate);
+    WriteParam(aMsg, aParam.mMinBitrate);
+    WriteParam(aMsg, aParam.mMaxFramerate);
+    if (aParam.mCodecType == kGMPVideoCodecVP8) {
+      WriteParam(aMsg, aParam.mCodecSpecific.mVP8);
+    } else {
+      MOZ_ASSERT(false, "Serializing unknown codec type!");
+    }
+    WriteParam(aMsg, aParam.mQPMax);
+    WriteParam(aMsg, aParam.mNumberOfSimulcastStreams);
+    for (uint32_t i = 0; i < aParam.mNumberOfSimulcastStreams; i++) {
+      WriteParam(aMsg, aParam.mSimulcastStream[i]);
+    }
+    WriteParam(aMsg, aParam.mMode);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mCodecType))) {
+      return false;
+    }
+
+    nsAutoCString plName;
+    if (!ReadParam(aMsg, aIter, &plName) ||
+        plName.Length() > kGMPPayloadNameSize - 1) {
+      return false;
+    }
+    memcpy(aResult->mPLName, plName.get(), plName.Length());
+    memset(aResult->mPLName + plName.Length(), 0, kGMPPayloadNameSize - plName.Length());
+
+    if (!ReadParam(aMsg, aIter, &(aResult->mPLType)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mWidth)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mHeight)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mStartBitrate)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mMaxBitrate)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mMinBitrate)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mMaxFramerate))) {
+      return false;
+    }
+
+    if (aResult->mCodecType == kGMPVideoCodecVP8) {
+      if (!ReadParam(aMsg, aIter, &(aResult->mCodecSpecific.mVP8))) {
+        return false;
+      }
+    } else {
+      MOZ_ASSERT(false, "De-serializing unknown codec type!");
+      return false;
+    }
+
+    if (!ReadParam(aMsg, aIter, &(aResult->mQPMax)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mNumberOfSimulcastStreams))) {
+      return false;
+    }
+
+    if (aResult->mNumberOfSimulcastStreams > kGMPMaxSimulcastStreams) {
+      return false;
+    }
+
+    for (uint32_t i = 0; i < aResult->mNumberOfSimulcastStreams; i++) {
+      if (!ReadParam(aMsg, aIter, &(aResult->mSimulcastStream[i]))) {
+        return false;
+      }
+    }
+
+    if (!ReadParam(aMsg, aIter, &(aResult->mMode))) {
+      return false;
+    }
+
+    return true;
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog)
+  {
+    const char* codecName = nullptr;
+    if (aParam.mCodecType == kGMPVideoCodecVP8) {
+      codecName = "VP8";
+    }
+    aLog->append(StringPrintf(L"[%s, %u, %u]",
+                              codecName,
+                              aParam.mWidth,
+                              aParam.mHeight));
+  }
+};
+
+template <>
+struct ParamTraits<GMPCodecSpecificInfoVP8>
+{
+  typedef GMPCodecSpecificInfoVP8 paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mHasReceivedSLI);
+    WriteParam(aMsg, aParam.mPictureIdSLI);
+    WriteParam(aMsg, aParam.mHasReceivedRPSI);
+    WriteParam(aMsg, aParam.mPictureIdRPSI);
+    WriteParam(aMsg, aParam.mPictureId);
+    WriteParam(aMsg, aParam.mNonReference);
+    WriteParam(aMsg, aParam.mSimulcastIdx);
+    WriteParam(aMsg, aParam.mTemporalIdx);
+    WriteParam(aMsg, aParam.mLayerSync);
+    WriteParam(aMsg, aParam.mTL0PicIdx);
+    WriteParam(aMsg, aParam.mKeyIdx);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (ReadParam(aMsg, aIter, &(aResult->mHasReceivedSLI)) &&
+        ReadParam(aMsg, aIter, &(aResult->mPictureIdSLI)) &&
+        ReadParam(aMsg, aIter, &(aResult->mHasReceivedRPSI)) &&
+        ReadParam(aMsg, aIter, &(aResult->mPictureIdRPSI)) &&
+        ReadParam(aMsg, aIter, &(aResult->mPictureId)) &&
+        ReadParam(aMsg, aIter, &(aResult->mNonReference)) &&
+        ReadParam(aMsg, aIter, &(aResult->mSimulcastIdx)) &&
+        ReadParam(aMsg, aIter, &(aResult->mTemporalIdx)) &&
+        ReadParam(aMsg, aIter, &(aResult->mLayerSync)) &&
+        ReadParam(aMsg, aIter, &(aResult->mTL0PicIdx)) &&
+        ReadParam(aMsg, aIter, &(aResult->mKeyIdx))) {
+      return true;
+    }
+    return false;
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog)
+  {
+    aLog->append(StringPrintf(L"[%d, %u, %d, %u, %d, %d, %u, %u, %d, %d, %d]",
+                              aParam.mHasReceivedSLI,
+                              aParam.mPictureIdSLI,
+                              aParam.mHasReceivedRPSI,
+                              aParam.mPictureIdRPSI,
+                              aParam.mPictureId,
+                              aParam.mNonReference,
+                              aParam.mSimulcastIdx,
+                              aParam.mTemporalIdx,
+                              aParam.mLayerSync,
+                              aParam.mTL0PicIdx,
+                              aParam.mKeyIdx));
+  }
+};
+
+template <>
+struct ParamTraits<GMPCodecSpecificInfo>
+{
+  typedef GMPCodecSpecificInfo paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mCodecType);
+    if (aParam.mCodecType == kGMPVideoCodecVP8) {
+      WriteParam(aMsg, aParam.mCodecSpecific.mVP8);
+    } else {
+      MOZ_ASSERT(false, "Serializing unknown codec type!");
+    }
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mCodecType))) {
+      return false;
+    }
+
+    if (aResult->mCodecType == kGMPVideoCodecVP8) {
+      if (!ReadParam(aMsg, aIter, &(aResult->mCodecSpecific.mVP8))) {
+        return false;
+      }
+    } else {
+      MOZ_ASSERT(false, "De-serializing unknown codec type!");
+      return false;
+    }
+
+    return true;
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog)
+  {
+    const char* codecName = nullptr;
+    if (aParam.mCodecType == kGMPVideoCodecVP8) {
+      codecName = "VP8";
+    }
+    aLog->append(StringPrintf(L"[%s]", codecName));
+  }
+};
+
+} // namespace IPC
+
+#endif // GMPMessageUtils_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPParent.cpp
@@ -0,0 +1,424 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPParent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIInputStream.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsThreadUtils.h"
+#include "nsIRunnable.h"
+#include "mozIGeckoMediaPluginService.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPParent::GMPParent()
+  : mState(GMPStateNotLoaded)
+  , mProcess(nullptr)
+{
+}
+
+GMPParent::~GMPParent()
+{
+}
+
+nsresult
+GMPParent::Init(nsIFile* aPluginDir)
+{
+  MOZ_ASSERT(aPluginDir);
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  mDirectory = aPluginDir;
+
+  nsAutoString leafname;
+  nsresult rv = aPluginDir->GetLeafName(leafname);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  MOZ_ASSERT(leafname.Length() > 4);
+  mName = Substring(leafname, 4);
+
+  return ReadGMPMetaData();
+}
+
+nsresult
+GMPParent::LoadProcess()
+{
+  MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  if (mState == GMPStateLoaded) {
+    return NS_OK;
+  }
+
+  nsAutoCString path;
+  if (NS_FAILED(mDirectory->GetNativePath(path))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mProcess = new GMPProcessParent(path.get());
+  if (!mProcess->Launch(30 * 1000)) {
+    mProcess->Delete();
+    mProcess = nullptr;
+    return NS_ERROR_FAILURE;
+  }
+
+  bool opened = Open(mProcess->GetChannel(), mProcess->GetChildProcessHandle());
+  if (!opened) {
+    mProcess->Delete();
+    mProcess = nullptr;
+    return NS_ERROR_FAILURE;
+  }
+
+  mState = GMPStateLoaded;
+
+  return NS_OK;
+}
+
+void
+GMPParent::MaybeUnloadProcess()
+{
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  if (mVideoDecoders.Length() == 0 &&
+      mVideoEncoders.Length() == 0) {
+    UnloadProcess();
+  }
+}
+
+void
+GMPParent::UnloadProcess()
+{
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  if (mState == GMPStateNotLoaded) {
+    MOZ_ASSERT(mVideoDecoders.IsEmpty() && mVideoEncoders.IsEmpty());
+    return;
+  }
+
+  mState = GMPStateNotLoaded;
+
+  // Invalidate and remove any remaining API objects.
+  for (uint32_t i = mVideoDecoders.Length(); i > 0; i--) {
+    mVideoDecoders[i - 1]->DecodingComplete();
+  }
+
+  // Invalidate and remove any remaining API objects.
+  for (uint32_t i = mVideoEncoders.Length(); i > 0; i--) {
+    mVideoEncoders[i - 1]->EncodingComplete();
+  }
+
+  Close();
+  if (mProcess) {
+    mProcess->Delete();
+    mProcess = nullptr;
+  }
+}
+
+void
+GMPParent::VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder)
+{
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  MOZ_ALWAYS_TRUE(mVideoDecoders.RemoveElement(aDecoder));
+
+  // Recv__delete__ is on the stack, don't potentially destroy the top-level actor
+  // until after this has completed.
+  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &GMPParent::MaybeUnloadProcess);
+  NS_DispatchToCurrentThread(event);
+}
+
+void
+GMPParent::VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder)
+{
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  MOZ_ALWAYS_TRUE(mVideoEncoders.RemoveElement(aEncoder));
+
+  // Recv__delete__ is on the stack, don't potentially destroy the top-level actor
+  // until after this has completed.
+  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &GMPParent::MaybeUnloadProcess);
+  NS_DispatchToCurrentThread(event);
+}
+
+GMPState
+GMPParent::State() const
+{
+  return mState;
+}
+
+#ifdef DEBUG
+nsIThread*
+GMPParent::GMPThread()
+{
+  if (!mGMPThread) {
+    nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+    MOZ_ASSERT(mps);
+    if (!mps) {
+      return nullptr;
+    }
+    mps->GetThread(getter_AddRefs(mGMPThread));
+    MOZ_ASSERT(mGMPThread);
+  }
+
+  return mGMPThread;
+}
+#endif
+
+bool
+GMPParent::SupportsAPI(const nsCString& aAPI, const nsCString& aTag)
+{
+  for (uint32_t i = 0; i < mCapabilities.Length(); i++) {
+    if (!mCapabilities[i]->mAPIName.Equals(aAPI)) {
+      continue;
+    }
+    nsTArray<nsCString>& tags = mCapabilities[i]->mAPITags;
+    for (uint32_t j = 0; j < tags.Length(); j++) {
+      if (tags[j].Equals(aTag)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool
+GMPParent::EnsureProcessLoaded()
+{
+  if (mState == GMPStateLoaded) {
+    return true;
+  }
+
+  nsresult rv = LoadProcess();
+
+  return NS_SUCCEEDED(rv);
+}
+
+nsresult
+GMPParent::GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD)
+{
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  if (!EnsureProcessLoaded()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  PGMPVideoDecoderParent* pvdp = SendPGMPVideoDecoderConstructor();
+  if (!pvdp) {
+    return NS_ERROR_FAILURE;
+  }
+  nsRefPtr<GMPVideoDecoderParent> vdp = static_cast<GMPVideoDecoderParent*>(pvdp);
+  mVideoDecoders.AppendElement(vdp);
+  vdp.forget(aGMPVD);
+
+  return NS_OK;
+}
+
+nsresult
+GMPParent::GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE)
+{
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  if (!EnsureProcessLoaded()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  PGMPVideoEncoderParent* pvep = SendPGMPVideoEncoderConstructor();
+  if (!pvep) {
+    return NS_ERROR_FAILURE;
+  }
+  nsRefPtr<GMPVideoEncoderParent> vep = static_cast<GMPVideoEncoderParent*>(pvep);
+  mVideoEncoders.AppendElement(vep);
+  vep.forget(aGMPVE);
+
+  return NS_OK;
+}
+
+void
+GMPParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  UnloadProcess();
+}
+
+PGMPVideoDecoderParent*
+GMPParent::AllocPGMPVideoDecoderParent()
+{
+  GMPVideoDecoderParent* vdp = new GMPVideoDecoderParent(this);
+  NS_ADDREF(vdp);
+  return vdp;
+}
+
+bool
+GMPParent::DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor)
+{
+  GMPVideoDecoderParent* vdp = static_cast<GMPVideoDecoderParent*>(aActor);
+  NS_RELEASE(vdp);
+  return true;
+}
+
+PGMPVideoEncoderParent*
+GMPParent::AllocPGMPVideoEncoderParent()
+{
+  GMPVideoEncoderParent* vep = new GMPVideoEncoderParent(this);
+  NS_ADDREF(vep);
+  return vep;
+}
+
+bool
+GMPParent::DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor)
+{
+  GMPVideoEncoderParent* vep = static_cast<GMPVideoEncoderParent*>(aActor);
+  NS_RELEASE(vep);
+  return true;
+}
+
+nsresult
+ParseNextRecord(nsILineInputStream* aLineInputStream,
+                const nsCString& aPrefix,
+                nsCString& aResult,
+                bool& aMoreLines)
+{
+  nsAutoCString record;
+  nsresult rv = aLineInputStream->ReadLine(record, &aMoreLines);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (record.Length() <= aPrefix.Length() ||
+      !Substring(record, 0, aPrefix.Length()).Equals(aPrefix)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aResult = Substring(record, aPrefix.Length());
+  aResult.Trim("\b\t\r\n ");
+
+  return NS_OK;
+}
+
+nsresult
+GMPParent::ReadGMPMetaData()
+{
+  MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
+  MOZ_ASSERT(!mName.IsEmpty(), "Plugin mName cannot be empty!");
+
+  nsCOMPtr<nsIFile> infoFile;
+  nsresult rv = mDirectory->Clone(getter_AddRefs(infoFile));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  infoFile->AppendRelativePath(mName + NS_LITERAL_STRING(".info"));
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), infoFile);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(inputStream, &rv);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCString value;
+  bool moreLines = false;
+
+  // 'Name:' record
+  nsCString prefix = NS_LITERAL_CSTRING("Name:");
+  rv = ParseNextRecord(lineInputStream, prefix, value, moreLines);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (value.IsEmpty()) {
+    // Not OK for name to be empty. Must have one non-whitespace character.
+    return NS_ERROR_FAILURE;
+  }
+  mDisplayName = value;
+
+  // 'Description:' record
+  if (!moreLines) {
+    return NS_ERROR_FAILURE;
+  }
+  prefix = NS_LITERAL_CSTRING("Description:");
+  rv = ParseNextRecord(lineInputStream, prefix, value, moreLines);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  mDescription = value;
+
+  // 'Version:' record
+  if (!moreLines) {
+    return NS_ERROR_FAILURE;
+  }
+  prefix = NS_LITERAL_CSTRING("Version:");
+  rv = ParseNextRecord(lineInputStream, prefix, value, moreLines);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  mVersion = value;
+
+  // 'Capability:' record
+  if (!moreLines) {
+    return NS_ERROR_FAILURE;
+  }
+  prefix = NS_LITERAL_CSTRING("APIs:");
+  rv = ParseNextRecord(lineInputStream, prefix, value, moreLines);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsCCharSeparatedTokenizer apiTokens(value, ',');
+  while (apiTokens.hasMoreTokens()) {
+    nsAutoCString api(apiTokens.nextToken());
+    api.StripWhitespace();
+    if (api.IsEmpty()) {
+      continue;
+    }
+
+    int32_t tagsStart = api.FindChar('[');
+    if (tagsStart == 0) {
+      // Not allowed to be the first character.
+      // API name must be at least one character.
+      continue;
+    }
+
+    auto cap = new GMPCapability();
+    if (tagsStart == -1) {
+      // No tags.
+      cap->mAPIName.Assign(api);
+    } else {
+      auto tagsEnd = api.FindChar(']');
+      if (tagsEnd == -1 || tagsEnd < tagsStart) {
+        // Invalid syntax, skip whole capability.
+        delete cap;
+        continue;
+      }
+
+      cap->mAPIName.Assign(Substring(api, 0, tagsStart));
+
+      if ((tagsEnd - tagsStart) > 1) {
+        const nsDependentCSubstring ts(Substring(api, tagsStart + 1, tagsEnd - tagsStart - 1));
+        nsCCharSeparatedTokenizer tagTokens(ts, ':');
+        while (tagTokens.hasMoreTokens()) {
+          const nsDependentCSubstring tag(tagTokens.nextToken());
+          cap->mAPITags.AppendElement(tag);
+        }
+      }
+    }
+
+    mCapabilities.AppendElement(cap);
+  }
+
+  if (mCapabilities.IsEmpty()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPParent.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPParent_h_
+#define GMPParent_h_
+
+#include "GMPProcessParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "GMPVideoEncoderParent.h"
+#include "mozilla/gmp/PGMPParent.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsILineInputStream;
+class nsIThread;
+class nsIFile;
+
+namespace mozilla {
+namespace gmp {
+
+class GMPCapability
+{
+public:
+  nsCString mAPIName;
+  nsTArray<nsCString> mAPITags;
+};
+
+enum GMPState {
+  GMPStateNotLoaded,
+  GMPStateLoaded
+};
+
+class GMPParent MOZ_FINAL : public PGMPParent
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(GMPParent)
+
+  GMPParent();
+
+  nsresult Init(nsIFile* aPluginDir);
+  nsresult LoadProcess();
+  void MaybeUnloadProcess();
+  void UnloadProcess();
+  bool SupportsAPI(const nsCString& aAPI, const nsCString& aTag);
+  nsresult GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD);
+  void VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder);
+  nsresult GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE);
+  void VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder);
+  GMPState State() const;
+#ifdef DEBUG
+  nsIThread* GMPThread();
+#endif
+
+private:
+  ~GMPParent();
+  bool EnsureProcessLoaded();
+  nsresult ReadGMPMetaData();
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+  virtual PGMPVideoDecoderParent* AllocPGMPVideoDecoderParent() MOZ_OVERRIDE;
+  virtual bool DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor) MOZ_OVERRIDE;
+  virtual PGMPVideoEncoderParent* AllocPGMPVideoEncoderParent() MOZ_OVERRIDE;
+  virtual bool DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor) MOZ_OVERRIDE;
+
+  GMPState mState;
+  nsCOMPtr<nsIFile> mDirectory; // plugin directory on disk
+  nsString mName; // base name of plugin on disk, UTF-16 because used for paths
+  nsCString mDisplayName; // name of plugin displayed to users
+  nsCString mDescription; // description of plugin for display to users
+  nsCString mVersion;
+  nsTArray<nsAutoPtr<GMPCapability>> mCapabilities;
+  GMPProcessParent* mProcess;
+
+  nsTArray<nsRefPtr<GMPVideoDecoderParent>> mVideoDecoders;
+  nsTArray<nsRefPtr<GMPVideoEncoderParent>> mVideoEncoders;
+#ifdef DEBUG
+  nsCOMPtr<nsIThread> mGMPThread;
+#endif
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPParent_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPPlatform.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPPlatform.h"
+#include "mozilla/Monitor.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+namespace gmp {
+
+static MessageLoop* sMainLoop = nullptr;
+
+// We just need a refcounted wrapper for GMPTask objects.
+class Runnable MOZ_FINAL
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Runnable)
+
+  Runnable(GMPTask* aTask)
+  : mTask(aTask)
+  {
+    MOZ_ASSERT(mTask);
+  }
+
+  void Run()
+  {
+    mTask->Run();
+    mTask = nullptr;
+  }
+
+private:
+  ~Runnable()
+  {
+  }
+
+  nsAutoPtr<GMPTask> mTask;
+};
+
+class SyncRunnable MOZ_FINAL
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SyncRunnable)
+
+  SyncRunnable(GMPTask* aTask, MessageLoop* aMessageLoop)
+  : mDone(false)
+  , mTask(aTask)
+  , mMessageLoop(aMessageLoop)
+  , mMonitor("GMPSyncRunnable")
+  {
+    MOZ_ASSERT(mTask);
+    MOZ_ASSERT(mMessageLoop);
+  }
+
+  void Post()
+  {
+    // We assert here for two reasons.
+    // 1) Nobody should be blocking the main thread.
+    // 2) This prevents deadlocks when doing sync calls to main which if the
+    //    main thread tries to do a sync call back to the calling thread.
+    MOZ_ASSERT(MessageLoop::current() != sMainLoop);
+
+    mMessageLoop->PostTask(FROM_HERE, NewRunnableMethod(this, &SyncRunnable::Run));
+    MonitorAutoLock lock(mMonitor);
+    while (!mDone) {
+      lock.Wait();
+    }
+  }
+
+  void Run()
+  {
+    mTask->Run();
+    mTask = nullptr;
+    MonitorAutoLock lock(mMonitor);
+    mDone = true;
+    lock.Notify();
+  }
+
+private:
+  ~SyncRunnable()
+  {
+  }
+
+  bool mDone;
+  nsAutoPtr<GMPTask> mTask;
+  MessageLoop* mMessageLoop;
+  Monitor mMonitor;
+};
+
+GMPErr
+CreateThread(GMPThread** aThread)
+{
+  if (!aThread) {
+    return GMPGenericErr;
+  }
+
+  *aThread = new GMPThreadImpl();
+
+  return GMPNoErr;
+}
+
+GMPErr
+RunOnMainThread(GMPTask* aTask)
+{
+  if (!aTask || !sMainLoop) {
+    return GMPGenericErr;
+  }
+
+  nsRefPtr<Runnable> r = new Runnable(aTask);
+
+  sMainLoop->PostTask(FROM_HERE, NewRunnableMethod(r.get(), &Runnable::Run));
+
+  return GMPNoErr;
+}
+
+GMPErr
+SyncRunOnMainThread(GMPTask* aTask)
+{
+  if (!aTask || !sMainLoop || sMainLoop == MessageLoop::current()) {
+    return GMPGenericErr;
+  }
+
+  nsRefPtr<SyncRunnable> r = new SyncRunnable(aTask, sMainLoop);
+
+  r->Post();
+
+  return GMPNoErr;
+}
+
+GMPErr
+CreateMutex(GMPMutex** aMutex)
+{
+  if (!aMutex) {
+    return GMPGenericErr;
+  }
+
+  *aMutex = new GMPMutexImpl();
+
+  return GMPNoErr;
+}
+
+void
+InitPlatformAPI(GMPPlatformAPI& aPlatformAPI)
+{
+  if (!sMainLoop) {
+    sMainLoop = MessageLoop::current();
+  }
+
+  aPlatformAPI.version = 0;
+  aPlatformAPI.createthread = &CreateThread;
+  aPlatformAPI.runonmainthread = &RunOnMainThread;
+  aPlatformAPI.syncrunonmainthread = &SyncRunOnMainThread;
+  aPlatformAPI.createmutex = &CreateMutex;
+}
+
+GMPThreadImpl::GMPThreadImpl()
+: mMutex("GMPThreadImpl"),
+  mThread("GMPThread")
+{
+}
+
+GMPThreadImpl::~GMPThreadImpl()
+{
+}
+
+void
+GMPThreadImpl::Post(GMPTask* aTask)
+{
+  MutexAutoLock lock(mMutex);
+
+  if (!mThread.IsRunning()) {
+    bool started = mThread.Start();
+    if (!started) {
+      NS_WARNING("Unable to start GMPThread!");
+      return;
+    }
+  }
+
+  nsRefPtr<Runnable> r = new Runnable(aTask);
+
+  mThread.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(r.get(), &Runnable::Run));
+}
+
+void
+GMPThreadImpl::Join()
+{
+  MutexAutoLock lock(mMutex);
+  if (mThread.IsRunning()) {
+    mThread.Stop();
+  }
+}
+
+GMPMutexImpl::GMPMutexImpl()
+: mMutex("gmp-mutex")
+{
+}
+
+GMPMutexImpl::~GMPMutexImpl()
+{
+}
+
+void
+GMPMutexImpl::Acquire()
+{
+  mMutex.Lock();
+}
+
+void
+GMPMutexImpl::Release()
+{
+  mMutex.Unlock();
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPPlatform.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPPlatform_h_
+#define GMPPlatform_h_
+
+#include "mozilla/Mutex.h"
+#include "gmp-platform.h"
+#include "base/thread.h"
+
+namespace mozilla {
+namespace gmp {
+
+void InitPlatformAPI(GMPPlatformAPI& aPlatformAPI);
+
+class GMPThreadImpl : public GMPThread
+{
+public:
+  GMPThreadImpl();
+  virtual ~GMPThreadImpl();
+
+  // GMPThread
+  virtual void Post(GMPTask* aTask) MOZ_OVERRIDE;
+  virtual void Join() MOZ_OVERRIDE;
+
+private:
+  Mutex mMutex;
+  base::Thread mThread;
+};
+
+class GMPMutexImpl : public GMPMutex
+{
+public:
+  GMPMutexImpl();
+  virtual ~GMPMutexImpl();
+
+  // GMPMutex
+  virtual void Acquire() MOZ_OVERRIDE;
+  virtual void Release() MOZ_OVERRIDE;
+
+private:
+  Mutex mMutex;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPPlatform_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPProcessChild.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPProcessChild.h"
+
+#include "base/command_line.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "mozilla/ipc/IOThreadChild.h"
+#include "mozilla/BackgroundHangMonitor.h"
+
+using mozilla::ipc::IOThreadChild;
+
+namespace mozilla {
+namespace gmp {
+
+GMPProcessChild::GMPProcessChild(ProcessHandle parentHandle)
+: ProcessChild(parentHandle)
+{
+}
+
+GMPProcessChild::~GMPProcessChild()
+{
+}
+
+bool
+GMPProcessChild::Init()
+{
+  std::string pluginFilename;
+
+#if defined(OS_POSIX)
+  // NB: need to be very careful in ensuring that the first arg
+  // (after the binary name) here is indeed the plugin module path.
+  // Keep in sync with dom/plugins/PluginModuleParent.
+  std::vector<std::string> values = CommandLine::ForCurrentProcess()->argv();
+  NS_ABORT_IF_FALSE(values.size() >= 2, "not enough args");
+  pluginFilename = values[1];
+#elif defined(OS_WIN)
+  std::vector<std::wstring> values = CommandLine::ForCurrentProcess()->GetLooseValues();
+  NS_ABORT_IF_FALSE(values.size() >= 1, "not enough loose args");
+  pluginFilename = WideToUTF8(values[0]);
+#else
+#error Not implemented
+#endif
+
+  BackgroundHangMonitor::Startup();
+
+  return mPlugin.Init(pluginFilename,
+                      ParentHandle(),
+                      IOThreadChild::message_loop(),
+                      IOThreadChild::channel());
+}
+
+void
+GMPProcessChild::CleanUp()
+{
+  BackgroundHangMonitor::Shutdown();
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPProcessChild.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPProcessChild_h_
+#define GMPProcessChild_h_
+
+#include "mozilla/ipc/ProcessChild.h"
+#include "GMPChild.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPProcessChild MOZ_FINAL : public mozilla::ipc::ProcessChild {
+protected:
+  typedef mozilla::ipc::ProcessChild ProcessChild;
+
+public:
+  GMPProcessChild(ProcessHandle parentHandle);
+  ~GMPProcessChild();
+
+  virtual bool Init() MOZ_OVERRIDE;
+  virtual void CleanUp() MOZ_OVERRIDE;
+
+private:
+  GMPChild mPlugin;
+
+  DISALLOW_COPY_AND_ASSIGN(GMPProcessChild);
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPProcessChild_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPProcessParent.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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 "GMPProcessParent.h"
+
+#include "base/string_util.h"
+#include "base/process_util.h"
+
+using std::vector;
+using std::string;
+
+using mozilla::gmp::GMPProcessParent;
+using mozilla::ipc::GeckoChildProcessHost;
+using base::ProcessArchitecture;
+
+template<>
+struct RunnableMethodTraits<GMPProcessParent>
+{
+  static void RetainCallee(GMPProcessParent* obj) { }
+  static void ReleaseCallee(GMPProcessParent* obj) { }
+};
+
+namespace mozilla {
+namespace gmp {
+
+GMPProcessParent::GMPProcessParent(const std::string& aGMPPath)
+: GeckoChildProcessHost(GeckoProcessType_GMPlugin),
+  mGMPPath(aGMPPath)
+{
+}
+
+GMPProcessParent::~GMPProcessParent()
+{
+}
+
+bool
+GMPProcessParent::Launch(int32_t aTimeoutMs)
+{
+  vector<string> args;
+  args.push_back(mGMPPath);
+  return SyncLaunch(args, aTimeoutMs, base::GetCurrentProcessArchitecture());
+}
+
+void
+GMPProcessParent::Delete()
+{
+  MessageLoop* currentLoop = MessageLoop::current();
+  MessageLoop* ioLoop = XRE_GetIOMessageLoop();
+
+  if (currentLoop == ioLoop) {
+    delete this;
+    return;
+  }
+
+  ioLoop->PostTask(FROM_HERE, NewRunnableMethod(this, &GMPProcessParent::Delete));
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPProcessParent.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPProcessParent_h
+#define GMPProcessParent_h 1
+
+#include "mozilla/Attributes.h"
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/scoped_ptr.h"
+#include "base/thread.h"
+#include "base/waitable_event.h"
+#include "chrome/common/child_process_host.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPProcessParent MOZ_FINAL : public mozilla::ipc::GeckoChildProcessHost
+{
+public:
+  GMPProcessParent(const std::string& aGMPPath);
+  ~GMPProcessParent();
+
+  // Synchronously launch the plugin process. If the process fails to launch
+  // after timeoutMs, this method will return false.
+  bool Launch(int32_t aTimeoutMs);
+
+  void Delete();
+
+  virtual bool CanShutdown() MOZ_OVERRIDE { return true; }
+  const std::string& GetPluginFilePath() { return mGMPPath; }
+
+  using mozilla::ipc::GeckoChildProcessHost::GetShutDownEvent;
+  using mozilla::ipc::GeckoChildProcessHost::GetChannel;
+  using mozilla::ipc::GeckoChildProcessHost::GetChildProcessHandle;
+
+private:
+  std::string mGMPPath;
+
+  DISALLOW_COPY_AND_ASSIGN(GMPProcessParent);
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // ifndef GMPProcessParent_h
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPService.cpp
@@ -0,0 +1,485 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPService.h"
+#include "GMPParent.h"
+#include "GMPVideoDecoderParent.h"
+#include "nsIObserverService.h"
+#include "GeckoChildProcessHost.h"
+#if defined(XP_WIN)
+#include "nsIWindowsRegKey.h"
+#endif
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsXPCOMPrivate.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace gmp {
+
+static StaticRefPtr<GeckoMediaPluginService> sSingletonService;
+
+class GMPServiceCreateHelper MOZ_FINAL : public nsRunnable
+{
+  nsRefPtr<GeckoMediaPluginService> mService;
+
+public:
+  static already_AddRefed<GeckoMediaPluginService>
+  GetOrCreate()
+  {
+    nsRefPtr<GeckoMediaPluginService> service;
+
+    if (NS_IsMainThread()) {
+      service = GetOrCreateOnMainThread();
+    } else {
+      nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+      MOZ_ASSERT(mainThread);
+
+      nsRefPtr<GMPServiceCreateHelper> createHelper = new GMPServiceCreateHelper();
+
+      mozilla::SyncRunnable::DispatchToThread(mainThread, createHelper, true);
+
+      service = createHelper->mService.forget();
+    }
+
+    return service.forget();
+  }
+
+private:
+  GMPServiceCreateHelper()
+  {
+  }
+
+  ~GMPServiceCreateHelper()
+  {
+    MOZ_ASSERT(!mService);
+  }
+
+  static already_AddRefed<GeckoMediaPluginService>
+  GetOrCreateOnMainThread()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsRefPtr<GeckoMediaPluginService> service = sSingletonService.get();
+    if (!service) {
+      service = new GeckoMediaPluginService();
+      service->Init();
+
+      sSingletonService = service;
+      ClearOnShutdown(&sSingletonService);
+    }
+
+    return service.forget();
+  }
+
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mService = GetOrCreateOnMainThread();
+    return NS_OK;
+  }
+};
+
+already_AddRefed<GeckoMediaPluginService>
+GeckoMediaPluginService::GetGeckoMediaPluginService()
+{
+  return GMPServiceCreateHelper::GetOrCreate();
+}
+
+NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService, nsIObserver)
+
+GeckoMediaPluginService::GeckoMediaPluginService()
+  : mMutex("GeckoMediaPluginService::mMutex")
+  , mShuttingDown(false)
+  , mShuttingDownOnGMPThread(false)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+GeckoMediaPluginService::~GeckoMediaPluginService()
+{
+  MOZ_ASSERT(mPlugins.IsEmpty());
+}
+
+void
+GeckoMediaPluginService::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Cache user directory while we're on the main thread. We do this because
+  // if we try to use "~" in a path during plugin lookup on a non-main thread,
+  // the nsIFile code will try to resolve it using NS_GetSpecialDirectory, which
+  // doesn't work on non-main threads.
+  nsCOMPtr<nsIFile> homeDir;
+  NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(homeDir));
+  if (homeDir) {
+    homeDir->GetPath(mHomePath);
+  }
+
+  nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+  MOZ_ASSERT(obsService);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)));
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::Observe(nsISupports* aSubject,
+                                 const char* aTopic,
+                                 const char16_t* aSomeData)
+{
+  if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
+    nsCOMPtr<nsIThread> gmpThread;
+    {
+      MutexAutoLock lock(mMutex);
+      MOZ_ASSERT(!mShuttingDown);
+      mShuttingDown = true;
+      gmpThread = mGMPThread;
+    }
+
+    if (gmpThread) {
+      gmpThread->Dispatch(NS_NewRunnableMethod(this, &GeckoMediaPluginService::UnloadPlugins),
+                           NS_DISPATCH_SYNC);
+    } else {
+      MOZ_ASSERT(mPlugins.IsEmpty());
+    }
+  } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
+    nsCOMPtr<nsIThread> gmpThread;
+    {
+      MutexAutoLock lock(mMutex);
+      MOZ_ASSERT(mShuttingDown);
+      mGMPThread.swap(gmpThread);
+    }
+
+    if (gmpThread) {
+      gmpThread->Shutdown();
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetThread(nsIThread** aThread)
+{
+  MOZ_ASSERT(aThread);
+
+  // This can be called from any thread.
+  MutexAutoLock lock(mMutex);
+
+  if (!mGMPThread) {
+    // Don't allow the thread to be created after shutdown has started.
+    if (mShuttingDown) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread));
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
+  NS_ADDREF(mGMPThread);
+  *aThread = mGMPThread;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPVideoDecoderVP8(GMPVideoHost** aOutVideoHost, GMPVideoDecoder** aGMPVD)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  if (mShuttingDownOnGMPThread) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<GMPParent> gmp = SelectPluginForAPI(NS_LITERAL_CSTRING("decode-video"),
+                                               NS_LITERAL_CSTRING("vp8"));
+  if (!gmp) {
+    return NS_ERROR_FAILURE;
+  }
+
+  GMPVideoDecoderParent* gmpVDP;
+  nsresult rv = gmp->GetGMPVideoDecoder(&gmpVDP);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  *aGMPVD = gmpVDP;
+  *aOutVideoHost = &gmpVDP->Host();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::GetGMPVideoEncoderVP8(GMPVideoHost** aOutVideoHost, GMPVideoEncoder** aGMPVE)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  if (mShuttingDownOnGMPThread) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<GMPParent> gmp = SelectPluginForAPI(NS_LITERAL_CSTRING("encode-video"),
+                                               NS_LITERAL_CSTRING("vp8"));
+  if (!gmp) {
+    return NS_ERROR_FAILURE;
+  }
+
+  GMPVideoEncoderParent* gmpVEP;
+  nsresult rv = gmp->GetGMPVideoEncoder(&gmpVEP);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  *aGMPVE = gmpVEP;
+  *aOutVideoHost = &gmpVEP->Host();
+
+  return NS_OK;
+}
+
+void
+GeckoMediaPluginService::UnloadPlugins()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  MOZ_ASSERT(!mShuttingDownOnGMPThread);
+  mShuttingDownOnGMPThread = true;
+
+  for (uint32_t i = 0; i < mPlugins.Length(); i++) {
+    mPlugins[i]->UnloadProcess();
+  }
+  mPlugins.Clear();
+}
+
+GMPParent*
+GeckoMediaPluginService::SelectPluginForAPI(const nsCString& aAPI,
+                                            const nsCString& aTag)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  GMPParent* gmp = SelectPluginFromListForAPI(aAPI, aTag);
+  if (gmp) {
+    return gmp;
+  }
+
+  RefreshPluginList();
+
+  return SelectPluginFromListForAPI(aAPI, aTag);
+}
+
+GMPParent*
+GeckoMediaPluginService::SelectPluginFromListForAPI(const nsCString& aAPI,
+                                                    const nsCString& aTag)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  for (uint32_t i = 0; i < mPlugins.Length(); i++) {
+    GMPParent* gmp = mPlugins[i];
+    if (gmp->SupportsAPI(aAPI, aTag)) {
+      return gmp;
+    }
+  }
+  return nullptr;
+}
+
+nsresult
+GeckoMediaPluginService::GetDirectoriesToSearch(nsTArray<nsCOMPtr<nsIFile>> &aDirs)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+#if defined(XP_MACOSX)
+  nsCOMPtr<nsIFile> searchDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+  MOZ_ASSERT(!mHomePath.IsEmpty());
+  nsresult rv = searchDir->InitWithPath(mHomePath + NS_LITERAL_STRING("/Library/Internet Plug-Ins/"));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aDirs.AppendElement(searchDir);
+  searchDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+  rv = searchDir->InitWithPath(NS_LITERAL_STRING("/Library/Internet Plug-Ins/"));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aDirs.AppendElement(searchDir);
+#elif defined(OS_POSIX)
+  nsCOMPtr<nsIFile> searchDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+  nsresult rv = searchDir->InitWithPath(NS_LITERAL_STRING("/usr/lib/mozilla/plugins/"));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aDirs.AppendElement(searchDir);
+#endif
+  return NS_OK;
+}
+
+#if defined(XP_WIN)
+static nsresult
+GetPossiblePluginsForRegRoot(uint32_t aKey, nsTArray<nsCOMPtr<nsIFile>>& aDirs)
+{
+  nsCOMPtr<nsIWindowsRegKey> regKey = do_CreateInstance("@mozilla.org/windows-registry-key;1");
+  if (!regKey) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = regKey->Open(aKey,
+                             NS_LITERAL_STRING("Software\\MozillaPlugins"),
+                             nsIWindowsRegKey::ACCESS_READ);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  uint32_t childCount = 0;
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(regKey->GetChildCount(&childCount)));
+  for (uint32_t index = 0; index < childCount; index++) {
+    nsAutoString childName;
+    rv = regKey->GetChildName(index, childName);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    nsCOMPtr<nsIWindowsRegKey> childKey;
+    rv = regKey->OpenChild(childName, nsIWindowsRegKey::ACCESS_QUERY_VALUE,
+                           getter_AddRefs(childKey));
+    if (NS_FAILED(rv) || !childKey) {
+      continue;
+    }
+
+    nsAutoString path;
+    rv = childKey->ReadStringValue(NS_LITERAL_STRING("Path"), path);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    nsCOMPtr<nsIFile> localFile;
+    if (NS_SUCCEEDED(NS_NewLocalFile(path, true, getter_AddRefs(localFile))) &&
+        localFile) {
+      bool isFileThere = false;
+      if (NS_SUCCEEDED(localFile->Exists(&isFileThere)) && isFileThere) {
+        aDirs.AppendElement(localFile);
+      }
+    }
+  }
+
+  regKey->Close();
+
+  return NS_OK;
+}
+#endif
+
+nsresult
+GeckoMediaPluginService::GetPossiblePlugins(nsTArray<nsCOMPtr<nsIFile>>& aDirs)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+#if defined(XP_WIN)
+  // The ROOT_KEY_CURRENT_USER entry typically fails to open, causing this call to
+  // fail. Don't check any return values because if we find nothing we don't care.
+  GetPossiblePluginsForRegRoot(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, aDirs);
+  GetPossiblePluginsForRegRoot(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, aDirs);
+#endif
+  return NS_OK;
+}
+
+nsresult
+GeckoMediaPluginService::SearchDirectory(nsIFile* aSearchDir)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+  MOZ_ASSERT(aSearchDir);
+
+  nsCOMPtr<nsISimpleEnumerator> iter;
+  nsresult rv = aSearchDir->GetDirectoryEntries(getter_AddRefs(iter));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  bool hasMore;
+  while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+    nsCOMPtr<nsISupports> supports;
+    rv = iter->GetNext(getter_AddRefs(supports));
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+    nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv));
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+    ProcessPossiblePlugin(dirEntry);
+  }
+
+  return NS_OK;
+}
+
+void
+GeckoMediaPluginService::ProcessPossiblePlugin(nsIFile* aDir)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+  MOZ_ASSERT(aDir);
+
+  bool isDirectory = false;
+  nsresult rv = aDir->IsDirectory(&isDirectory);
+  if (NS_FAILED(rv) || !isDirectory) {
+    return;
+  }
+
+  nsAutoString leafName;
+  rv = aDir->GetLeafName(leafName);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  NS_NAMED_LITERAL_STRING(prefix, "gmp-");
+  if (leafName.Length() <= prefix.Length() ||
+      !Substring(leafName, 0, prefix.Length()).Equals(prefix)) {
+    return;
+  }
+
+  nsRefPtr<GMPParent> gmp = new GMPParent();
+  rv = gmp->Init(aDir);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  mPlugins.AppendElement(gmp);
+}
+
+void
+GeckoMediaPluginService::RefreshPluginList()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  for (uint32_t iPlusOne = mPlugins.Length(); iPlusOne > 0; iPlusOne--) {
+    if (mPlugins[iPlusOne - 1]->State() == GMPStateNotLoaded) {
+      mPlugins.RemoveElementAt(iPlusOne - 1);
+    }
+  }
+
+  nsTArray<nsCOMPtr<nsIFile>> searchDirs;
+  nsresult rv = GetDirectoriesToSearch(searchDirs);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < searchDirs.Length(); i++) {
+    SearchDirectory(searchDirs[i]);
+  }
+
+  nsTArray<nsCOMPtr<nsIFile>> possiblePlugins;
+  rv = GetPossiblePlugins(possiblePlugins);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < possiblePlugins.Length(); i++) {
+    ProcessPossiblePlugin(possiblePlugins[i]);
+  }
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPService.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPService_h_
+#define GMPService_h_
+
+#include "mozIGeckoMediaPluginService.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/Mutex.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIThread.h"
+
+class nsIFile;
+template <class> class already_AddRefed;
+
+namespace mozilla {
+namespace gmp {
+
+class GMPParent;
+
+class GeckoMediaPluginService MOZ_FINAL : public mozIGeckoMediaPluginService
+                                        , public nsIObserver
+{
+public:
+  static already_AddRefed<GeckoMediaPluginService> GetGeckoMediaPluginService();
+
+  GeckoMediaPluginService();
+  void Init();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_MOZIGECKOMEDIAPLUGINSERVICE
+  NS_DECL_NSIOBSERVER
+
+private:
+  ~GeckoMediaPluginService();
+
+  GMPParent* SelectPluginFromListForAPI(const nsCString& aAPI, const nsCString& aTag);
+  GMPParent* SelectPluginForAPI(const nsCString& aAPI, const nsCString& aTag);
+  void UnloadPlugins();
+
+  void RefreshPluginList();
+  void ProcessPossiblePlugin(nsIFile* aDir);
+  nsresult SearchDirectory(nsIFile* aSearchDir);
+  nsresult GetPossiblePlugins(nsTArray<nsCOMPtr<nsIFile>>& aDirs);
+  nsresult GetDirectoriesToSearch(nsTArray<nsCOMPtr<nsIFile>>& aDirs);
+
+  nsTArray<nsRefPtr<GMPParent>> mPlugins;
+  Mutex mMutex; // Protects mGMPThread and mShuttingDown
+  nsCOMPtr<nsIThread> mGMPThread;
+  bool mShuttingDown;
+  bool mShuttingDownOnGMPThread;
+  nsString mHomePath;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPService_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPSharedMemManager.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPSharedMemManager_h_
+#define GMPSharedMemManager_h_
+
+#include "mozilla/ipc/Shmem.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPSharedMemManager
+{
+public:
+  virtual bool MgrAllocShmem(size_t aSize,
+                             ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                             ipc::Shmem* aMem) = 0;
+  virtual bool MgrDeallocShmem(ipc::Shmem& aMem) = 0;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPSharedMemManager_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPTypes.ipdlh
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+namespace mozilla {
+namespace gmp {
+
+struct GMPVideoEncodedFrameData
+{
+  int64_t mCaptureTime_ms;
+  uint32_t mEncodedWidth;
+  uint32_t mEncodedHeight;
+  uint32_t mTimeStamp;
+  uint32_t mFrameType;
+  uint32_t mSize;
+  Shmem mBuffer;
+  bool mCompleteFrame;
+};
+
+struct GMPPlaneData
+{
+  int32_t mSize;
+  int32_t mStride;
+  Shmem mBuffer;
+};
+
+struct GMPVideoi420FrameData
+{
+  GMPPlaneData mYPlane;
+  GMPPlaneData mUPlane;
+  GMPPlaneData mVPlane;
+  int32_t mWidth;
+  int32_t mHeight;
+  uint32_t mTimestamp;
+  int64_t mRenderTime_ms;
+};
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoDecoderChild.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPVideoDecoderChild.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPChild.h"
+#include <stdio.h>
+#include "mozilla/unused.h"
+#include "GMPVideoEncodedFrameImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoDecoderChild::GMPVideoDecoderChild(GMPChild* aPlugin)
+: mPlugin(aPlugin),
+  mVideoDecoder(nullptr),
+  mVideoHost(MOZ_THIS_IN_INITIALIZER_LIST())
+{
+  MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoDecoderChild::~GMPVideoDecoderChild()
+{
+}
+
+void
+GMPVideoDecoderChild::Init(GMPVideoDecoder* aDecoder)
+{
+  MOZ_ASSERT(aDecoder, "Cannot initialize video decoder child without a video decoder!");
+  mVideoDecoder = aDecoder;
+}
+
+GMPVideoHostImpl&
+GMPVideoDecoderChild::Host()
+{
+  return mVideoHost;
+}
+
+void
+GMPVideoDecoderChild::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  if (!aDecodedFrame) {
+    MOZ_CRASH("Not given a decoded frame!");
+  }
+
+  auto df = static_cast<GMPVideoi420FrameImpl*>(aDecodedFrame);
+
+  GMPVideoi420FrameData frameData;
+  df->InitFrameData(frameData);
+  SendDecoded(frameData);
+
+  aDecodedFrame->Destroy();
+}
+
+void
+GMPVideoDecoderChild::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  SendReceivedDecodedReferenceFrame(aPictureId);
+}
+
+void
+GMPVideoDecoderChild::ReceivedDecodedFrame(const uint64_t aPictureId)
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  SendReceivedDecodedFrame(aPictureId);
+}
+
+void
+GMPVideoDecoderChild::InputDataExhausted()
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  SendInputDataExhausted();
+}
+
+bool
+GMPVideoDecoderChild::MgrAllocShmem(size_t aSize,
+                                    ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                                    ipc::Shmem* aMem)
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  return AllocShmem(aSize, aType, aMem);
+}
+
+bool
+GMPVideoDecoderChild::MgrDeallocShmem(Shmem& aMem)
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  return DeallocShmem(aMem);
+}
+
+bool
+GMPVideoDecoderChild::RecvInitDecode(const GMPVideoCodec& aCodecSettings,
+                                     const int32_t& aCoreCount)
+{
+  if (!mVideoDecoder) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoDecoder->InitDecode(aCodecSettings, this, aCoreCount);
+
+  return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDecode(const GMPVideoEncodedFrameData& aInputFrame,
+                                 const bool& aMissingFrames,
+                                 const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                                 const int64_t& aRenderTimeMs)
+{
+  if (!mVideoDecoder) {
+    return false;
+  }
+
+  auto f = new GMPVideoEncodedFrameImpl(aInputFrame, &mVideoHost);
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoDecoder->Decode(f, aMissingFrames, aCodecSpecificInfo, aRenderTimeMs);
+
+  return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvReset()
+{
+  if (!mVideoDecoder) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoDecoder->Reset();
+
+  return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDrain()
+{
+  if (!mVideoDecoder) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoDecoder->Drain();
+
+  return true;
+}
+
+bool
+GMPVideoDecoderChild::RecvDecodingComplete()
+{
+  if (mVideoDecoder) {
+    // Ignore any return code. It is OK for this to fail without killing the process.
+    mVideoDecoder->DecodingComplete();
+    mVideoDecoder = nullptr;
+  }
+
+  mVideoHost.DoneWithAPI();
+
+  mPlugin = nullptr;
+
+  unused << Send__delete__(this);
+
+  return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoDecoderChild.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoDecoderChild_h_
+#define GMPVideoDecoderChild_h_
+
+#include "nsString.h"
+#include "mozilla/gmp/PGMPVideoDecoderChild.h"
+#include "gmp-video-decode.h"
+#include "GMPSharedMemManager.h"
+#include "GMPVideoHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+
+class GMPVideoDecoderChild : public PGMPVideoDecoderChild,
+                             public GMPDecoderCallback,
+                             public GMPSharedMemManager
+{
+public:
+  GMPVideoDecoderChild(GMPChild* aPlugin);
+  virtual ~GMPVideoDecoderChild();
+
+  void Init(GMPVideoDecoder* aDecoder);
+  GMPVideoHostImpl& Host();
+
+  // GMPDecoderCallback
+  virtual void Decoded(GMPVideoi420Frame* decodedFrame) MOZ_OVERRIDE;
+  virtual void ReceivedDecodedReferenceFrame(const uint64_t pictureId) MOZ_OVERRIDE;
+  virtual void ReceivedDecodedFrame(const uint64_t pictureId) MOZ_OVERRIDE;
+  virtual void InputDataExhausted() MOZ_OVERRIDE;
+
+  // GMPSharedMemManager
+  virtual bool MgrAllocShmem(size_t aSize,
+                             ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                             ipc::Shmem* aMem) MOZ_OVERRIDE;
+  virtual bool MgrDeallocShmem(Shmem& aMem) MOZ_OVERRIDE;
+
+private:
+  // PGMPVideoDecoderChild
+  virtual bool RecvInitDecode(const GMPVideoCodec& codecSettings,
+                              const int32_t& coreCount) MOZ_OVERRIDE;
+  virtual bool RecvDecode(const GMPVideoEncodedFrameData& inputFrame,
+                          const bool& missingFrames,
+                          const GMPCodecSpecificInfo& codecSpecificInfo,
+                          const int64_t& renderTimeMs) MOZ_OVERRIDE;
+  virtual bool RecvReset() MOZ_OVERRIDE;
+  virtual bool RecvDrain() MOZ_OVERRIDE;
+  virtual bool RecvDecodingComplete() MOZ_OVERRIDE;
+
+  GMPChild* mPlugin;
+  GMPVideoDecoder* mVideoDecoder;
+  GMPVideoHostImpl mVideoHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoDecoderChild_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoDecoderParent.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPVideoDecoderParent.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPParent.h"
+#include <stdio.h>
+#include "mozilla/unused.h"
+#include "GMPMessageUtils.h"
+#include "nsAutoRef.h"
+#include "nsThreadUtils.h"
+
+template <>
+class nsAutoRefTraits<GMPVideoEncodedFrame> : public nsPointerRefTraits<GMPVideoEncodedFrame>
+{
+public:
+  static void Release(GMPVideoEncodedFrame* aFrame) { aFrame->Destroy(); }
+};
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoDecoderParent::GMPVideoDecoderParent(GMPParent* aPlugin)
+  : mCanSendMessages(true)
+  , mPlugin(aPlugin)
+  , mCallback(nullptr)
+  , mVideoHost(MOZ_THIS_IN_INITIALIZER_LIST())
+{
+  MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoDecoderParent::~GMPVideoDecoderParent()
+{
+}
+
+GMPVideoHostImpl&
+GMPVideoDecoderParent::Host()
+{
+  return mVideoHost;
+}
+
+bool
+GMPVideoDecoderParent::MgrAllocShmem(size_t aSize,
+                                     ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                                     ipc::Shmem* aMem)
+{
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  return AllocShmem(aSize, aType, aMem);
+}
+
+bool
+GMPVideoDecoderParent::MgrDeallocShmem(Shmem& aMem)
+{
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  return DeallocShmem(aMem);
+}
+
+GMPVideoErr
+GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
+                                  GMPDecoderCallback* aCallback,
+                                  int32_t aCoreCount)
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video decoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  if (!aCallback) {
+    return GMPVideoGenericErr;
+  }
+  mCallback = aCallback;
+
+  if (!SendInitDecode(aCodecSettings, aCoreCount)) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoDecoderParent::Decode(GMPVideoEncodedFrame* aInputFrame,
+                              bool aMissingFrames,
+                              const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                              int64_t aRenderTimeMs)
+{
+  nsAutoRef<GMPVideoEncodedFrame> autoDestroy(aInputFrame);
+
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video decoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  auto inputFrameImpl = static_cast<GMPVideoEncodedFrameImpl*>(aInputFrame);
+
+  GMPVideoEncodedFrameData frameData;
+  inputFrameImpl->RelinquishFrameData(frameData);
+
+  if (!SendDecode(frameData,
+                  aMissingFrames,
+                  aCodecSpecificInfo,
+                  aRenderTimeMs)) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoDecoderParent::Reset()
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video decoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  if (!SendReset()) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoDecoderParent::Drain()
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video decoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  if (!SendDrain()) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+void
+GMPVideoDecoderParent::DecodingComplete()
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video decoder!");
+    return;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  mCanSendMessages = false;
+
+  mCallback = nullptr;
+
+  mVideoHost.DoneWithAPI();
+
+  unused << SendDecodingComplete();
+}
+
+// Note: Keep this sync'd up with DecodingComplete
+void
+GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  if (mPlugin) {
+    // Ignore any return code. It is OK for this to fail without killing the process.
+    mPlugin->VideoDecoderDestroyed(this);
+    mPlugin = nullptr;
+  }
+  mCanSendMessages = false;
+  mCallback = nullptr;
+  mVideoHost.ActorDestroyed();
+}
+
+bool
+GMPVideoDecoderParent::RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame)
+{
+  if (!mCallback) {
+    return false;
+  }
+
+  auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost);
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mCallback->Decoded(f);
+
+  return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId)
+{
+  if (!mCallback) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mCallback->ReceivedDecodedReferenceFrame(aPictureId);
+
+  return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvReceivedDecodedFrame(const uint64_t& aPictureId)
+{
+  if (!mCallback) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mCallback->ReceivedDecodedFrame(aPictureId);
+
+  return true;
+}
+
+bool
+GMPVideoDecoderParent::RecvInputDataExhausted()
+{
+  if (!mCallback) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mCallback->InputDataExhausted();
+
+  return true;
+}
+
+bool
+GMPVideoDecoderParent::Recv__delete__()
+{
+  if (mPlugin) {
+    // Ignore any return code. It is OK for this to fail without killing the process.
+    mPlugin->VideoDecoderDestroyed(this);
+    mPlugin = nullptr;
+  }
+
+  return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoDecoderParent.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoDecoderParent_h_
+#define GMPVideoDecoderParent_h_
+
+#include "mozilla/RefPtr.h"
+#include "gmp-video-decode.h"
+#include "mozilla/gmp/PGMPVideoDecoderParent.h"
+#include "GMPMessageUtils.h"
+#include "GMPSharedMemManager.h"
+#include "GMPVideoHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPParent;
+
+class GMPVideoDecoderParent MOZ_FINAL : public GMPVideoDecoder
+                                      , public PGMPVideoDecoderParent
+                                      , public GMPSharedMemManager
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(GMPVideoDecoderParent)
+
+  GMPVideoDecoderParent(GMPParent *aPlugin);
+
+  GMPVideoHostImpl& Host();
+
+  // GMPSharedMemManager
+  virtual bool MgrAllocShmem(size_t aSize,
+                             ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                             ipc::Shmem* aMem) MOZ_OVERRIDE;
+  virtual bool MgrDeallocShmem(Shmem& aMem) MOZ_OVERRIDE;
+
+  // GMPVideoDecoder
+  virtual GMPVideoErr InitDecode(const GMPVideoCodec& aCodecSettings,
+                                 GMPDecoderCallback* aCallback,
+                                 int32_t aCoreCount) MOZ_OVERRIDE;
+  virtual GMPVideoErr Decode(GMPVideoEncodedFrame* aInputFrame,
+                             bool aMissingFrames,
+                             const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                             int64_t aRenderTimeMs = -1) MOZ_OVERRIDE;
+  virtual GMPVideoErr Reset() MOZ_OVERRIDE;
+  virtual GMPVideoErr Drain() MOZ_OVERRIDE;
+  virtual void DecodingComplete() MOZ_OVERRIDE;
+
+private:
+  ~GMPVideoDecoderParent();
+
+  // PGMPVideoDecoderParent
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+  virtual bool RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame) MOZ_OVERRIDE;
+  virtual bool RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId) MOZ_OVERRIDE;
+  virtual bool RecvReceivedDecodedFrame(const uint64_t& aPictureId) MOZ_OVERRIDE;
+  virtual bool RecvInputDataExhausted() MOZ_OVERRIDE;
+  virtual bool Recv__delete__() MOZ_OVERRIDE;
+
+  bool mCanSendMessages;
+  GMPParent* mPlugin;
+  GMPDecoderCallback* mCallback;
+  GMPVideoHostImpl mVideoHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoDecoderParent_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoEncodedFrameImpl.cpp
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoHost.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPSharedMemManager.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoEncodedFrameImpl::GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost)
+: mEncodedWidth(0),
+  mEncodedHeight(0),
+  mTimeStamp(0),
+  mCaptureTime_ms(0),
+  mFrameType(kGMPDeltaFrame),
+  mSize(0),
+  mCompleteFrame(false),
+  mHost(aHost)
+{
+  MOZ_ASSERT(aHost);
+  aHost->EncodedFrameCreated(this);
+}
+
+GMPVideoEncodedFrameImpl::GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData,
+                                                   GMPVideoHostImpl* aHost)
+: mEncodedWidth(aFrameData.mEncodedWidth()),
+  mEncodedHeight(aFrameData.mEncodedHeight()),
+  mTimeStamp(aFrameData.mTimeStamp()),
+  mCaptureTime_ms(aFrameData.mCaptureTime_ms()),
+  mFrameType(static_cast<GMPVideoFrameType>(aFrameData.mFrameType())),
+  mSize(aFrameData.mSize()),
+  mCompleteFrame(aFrameData.mCompleteFrame()),
+  mHost(aHost),
+  mBuffer(aFrameData.mBuffer())
+{
+  MOZ_ASSERT(aHost);
+  aHost->EncodedFrameCreated(this);
+}
+
+GMPVideoEncodedFrameImpl::~GMPVideoEncodedFrameImpl()
+{
+  DestroyBuffer();
+  if (mHost) {
+    mHost->EncodedFrameDestroyed(this);
+  }
+}
+
+GMPVideoFrameFormat
+GMPVideoEncodedFrameImpl::GetFrameFormat()
+{
+  return kGMPEncodedVideoFrame;
+}
+
+void
+GMPVideoEncodedFrameImpl::DoneWithAPI()
+{
+  DestroyBuffer();
+
+  // Do this after destroying the buffer because destruction
+  // involves deallocation, which requires a host.
+  mHost = nullptr;
+}
+
+void
+GMPVideoEncodedFrameImpl::ActorDestroyed()
+{
+  // Simply clear out Shmem reference, do not attempt to
+  // properly free it. It has already been freed.
+  mBuffer = ipc::Shmem();
+  // No more host.
+  mHost = nullptr;
+}
+
+bool
+GMPVideoEncodedFrameImpl::RelinquishFrameData(GMPVideoEncodedFrameData& aFrameData)
+{
+  aFrameData.mEncodedWidth() = mEncodedWidth;
+  aFrameData.mEncodedHeight() = mEncodedHeight;
+  aFrameData.mTimeStamp() = mTimeStamp;
+  aFrameData.mCaptureTime_ms() = mCaptureTime_ms;
+  aFrameData.mFrameType() = mFrameType;
+  aFrameData.mSize() = mSize;
+  aFrameData.mCompleteFrame() = mCompleteFrame;
+  aFrameData.mBuffer() = mBuffer;
+
+  // This method is called right before Shmem is sent to another process.
+  // We need to effectively zero out our member copy so that we don't
+  // try to delete Shmem we don't own later.
+  mBuffer = ipc::Shmem();
+
+  return true;
+}
+
+void
+GMPVideoEncodedFrameImpl::DestroyBuffer()
+{
+  if (mHost && mBuffer.IsWritable()) {
+    mHost->SharedMemMgr()->MgrDeallocShmem(mBuffer);
+  }
+  mBuffer = ipc::Shmem();
+}
+
+GMPVideoErr
+GMPVideoEncodedFrameImpl::CreateEmptyFrame(uint32_t aSize)
+{
+  DestroyBuffer();
+
+  if (aSize != 0) {
+    if (!mHost->SharedMemMgr()->MgrAllocShmem(aSize, ipc::SharedMemory::TYPE_BASIC, &mBuffer) ||
+        !Buffer()) {
+      return GMPVideoAllocErr;
+    }
+  }
+
+  mSize = aSize;
+
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoEncodedFrameImpl::CopyFrame(const GMPVideoEncodedFrame& aFrame)
+{
+  auto& f = static_cast<const GMPVideoEncodedFrameImpl&>(aFrame);
+
+  if (f.mSize != 0) {
+    GMPVideoErr err = CreateEmptyFrame(f.mSize);
+    if (err != GMPVideoNoErr) {
+      return err;
+    }
+    memcpy(Buffer(), f.Buffer(), f.mSize);
+  }
+  mEncodedWidth = f.mEncodedWidth;
+  mEncodedHeight = f.mEncodedHeight;
+  mTimeStamp = f.mTimeStamp;
+  mCaptureTime_ms = f.mCaptureTime_ms;
+  mFrameType = f.mFrameType;
+  mSize = f.mSize;
+  mCompleteFrame = f.mCompleteFrame;
+  // Don't copy host, that should have been set properly on object creation via host.
+
+  return GMPVideoNoErr;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetEncodedWidth(uint32_t aEncodedWidth)
+{
+  mEncodedWidth = aEncodedWidth;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::EncodedWidth()
+{
+  return mEncodedWidth;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetEncodedHeight(uint32_t aEncodedHeight)
+{
+  mEncodedHeight = aEncodedHeight;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::EncodedHeight()
+{
+  return mEncodedHeight;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetTimeStamp(uint32_t aTimeStamp)
+{
+  mTimeStamp = aTimeStamp;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::TimeStamp()
+{
+  return mTimeStamp;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetCaptureTime(int64_t aCaptureTime)
+{
+  mCaptureTime_ms = aCaptureTime;
+}
+
+int64_t
+GMPVideoEncodedFrameImpl::CaptureTime()
+{
+  return mCaptureTime_ms;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetFrameType(GMPVideoFrameType aFrameType)
+{
+  mFrameType = aFrameType;
+}
+
+GMPVideoFrameType
+GMPVideoEncodedFrameImpl::FrameType()
+{
+  return mFrameType;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetAllocatedSize(uint32_t aNewSize)
+{
+  if (aNewSize <= AllocatedSize()) {
+    return;
+  }
+
+  if (!mHost) {
+    return;
+  }
+
+  ipc::Shmem new_mem;
+  if (!mHost->SharedMemMgr()->MgrAllocShmem(aNewSize, ipc::SharedMemory::TYPE_BASIC, &new_mem) ||
+      !new_mem.get<uint8_t>()) {
+    return;
+  }
+
+  if (mBuffer.IsReadable()) {
+    memcpy(new_mem.get<uint8_t>(), Buffer(), mSize);
+  }
+
+  DestroyBuffer();
+
+  mBuffer = new_mem;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::AllocatedSize()
+{
+  if (mBuffer.IsWritable()) {
+    return mBuffer.Size<uint8_t>();
+  }
+  return 0;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetSize(uint32_t aSize)
+{
+  mSize = aSize;
+}
+
+uint32_t
+GMPVideoEncodedFrameImpl::Size()
+{
+  return mSize;
+}
+
+void
+GMPVideoEncodedFrameImpl::SetCompleteFrame(bool aCompleteFrame)
+{
+  mCompleteFrame = aCompleteFrame;
+}
+
+bool
+GMPVideoEncodedFrameImpl::CompleteFrame()
+{
+  return mCompleteFrame;
+}
+
+const uint8_t*
+GMPVideoEncodedFrameImpl::Buffer() const
+{
+  return mBuffer.get<uint8_t>();
+}
+
+uint8_t*
+GMPVideoEncodedFrameImpl::Buffer()
+{
+  return mBuffer.get<uint8_t>();
+}
+
+void
+GMPVideoEncodedFrameImpl::Destroy()
+{
+  delete this;
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoEncodedFrameImpl.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMPVideoEncodedFrameImpl_h_
+#define GMPVideoEncodedFrameImpl_h_
+
+#include "gmp-video-errors.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-frame-encoded.h"
+#include "mozilla/ipc/Shmem.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPVideoHostImpl;
+class GMPVideoEncodedFrameData;
+
+class GMPVideoEncodedFrameImpl: public GMPVideoEncodedFrame
+{
+  friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoEncodedFrameImpl>;
+public:
+  GMPVideoEncodedFrameImpl(GMPVideoHostImpl* aHost);
+  GMPVideoEncodedFrameImpl(const GMPVideoEncodedFrameData& aFrameData, GMPVideoHostImpl* aHost);
+  virtual ~GMPVideoEncodedFrameImpl();
+
+  // This is called during a normal destroy sequence, which is
+  // when a consumer is finished or during XPCOM shutdown.
+  void DoneWithAPI();
+  // Does not attempt to release Shmem, as the Shmem has already been released.
+  void ActorDestroyed();
+
+  bool RelinquishFrameData(GMPVideoEncodedFrameData& aFrameData);
+
+  // GMPVideoFrame
+  virtual GMPVideoFrameFormat GetFrameFormat() MOZ_OVERRIDE;
+  virtual void Destroy() MOZ_OVERRIDE;
+
+  // GMPVideoEncodedFrame
+  virtual GMPVideoErr CreateEmptyFrame(uint32_t aSize) MOZ_OVERRIDE;
+  virtual GMPVideoErr CopyFrame(const GMPVideoEncodedFrame& aFrame) MOZ_OVERRIDE;
+  virtual void     SetEncodedWidth(uint32_t aEncodedWidth) MOZ_OVERRIDE;
+  virtual uint32_t EncodedWidth() MOZ_OVERRIDE;
+  virtual void     SetEncodedHeight(uint32_t aEncodedHeight) MOZ_OVERRIDE;
+  virtual uint32_t EncodedHeight() MOZ_OVERRIDE;
+  virtual void     SetTimeStamp(uint32_t aTimeStamp) MOZ_OVERRIDE;
+  virtual uint32_t TimeStamp() MOZ_OVERRIDE;
+  virtual void     SetCaptureTime(int64_t aCaptureTime) MOZ_OVERRIDE;
+  virtual int64_t  CaptureTime() MOZ_OVERRIDE;
+  virtual void     SetFrameType(GMPVideoFrameType aFrameType) MOZ_OVERRIDE;
+  virtual GMPVideoFrameType FrameType() MOZ_OVERRIDE;
+  virtual void     SetAllocatedSize(uint32_t aNewSize) MOZ_OVERRIDE;
+  virtual uint32_t AllocatedSize() MOZ_OVERRIDE;
+  virtual void     SetSize(uint32_t aSize) MOZ_OVERRIDE;
+  virtual uint32_t Size() MOZ_OVERRIDE;
+  virtual void     SetCompleteFrame(bool aCompleteFrame) MOZ_OVERRIDE;
+  virtual bool     CompleteFrame() MOZ_OVERRIDE;
+  virtual const uint8_t* Buffer() const MOZ_OVERRIDE;
+  virtual uint8_t* Buffer() MOZ_OVERRIDE;
+
+private:
+  void DestroyBuffer();
+
+  uint32_t mEncodedWidth;
+  uint32_t mEncodedHeight;
+  uint32_t mTimeStamp;
+  int64_t  mCaptureTime_ms;
+  GMPVideoFrameType mFrameType;
+  uint32_t mSize;
+  bool     mCompleteFrame;
+  GMPVideoHostImpl* mHost;
+  ipc::Shmem mBuffer;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoEncodedFrameImpl_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoEncoderChild.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPVideoEncoderChild.h"
+#include "GMPChild.h"
+#include <stdio.h>
+#include "mozilla/unused.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "GMPVideoi420FrameImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoEncoderChild::GMPVideoEncoderChild(GMPChild* aPlugin)
+: mPlugin(aPlugin),
+  mVideoEncoder(nullptr),
+  mVideoHost(MOZ_THIS_IN_INITIALIZER_LIST())
+{
+  MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoEncoderChild::~GMPVideoEncoderChild()
+{
+}
+
+void
+GMPVideoEncoderChild::Init(GMPVideoEncoder* aEncoder)
+{
+  MOZ_ASSERT(aEncoder, "Cannot initialize video encoder child without a video encoder!");
+  mVideoEncoder = aEncoder;
+}
+
+GMPVideoHostImpl&
+GMPVideoEncoderChild::Host()
+{
+  return mVideoHost;
+}
+
+void
+GMPVideoEncoderChild::Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+                              const GMPCodecSpecificInfo& aCodecSpecificInfo)
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  auto ef = static_cast<GMPVideoEncodedFrameImpl*>(aEncodedFrame);
+
+  GMPVideoEncodedFrameData frameData;
+  ef->RelinquishFrameData(frameData);
+
+  SendEncoded(frameData, aCodecSpecificInfo);
+
+  aEncodedFrame->Destroy();
+}
+
+bool
+GMPVideoEncoderChild::MgrAllocShmem(size_t aSize,
+                                    ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                                    ipc::Shmem* aMem)
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  return AllocShmem(aSize, aType, aMem);
+}
+
+bool
+GMPVideoEncoderChild::MgrDeallocShmem(Shmem& aMem)
+{
+  MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
+
+  return DeallocShmem(aMem);
+}
+
+bool
+GMPVideoEncoderChild::RecvInitEncode(const GMPVideoCodec& aCodecSettings,
+                                     const int32_t& aNumberOfCores,
+                                     const uint32_t& aMaxPayloadSize)
+{
+  if (!mVideoEncoder) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoEncoder->InitEncode(aCodecSettings, this, aNumberOfCores, aMaxPayloadSize);
+
+  return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvEncode(const GMPVideoi420FrameData& aInputFrame,
+                                 const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                                 const InfallibleTArray<int>& aFrameTypes)
+{
+  if (!mVideoEncoder) {
+    return false;
+  }
+
+  auto f = new GMPVideoi420FrameImpl(aInputFrame, &mVideoHost);
+
+  std::vector<GMPVideoFrameType> frameTypes(aFrameTypes.Length());
+  for (uint32_t i = 0; i < aFrameTypes.Length(); i++) {
+    frameTypes[i] = static_cast<GMPVideoFrameType>(aFrameTypes[i]);
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoEncoder->Encode(f, aCodecSpecificInfo, frameTypes);
+
+  return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetChannelParameters(const uint32_t& aPacketLoss,
+                                               const uint32_t& aRTT)
+{
+  if (!mVideoEncoder) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoEncoder->SetChannelParameters(aPacketLoss, aRTT);
+
+  return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetRates(const uint32_t& aNewBitRate,
+                                   const uint32_t& aFrameRate)
+{
+  if (!mVideoEncoder) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoEncoder->SetRates(aNewBitRate, aFrameRate);
+
+  return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvSetPeriodicKeyFrames(const bool& aEnable)
+{
+  if (!mVideoEncoder) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoEncoder->SetPeriodicKeyFrames(aEnable);
+
+  return true;
+}
+
+bool
+GMPVideoEncoderChild::RecvEncodingComplete()
+{
+  if (!mVideoEncoder) {
+    return false;
+  }
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mVideoEncoder->EncodingComplete();
+
+  mVideoHost.DoneWithAPI();
+
+  mPlugin = nullptr;
+
+  unused << Send__delete__(this);
+
+  return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoEncoderChild.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoEncoderChild_h_
+#define GMPVideoEncoderChild_h_
+
+#include "nsString.h"
+#include "mozilla/gmp/PGMPVideoEncoderChild.h"
+#include "gmp-video-encode.h"
+#include "GMPSharedMemManager.h"
+#include "GMPVideoHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPChild;
+
+class GMPVideoEncoderChild : public PGMPVideoEncoderChild,
+                             public GMPEncoderCallback,
+                             public GMPSharedMemManager
+{
+public:
+  GMPVideoEncoderChild(GMPChild* aPlugin);
+  virtual ~GMPVideoEncoderChild();
+
+  void Init(GMPVideoEncoder* aEncoder);
+  GMPVideoHostImpl& Host();
+
+  // GMPEncoderCallback
+  virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+                       const GMPCodecSpecificInfo& aCodecSpecificInfo) MOZ_OVERRIDE;
+
+  // GMPSharedMemManager
+  virtual bool MgrAllocShmem(size_t aSize,
+                             ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                             ipc::Shmem* aMem) MOZ_OVERRIDE;
+  virtual bool MgrDeallocShmem(Shmem& aMem) MOZ_OVERRIDE;
+
+private:
+  // PGMPVideoEncoderChild
+  virtual bool RecvInitEncode(const GMPVideoCodec& aCodecSettings,
+                              const int32_t& aNumberOfCores,
+                              const uint32_t& aMaxPayloadSize) MOZ_OVERRIDE;
+  virtual bool RecvEncode(const GMPVideoi420FrameData& aInputFrame,
+                          const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                          const InfallibleTArray<int>& aFrameTypes) MOZ_OVERRIDE;
+  virtual bool RecvSetChannelParameters(const uint32_t& aPacketLoss,
+                                        const uint32_t& aRTT) MOZ_OVERRIDE;
+  virtual bool RecvSetRates(const uint32_t& aNewBitRate,
+                            const uint32_t& aFrameRate) MOZ_OVERRIDE;
+  virtual bool RecvSetPeriodicKeyFrames(const bool& aEnable) MOZ_OVERRIDE;
+  virtual bool RecvEncodingComplete() MOZ_OVERRIDE;
+
+  GMPChild* mPlugin;
+  GMPVideoEncoder* mVideoEncoder;
+  GMPVideoHostImpl mVideoHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoEncoderChild_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoEncoderParent.cpp
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPVideoEncoderParent.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include <stdio.h>
+#include "mozilla/unused.h"
+#include "GMPMessageUtils.h"
+#include "nsAutoRef.h"
+#include "GMPParent.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "nsThreadUtils.h"
+
+template <>
+class nsAutoRefTraits<GMPVideoi420Frame> : public nsPointerRefTraits<GMPVideoi420Frame>
+{
+public:
+  static void Release(GMPVideoi420Frame* aFrame) { aFrame->Destroy(); }
+};
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoEncoderParent::GMPVideoEncoderParent(GMPParent *aPlugin)
+: mCanSendMessages(true),
+  mPlugin(aPlugin),
+  mCallback(nullptr),
+  mVideoHost(MOZ_THIS_IN_INITIALIZER_LIST())
+{
+  MOZ_ASSERT(mPlugin);
+}
+
+GMPVideoEncoderParent::~GMPVideoEncoderParent()
+{
+}
+
+GMPVideoHostImpl&
+GMPVideoEncoderParent::Host()
+{
+  return mVideoHost;
+}
+
+bool
+GMPVideoEncoderParent::MgrAllocShmem(size_t aSize,
+                                     ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                                     ipc::Shmem* aMem)
+{
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  return AllocShmem(aSize, aType, aMem);
+}
+
+bool
+GMPVideoEncoderParent::MgrDeallocShmem(Shmem& aMem)
+{
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  return DeallocShmem(aMem);
+}
+
+GMPVideoErr
+GMPVideoEncoderParent::InitEncode(const GMPVideoCodec& aCodecSettings,
+                                  GMPEncoderCallback* aCallback,
+                                  int32_t aNumberOfCores,
+                                  uint32_t aMaxPayloadSize)
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video encoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  if (!aCallback) {
+    return GMPVideoGenericErr;
+  }
+  mCallback = aCallback;
+
+  if (!SendInitEncode(aCodecSettings, aNumberOfCores, aMaxPayloadSize)) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoEncoderParent::Encode(GMPVideoi420Frame* aInputFrame,
+                              const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                              const std::vector<GMPVideoFrameType>& aFrameTypes)
+{
+  nsAutoRef<GMPVideoi420Frame> frameRef(aInputFrame);
+
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video encoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  auto inputFrameImpl = static_cast<GMPVideoi420FrameImpl*>(aInputFrame);
+
+  GMPVideoi420FrameData frameData;
+  inputFrameImpl->InitFrameData(frameData);
+
+  InfallibleTArray<int> frameTypes;
+  frameTypes.SetCapacity(aFrameTypes.size());
+  for (std::vector<int>::size_type i = 0; i != aFrameTypes.size(); i++) {
+    frameTypes.AppendElement(static_cast<int>(aFrameTypes[i]));
+  }
+
+  if (!SendEncode(frameData,
+                  aCodecSpecificInfo,
+                  frameTypes)) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoEncoderParent::SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT)
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video encoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  if (!SendSetChannelParameters(aPacketLoss, aRTT)) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoEncoderParent::SetRates(uint32_t aNewBitRate, uint32_t aFrameRate)
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video encoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  if (!SendSetRates(aNewBitRate, aFrameRate)) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoEncoderParent::SetPeriodicKeyFrames(bool aEnable)
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video encoder!");
+    return GMPVideoGenericErr;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  if (!SendSetPeriodicKeyFrames(aEnable)) {
+    return GMPVideoGenericErr;
+  }
+
+  // Async IPC, we don't have access to a return value.
+  return GMPVideoNoErr;
+}
+
+// Note: Consider keeping ActorDestroy sync'd up when making changes here.
+void
+GMPVideoEncoderParent::EncodingComplete()
+{
+  if (!mCanSendMessages) {
+    NS_WARNING("Trying to use an invalid GMP video encoder!");
+    return;
+  }
+
+  MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
+
+  mCanSendMessages = false;
+
+  mCallback = nullptr;
+
+  mVideoHost.DoneWithAPI();
+
+  unused << SendEncodingComplete();
+}
+
+// Note: Keep this sync'd up with DecodingComplete
+void
+GMPVideoEncoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  if (mPlugin) {
+    // Ignore any return code. It is OK for this to fail without killing the process.
+    mPlugin->VideoEncoderDestroyed(this);
+    mPlugin = nullptr;
+  }
+  mCanSendMessages = false;
+  mCallback = nullptr;
+  mVideoHost.ActorDestroyed();
+}
+
+bool
+GMPVideoEncoderParent::RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame,
+                                   const GMPCodecSpecificInfo& aCodecSpecificInfo)
+{
+  if (!mCallback) {
+    return false;
+  }
+
+  auto f = new GMPVideoEncodedFrameImpl(aEncodedFrame, &mVideoHost);
+
+  // Ignore any return code. It is OK for this to fail without killing the process.
+  mCallback->Encoded(f, aCodecSpecificInfo);
+
+  return true;
+}
+
+bool
+GMPVideoEncoderParent::Recv__delete__()
+{
+  if (mPlugin) {
+    // Ignore any return code. It is OK for this to fail without killing the process.
+    mPlugin->VideoEncoderDestroyed(this);
+    mPlugin = nullptr;
+  }
+
+  return true;
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoEncoderParent.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoEncoderParent_h_
+#define GMPVideoEncoderParent_h_
+
+#include "mozilla/RefPtr.h"
+#include "gmp-video-encode.h"
+#include "mozilla/gmp/PGMPVideoEncoderParent.h"
+#include "GMPMessageUtils.h"
+#include "GMPSharedMemManager.h"
+#include "GMPVideoHost.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPParent;
+
+class GMPVideoEncoderParent : public GMPVideoEncoder,
+                              public PGMPVideoEncoderParent,
+                              public GMPSharedMemManager
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(GMPVideoEncoderParent)
+
+  GMPVideoEncoderParent(GMPParent *aPlugin);
+
+  GMPVideoHostImpl& Host();
+
+  // GMPSharedMemManager
+  virtual bool MgrAllocShmem(size_t aSize,
+                             ipc::Shmem::SharedMemory::SharedMemoryType aType,
+                             ipc::Shmem* aMem) MOZ_OVERRIDE;
+  virtual bool MgrDeallocShmem(Shmem& aMem) MOZ_OVERRIDE;
+
+  // GMPVideoEncoder
+  virtual GMPVideoErr InitEncode(const GMPVideoCodec& aCodecSettings,
+                                 GMPEncoderCallback* aCallback,
+                                 int32_t aNumberOfCores,
+                                 uint32_t aMaxPayloadSize) MOZ_OVERRIDE;
+  virtual GMPVideoErr Encode(GMPVideoi420Frame* aInputFrame,
+                             const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                             const std::vector<GMPVideoFrameType>& aFrameTypes) MOZ_OVERRIDE;
+  virtual GMPVideoErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) MOZ_OVERRIDE;
+  virtual GMPVideoErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) MOZ_OVERRIDE;
+  virtual GMPVideoErr SetPeriodicKeyFrames(bool aEnable) MOZ_OVERRIDE;
+  virtual void EncodingComplete() MOZ_OVERRIDE;
+
+private:
+  virtual ~GMPVideoEncoderParent();
+
+  // PGMPVideoEncoderParent
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+  virtual bool RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame,
+                           const GMPCodecSpecificInfo& aCodecSpecificInfo) MOZ_OVERRIDE;
+  virtual bool Recv__delete__() MOZ_OVERRIDE;
+
+  bool mCanSendMessages;
+  GMPParent* mPlugin;
+  GMPEncoderCallback* mCallback;
+  GMPVideoHostImpl mVideoHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoEncoderParent_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoHost.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPVideoHost.h"
+#include "mozilla/Assertions.h"
+#include "GMPVideoi420FrameImpl.h"
+#include "GMPVideoEncodedFrameImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoHostImpl::GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr)
+: mSharedMemMgr(aSharedMemMgr)
+{
+}
+
+GMPVideoHostImpl::~GMPVideoHostImpl()
+{
+}
+
+GMPVideoErr
+GMPVideoHostImpl::CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame)
+{
+  if (!mSharedMemMgr) {
+    return GMPVideoGenericErr;
+  }
+
+  if (!aFrame) {
+    return GMPVideoGenericErr;
+  }
+  *aFrame = nullptr;
+
+  switch (aFormat) {
+    case kGMPI420VideoFrame:
+      *aFrame = new GMPVideoi420FrameImpl(this);
+      return GMPVideoNoErr;
+    case kGMPEncodedVideoFrame:
+      *aFrame = new GMPVideoEncodedFrameImpl(this);
+      return GMPVideoNoErr;
+    default:
+      NS_NOTREACHED("Unknown frame format!");
+  }
+
+  return GMPVideoGenericErr;
+}
+
+GMPVideoErr
+GMPVideoHostImpl::CreatePlane(GMPPlane** aPlane)
+{
+  if (!mSharedMemMgr) {
+    return GMPVideoGenericErr;
+  }
+
+  if (!aPlane) {
+    return GMPVideoGenericErr;
+  }
+  *aPlane = nullptr;
+
+  auto p = new GMPPlaneImpl(this);
+
+  *aPlane = p;
+
+  return GMPVideoNoErr;
+}
+
+GMPSharedMemManager*
+GMPVideoHostImpl::SharedMemMgr()
+{
+  return mSharedMemMgr;
+}
+
+void
+GMPVideoHostImpl::DoneWithAPI()
+{
+  for (uint32_t i = mPlanes.Length(); i > 0; i--) {
+    mPlanes[i - 1]->DoneWithAPI();
+    mPlanes.RemoveElementAt(i - 1);
+  }
+  for (uint32_t i = mEncodedFrames.Length(); i > 0; i--) {
+    mEncodedFrames[i - 1]->DoneWithAPI();
+    mEncodedFrames.RemoveElementAt(i - 1);
+  }
+  mSharedMemMgr = nullptr;
+}
+
+void
+GMPVideoHostImpl::ActorDestroyed()
+{
+  for (uint32_t i = mPlanes.Length(); i > 0; i--) {
+    mPlanes[i - 1]->ActorDestroyed();
+    mPlanes.RemoveElementAt(i - 1);
+  }
+  for (uint32_t i = mEncodedFrames.Length(); i > 0; i--) {
+    mEncodedFrames[i - 1]->ActorDestroyed();
+    mEncodedFrames.RemoveElementAt(i - 1);
+  }
+  mSharedMemMgr = nullptr;  
+}
+
+void
+GMPVideoHostImpl::PlaneCreated(GMPPlaneImpl* aPlane)
+{
+  mPlanes.AppendElement(aPlane);
+}
+
+void
+GMPVideoHostImpl::PlaneDestroyed(GMPPlaneImpl* aPlane)
+{
+  MOZ_ALWAYS_TRUE(mPlanes.RemoveElement(aPlane));
+}
+
+void
+GMPVideoHostImpl::EncodedFrameCreated(GMPVideoEncodedFrameImpl* aEncodedFrame)
+{
+  mEncodedFrames.AppendElement(aEncodedFrame);
+}
+
+void
+GMPVideoHostImpl::EncodedFrameDestroyed(GMPVideoEncodedFrameImpl* aFrame)
+{
+  MOZ_ALWAYS_TRUE(mEncodedFrames.RemoveElement(aFrame));
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoHost.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoHost_h_
+#define GMPVideoHost_h_
+
+#include "gmp-video-host.h"
+#include "gmp-video-plane.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-host.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPSharedMemManager;
+class GMPPlaneImpl;
+class GMPVideoEncodedFrameImpl;
+
+class GMPVideoHostImpl : public GMPVideoHost
+{
+public:
+  GMPVideoHostImpl(GMPSharedMemManager* aSharedMemMgr);
+  virtual ~GMPVideoHostImpl();
+
+  // Used for shared memory allocation and deallocation.
+  GMPSharedMemManager* SharedMemMgr();
+  void DoneWithAPI();
+  void ActorDestroyed();
+  void PlaneCreated(GMPPlaneImpl* aPlane);
+  void PlaneDestroyed(GMPPlaneImpl* aPlane);
+  void EncodedFrameCreated(GMPVideoEncodedFrameImpl* aEncodedFrame);
+  void EncodedFrameDestroyed(GMPVideoEncodedFrameImpl* aFrame);
+
+  // GMPVideoHost
+  virtual GMPVideoErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) MOZ_OVERRIDE;
+  virtual GMPVideoErr CreatePlane(GMPPlane** aPlane) MOZ_OVERRIDE;
+
+private:
+  // All shared memory allocations have to be made by an IPDL actor.
+  // This is a reference to the owning actor. If this reference is
+  // null then the actor has died and all allocations must fail.
+  GMPSharedMemManager* mSharedMemMgr;
+
+  // We track all of these things because they need to handle further
+  // allocations through us and we need to notify them when they
+  // can't use us any more.
+  nsTArray<GMPPlaneImpl*> mPlanes;
+  nsTArray<GMPVideoEncodedFrameImpl*> mEncodedFrames;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoHost_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoPlaneImpl.cpp
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPVideoPlaneImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPVideoHost.h"
+#include "GMPSharedMemManager.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPPlaneImpl::GMPPlaneImpl(GMPVideoHostImpl* aHost)
+: mSize(0),
+  mStride(0),
+  mHost(aHost)
+{
+  MOZ_ASSERT(mHost);
+  mHost->PlaneCreated(this);
+}
+
+GMPPlaneImpl::GMPPlaneImpl(const GMPPlaneData& aPlaneData, GMPVideoHostImpl* aHost)
+: mBuffer(aPlaneData.mBuffer()),
+  mSize(aPlaneData.mSize()),
+  mStride(aPlaneData.mStride()),
+  mHost(aHost)
+{
+  MOZ_ASSERT(mHost);
+  mHost->PlaneCreated(this);
+}
+
+GMPPlaneImpl::~GMPPlaneImpl()
+{
+  DestroyBuffer();
+  if (mHost) {
+    mHost->PlaneDestroyed(this);
+  }
+}
+
+void
+GMPPlaneImpl::DoneWithAPI()
+{
+  DestroyBuffer();
+
+  // Do this after destroying the buffer because destruction
+  // involves deallocation, which requires a host.
+  mHost = nullptr;
+}
+
+void
+GMPPlaneImpl::ActorDestroyed()
+{
+  // Simply clear out Shmem reference, do not attempt to
+  // properly free it. It has already been freed.
+  mBuffer = ipc::Shmem();
+  // No more host.
+  mHost = nullptr;
+}
+
+bool
+GMPPlaneImpl::InitPlaneData(GMPPlaneData& aPlaneData)
+{
+  aPlaneData.mBuffer() = mBuffer;
+  aPlaneData.mSize() = mSize;
+  aPlaneData.mStride() = mStride;
+
+  // This method is called right before Shmem is sent to another process.
+  // We need to effectively zero out our member copy so that we don't
+  // try to delete memory we don't own later.
+  mBuffer = ipc::Shmem();
+
+  return true;
+}
+
+GMPVideoErr
+GMPPlaneImpl::MaybeResize(int32_t aNewSize) {
+  if (aNewSize <= AllocatedSize()) {
+    return GMPVideoNoErr;
+  }
+
+  if (!mHost) {
+    return GMPVideoGenericErr;
+  }
+
+  ipc::Shmem new_mem;
+  if (!mHost->SharedMemMgr()->MgrAllocShmem(aNewSize, ipc::SharedMemory::TYPE_BASIC, &new_mem) ||
+      !new_mem.get<uint8_t>()) {
+    return GMPVideoAllocErr;
+  }
+
+  if (mBuffer.IsReadable()) {
+    memcpy(new_mem.get<uint8_t>(), Buffer(), mSize);
+  }
+
+  DestroyBuffer();
+
+  mBuffer = new_mem;
+
+  return GMPVideoNoErr;
+}
+
+void
+GMPPlaneImpl::DestroyBuffer()
+{
+  if (mHost && mBuffer.IsWritable()) {
+    mHost->SharedMemMgr()->MgrDeallocShmem(mBuffer);
+  }
+  mBuffer = ipc::Shmem();
+}
+
+GMPVideoErr
+GMPPlaneImpl::CreateEmptyPlane(int32_t aAllocatedSize, int32_t aStride, int32_t aPlaneSize)
+{
+  if (aAllocatedSize < 1 || aStride < 1 || aPlaneSize < 1) {
+    return GMPVideoGenericErr;
+  }
+
+  GMPVideoErr err = MaybeResize(aAllocatedSize);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  mSize = aPlaneSize;
+  mStride = aStride;
+
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPPlaneImpl::Copy(const GMPPlane& aPlane)
+{
+  auto& planeimpl = static_cast<const GMPPlaneImpl&>(aPlane);
+
+  GMPVideoErr err = MaybeResize(planeimpl.mSize);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  if (planeimpl.Buffer() && planeimpl.mSize > 0) {
+    memcpy(Buffer(), planeimpl.Buffer(), mSize);
+  }
+
+  mSize = planeimpl.mSize;
+  mStride = planeimpl.mStride;
+
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPPlaneImpl::Copy(int32_t aSize, int32_t aStride, const uint8_t* aBuffer)
+{
+  GMPVideoErr err = MaybeResize(aSize);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  if (aBuffer && aSize > 0) {
+    memcpy(Buffer(), aBuffer, aSize);
+  }
+
+  mSize = aSize;
+  mStride = aStride;
+
+  return GMPVideoNoErr;
+}
+
+void
+GMPPlaneImpl::Swap(GMPPlane& aPlane)
+{
+  auto& planeimpl = static_cast<GMPPlaneImpl&>(aPlane);
+
+  std::swap(mStride, planeimpl.mStride);
+  std::swap(mSize, planeimpl.mSize);
+  std::swap(mBuffer, planeimpl.mBuffer);
+}
+
+int32_t
+GMPPlaneImpl::AllocatedSize() const
+{
+  if (mBuffer.IsWritable()) {
+    return mBuffer.Size<uint8_t>();
+  }
+  return 0;
+}
+
+void
+GMPPlaneImpl::ResetSize()
+{
+  mSize = 0;
+}
+
+bool
+GMPPlaneImpl::IsZeroSize() const
+{
+  return (mSize == 0);
+}
+
+int32_t
+GMPPlaneImpl::Stride() const
+{
+  return mStride;
+}
+
+const uint8_t*
+GMPPlaneImpl::Buffer() const
+{
+  return mBuffer.get<uint8_t>();
+}
+
+uint8_t*
+GMPPlaneImpl::Buffer()
+{
+  return mBuffer.get<uint8_t>();
+}
+
+void
+GMPPlaneImpl::Destroy()
+{
+  delete this;
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoPlaneImpl.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoPlaneImpl_h_
+#define GMPVideoPlaneImpl_h_
+
+#include "gmp-video-plane.h"
+#include "mozilla/ipc/Shmem.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPVideoHostImpl;
+class GMPPlaneData;
+
+class GMPPlaneImpl : public GMPPlane
+{
+  friend struct IPC::ParamTraits<mozilla::gmp::GMPPlaneImpl>;
+public:
+  GMPPlaneImpl(GMPVideoHostImpl* aHost);
+  GMPPlaneImpl(const GMPPlaneData& aPlaneData, GMPVideoHostImpl* aHost);
+  virtual ~GMPPlaneImpl();
+
+  // This is called during a normal destroy sequence, which is
+  // when a consumer is finished or during XPCOM shutdown.
+  void DoneWithAPI();
+  // This is called when something has gone wrong - specicifically,
+  // a child process has crashed. Does not attempt to release Shmem,
+  // as the Shmem has already been released.
+  void ActorDestroyed();
+
+  bool InitPlaneData(GMPPlaneData& aPlaneData);
+
+  // GMPPlane
+  virtual GMPVideoErr CreateEmptyPlane(int32_t aAllocatedSize,
+                                       int32_t aStride,
+                                       int32_t aPlaneSize) MOZ_OVERRIDE;
+  virtual GMPVideoErr Copy(const GMPPlane& aPlane) MOZ_OVERRIDE;
+  virtual GMPVideoErr Copy(int32_t aSize,
+                           int32_t aStride,
+                           const uint8_t* aBuffer) MOZ_OVERRIDE;
+  virtual void Swap(GMPPlane& aPlane) MOZ_OVERRIDE;
+  virtual int32_t AllocatedSize() const MOZ_OVERRIDE;
+  virtual void ResetSize() MOZ_OVERRIDE;
+  virtual bool IsZeroSize() const MOZ_OVERRIDE;
+  virtual int32_t Stride() const MOZ_OVERRIDE;
+  virtual const uint8_t* Buffer() const MOZ_OVERRIDE;
+  virtual uint8_t* Buffer() MOZ_OVERRIDE;
+  virtual void Destroy() MOZ_OVERRIDE;
+
+private:
+  GMPVideoErr MaybeResize(int32_t aNewSize);
+  void DestroyBuffer();
+
+  ipc::Shmem mBuffer;
+  int32_t mSize;
+  int32_t mStride;
+  GMPVideoHostImpl* mHost;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoPlaneImpl_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoi420FrameImpl.cpp
@@ -0,0 +1,337 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GMPVideoi420FrameImpl.h"
+#include "mozilla/gmp/GMPTypes.h"
+
+namespace mozilla {
+namespace gmp {
+
+GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost)
+: mYPlane(aHost),
+  mUPlane(aHost),
+  mVPlane(aHost),
+  mWidth(0),
+  mHeight(0),
+  mTimestamp(0),
+  mRenderTime_ms(0)
+{
+  MOZ_ASSERT(aHost);
+}
+
+GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData,
+                                             GMPVideoHostImpl* aHost)
+: mYPlane(aFrameData.mYPlane(), aHost),
+  mUPlane(aFrameData.mUPlane(), aHost),
+  mVPlane(aFrameData.mVPlane(), aHost),
+  mWidth(aFrameData.mWidth()),
+  mHeight(aFrameData.mHeight()),
+  mTimestamp(aFrameData.mTimestamp()),
+  mRenderTime_ms(aFrameData.mRenderTime_ms())
+{
+  MOZ_ASSERT(aHost);
+}
+
+GMPVideoi420FrameImpl::~GMPVideoi420FrameImpl()
+{
+}
+
+bool
+GMPVideoi420FrameImpl::InitFrameData(GMPVideoi420FrameData& aFrameData)
+{
+  mYPlane.InitPlaneData(aFrameData.mYPlane());
+  mUPlane.InitPlaneData(aFrameData.mUPlane());
+  mVPlane.InitPlaneData(aFrameData.mVPlane());
+  aFrameData.mWidth() = mWidth;
+  aFrameData.mHeight() = mHeight;
+  aFrameData.mTimestamp() = mTimestamp;
+  aFrameData.mRenderTime_ms() = mRenderTime_ms;
+  return true;
+}
+
+GMPVideoFrameFormat
+GMPVideoi420FrameImpl::GetFrameFormat()
+{
+  return kGMPI420VideoFrame;
+}
+
+void
+GMPVideoi420FrameImpl::Destroy()
+{
+  delete this;
+}
+
+bool
+GMPVideoi420FrameImpl::CheckDimensions(int32_t aWidth, int32_t aHeight,
+                                       int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+  int32_t half_width = (aWidth + 1) / 2;
+  if (aWidth < 1 || aHeight < 1 || aStride_y < aWidth ||
+                                   aStride_u < half_width ||
+                                   aStride_v < half_width) {
+    return false;
+  }
+  return true;
+}
+
+const GMPPlaneImpl*
+GMPVideoi420FrameImpl::GetPlane(GMPPlaneType aType) const {
+  switch (aType) {
+    case kGMPYPlane:
+      return &mYPlane;
+    case kGMPUPlane:
+      return &mUPlane;
+    case kGMPVPlane:
+      return &mVPlane;
+    default:
+      MOZ_CRASH("Unknown plane type!");
+  }
+  return nullptr;
+}
+
+GMPPlaneImpl*
+GMPVideoi420FrameImpl::GetPlane(GMPPlaneType aType) {
+  switch (aType) {
+    case kGMPYPlane :
+      return &mYPlane;
+    case kGMPUPlane :
+      return &mUPlane;
+    case kGMPVPlane :
+      return &mVPlane;
+    default:
+      MOZ_CRASH("Unknown plane type!");
+  }
+  return nullptr;
+}
+
+GMPVideoErr
+GMPVideoi420FrameImpl::CreateEmptyFrame(int32_t aWidth, int32_t aHeight,
+                                        int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+  if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) {
+    return GMPVideoGenericErr;
+  }
+
+  int32_t size_y = aStride_y * aHeight;
+  int32_t half_height = (aHeight + 1) / 2;
+  int32_t size_u = aStride_u * half_height;
+  int32_t size_v = aStride_v * half_height;
+
+  GMPVideoErr err = mYPlane.CreateEmptyPlane(size_y, aStride_y, size_y);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+  err = mUPlane.CreateEmptyPlane(size_u, aStride_u, size_u);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+  err = mVPlane.CreateEmptyPlane(size_v, aStride_v, size_v);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  mWidth = aWidth;
+  mHeight = aHeight;
+  mTimestamp = 0;
+  mRenderTime_ms = 0;
+
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoi420FrameImpl::CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+                                   int32_t aSize_u, const uint8_t* aBuffer_u,
+                                   int32_t aSize_v, const uint8_t* aBuffer_v,
+                                   int32_t aWidth, int32_t aHeight,
+                                   int32_t aStride_y, int32_t aStride_u, int32_t aStride_v)
+{
+  MOZ_ASSERT(aBuffer_y);
+  MOZ_ASSERT(aBuffer_u);
+  MOZ_ASSERT(aBuffer_v);
+
+  if (aSize_y < 1 || aSize_u < 1 || aSize_v < 1) {
+    return GMPVideoGenericErr;
+  }
+
+  if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) {
+    return GMPVideoGenericErr;
+  }
+
+  GMPVideoErr err = mYPlane.Copy(aSize_y, aStride_y, aBuffer_y);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+  err = mUPlane.Copy(aSize_u, aStride_u, aBuffer_u);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+  err = mVPlane.Copy(aSize_v, aStride_v, aBuffer_v);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  mWidth = aWidth;
+  mHeight = aHeight;
+
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoi420FrameImpl::CopyFrame(const GMPVideoi420Frame& aFrame)
+{
+  auto& f = static_cast<const GMPVideoi420FrameImpl&>(aFrame);
+
+  GMPVideoErr err = mYPlane.Copy(f.mYPlane);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  err = mUPlane.Copy(f.mUPlane);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  err = mVPlane.Copy(f.mVPlane);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  mWidth = f.mWidth;
+  mHeight = f.mHeight;
+  mTimestamp = f.mTimestamp;
+  mRenderTime_ms = f.mRenderTime_ms;
+
+  return GMPVideoNoErr;
+}
+
+void
+GMPVideoi420FrameImpl::SwapFrame(GMPVideoi420Frame* aFrame)
+{
+  auto f = static_cast<GMPVideoi420FrameImpl*>(aFrame);
+  mYPlane.Swap(f->mYPlane);
+  mUPlane.Swap(f->mUPlane);
+  mVPlane.Swap(f->mVPlane);
+  std::swap(mWidth, f->mWidth);
+  std::swap(mHeight, f->mHeight);
+  std::swap(mTimestamp, f->mTimestamp);
+  std::swap(mRenderTime_ms, f->mRenderTime_ms);
+}
+
+uint8_t*
+GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType)
+{
+  GMPPlane* p = GetPlane(aType);
+  if (p) {
+    return p->Buffer();
+  }
+  return nullptr;
+}
+
+const uint8_t*
+GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType) const
+{
+ const GMPPlane* p = GetPlane(aType);
+  if (p) {
+    return p->Buffer();
+  }
+  return nullptr;
+}
+
+int32_t
+GMPVideoi420FrameImpl::AllocatedSize(GMPPlaneType aType) const
+{
+  const GMPPlane* p = GetPlane(aType);
+  if (p) {
+    return p->AllocatedSize();
+  }
+  return -1;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Stride(GMPPlaneType aType) const
+{
+  const GMPPlane* p = GetPlane(aType);
+  if (p) {
+    return p->Stride();
+  }
+  return -1;
+}
+
+GMPVideoErr
+GMPVideoi420FrameImpl::SetWidth(int32_t aWidth)
+{
+  if (!CheckDimensions(aWidth, mHeight,
+                       mYPlane.Stride(), mUPlane.Stride(),
+                       mVPlane.Stride())) {
+    return GMPVideoGenericErr;
+  }
+  mWidth = aWidth;
+  return GMPVideoNoErr;
+}
+
+GMPVideoErr
+GMPVideoi420FrameImpl::SetHeight(int32_t aHeight)
+{
+  if (!CheckDimensions(mWidth, aHeight,
+                       mYPlane.Stride(), mUPlane.Stride(),
+                       mVPlane.Stride())) {
+    return GMPVideoGenericErr;
+  }
+  mHeight = aHeight;
+  return GMPVideoNoErr;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Width() const
+{
+  return mWidth;
+}
+
+int32_t
+GMPVideoi420FrameImpl::Height() const
+{
+  return mHeight;
+}
+
+void
+GMPVideoi420FrameImpl::SetTimestamp(uint32_t aTimestamp)
+{
+  mTimestamp = aTimestamp;
+}
+
+uint32_t
+GMPVideoi420FrameImpl::Timestamp() const
+{
+  return mTimestamp;
+}
+
+void
+GMPVideoi420FrameImpl::SetRenderTime_ms(int64_t aRenderTime_ms)
+{
+  mRenderTime_ms = aRenderTime_ms;
+}
+
+int64_t
+GMPVideoi420FrameImpl::RenderTime_ms() const
+{
+  return mRenderTime_ms;
+}
+
+bool
+GMPVideoi420FrameImpl::IsZeroSize() const
+{
+  return (mYPlane.IsZeroSize() && mUPlane.IsZeroSize() && mVPlane.IsZeroSize());
+}
+
+void
+GMPVideoi420FrameImpl::ResetSize()
+{
+  mYPlane.ResetSize();
+  mUPlane.ResetSize();
+  mVPlane.ResetSize();
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/GMPVideoi420FrameImpl.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVideoi420FrameImpl_h_
+#define GMPVideoi420FrameImpl_h_
+
+#include "gmp-video-frame-i420.h"
+#include "mozilla/ipc/Shmem.h"
+#include "GMPVideoPlaneImpl.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPVideoi420FrameData;
+
+class GMPVideoi420FrameImpl : public GMPVideoi420Frame
+{
+  friend struct IPC::ParamTraits<mozilla::gmp::GMPVideoi420FrameImpl>;
+public:
+  GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost);
+  GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData, GMPVideoHostImpl* aHost);
+  virtual ~GMPVideoi420FrameImpl();
+
+  bool InitFrameData(GMPVideoi420FrameData& aFrameData);
+  const GMPPlaneImpl* GetPlane(GMPPlaneType aType) const;
+  GMPPlaneImpl* GetPlane(GMPPlaneType aType);
+
+  // GMPVideoFrame
+  virtual GMPVideoFrameFormat GetFrameFormat() MOZ_OVERRIDE;
+  virtual void Destroy() MOZ_OVERRIDE;
+
+  // GMPVideoi420Frame
+  virtual GMPVideoErr CreateEmptyFrame(int32_t aWidth,
+                                       int32_t aHeight,
+                                       int32_t aStride_y,
+                                       int32_t aStride_u,
+                                       int32_t aStride_v) MOZ_OVERRIDE;
+  virtual GMPVideoErr CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+                                  int32_t aSize_u, const uint8_t* aBuffer_u,
+                                  int32_t aSize_v, const uint8_t* aBuffer_v,
+                                  int32_t aWidth,
+                                  int32_t aHeight,
+                                  int32_t aStride_y,
+                                  int32_t aStride_u,
+                                  int32_t aStride_v) MOZ_OVERRIDE;
+  virtual GMPVideoErr CopyFrame(const GMPVideoi420Frame& aFrame) MOZ_OVERRIDE;
+  virtual void SwapFrame(GMPVideoi420Frame* aFrame) MOZ_OVERRIDE;
+  virtual uint8_t* Buffer(GMPPlaneType aType) MOZ_OVERRIDE;
+  virtual const uint8_t* Buffer(GMPPlaneType aType) const MOZ_OVERRIDE;
+  virtual int32_t AllocatedSize(GMPPlaneType aType) const MOZ_OVERRIDE;
+  virtual int32_t Stride(GMPPlaneType aType) const MOZ_OVERRIDE;
+  virtual GMPVideoErr SetWidth(int32_t aWidth) MOZ_OVERRIDE;
+  virtual GMPVideoErr SetHeight(int32_t aHeight) MOZ_OVERRIDE;
+  virtual int32_t Width() const MOZ_OVERRIDE;
+  virtual int32_t Height() const MOZ_OVERRIDE;
+  virtual void SetTimestamp(uint32_t aTimestamp) MOZ_OVERRIDE;
+  virtual uint32_t Timestamp() const MOZ_OVERRIDE;
+  virtual void SetRenderTime_ms(int64_t aRenderTime_ms) MOZ_OVERRIDE;
+  virtual int64_t RenderTime_ms() const MOZ_OVERRIDE;
+  virtual bool IsZeroSize() const MOZ_OVERRIDE;
+  virtual void ResetSize() MOZ_OVERRIDE;
+
+private:
+  bool CheckDimensions(int32_t aWidth, int32_t aHeight,
+                       int32_t aStride_y, int32_t aStride_u, int32_t aStride_v);
+
+  GMPPlaneImpl mYPlane;
+  GMPPlaneImpl mUPlane;
+  GMPPlaneImpl mVPlane;
+  int32_t mWidth;
+  int32_t mHeight;
+  uint32_t mTimestamp;
+  int64_t mRenderTime_ms;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMPVideoi420FrameImpl_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/PGMP.ipdl
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; 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 protocol PGMPVideoDecoder;
+include protocol PGMPVideoEncoder;
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMP
+{
+  manages PGMPVideoDecoder;
+  manages PGMPVideoEncoder;
+child:
+  PGMPVideoDecoder();
+  PGMPVideoEncoder();
+};
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/PGMPVideoDecoder.ipdl
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; 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 protocol PGMP;
+include GMPTypes;
+
+using GMPVideoCodec from "gmp-video-codec.h";
+using GMPCodecSpecificInfo from "gmp-video-codec.h";
+
+include "GMPMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPVideoDecoder
+{
+  manager PGMP;
+child:
+  InitDecode(GMPVideoCodec aCodecSettings,
+             int32_t aCoreCount);
+  Decode(GMPVideoEncodedFrameData aInputFrame,
+         bool aMissingFrames,
+         GMPCodecSpecificInfo aCodecSpecificInfo,
+         int64_t aRenderTimeMs);
+  Reset();
+  Drain();
+  DecodingComplete();
+parent:
+  __delete__();
+  Decoded(GMPVideoi420FrameData aDecodedFrame);
+  ReceivedDecodedReferenceFrame(uint64_t aPictureId);
+  ReceivedDecodedFrame(uint64_t aPictureId);
+  InputDataExhausted();
+};
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/PGMPVideoEncoder.ipdl
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; 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 protocol PGMP;
+include GMPTypes;
+
+using GMPVideoCodec from "gmp-video-codec.h";
+using GMPCodecSpecificInfo from "gmp-video-codec.h";
+
+include "GMPMessageUtils.h";
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PGMPVideoEncoder
+{
+  manager PGMP;
+child:
+  InitEncode(GMPVideoCodec aCodecSettings,
+             int32_t aNumberOfCores,
+             uint32_t aMaxPayloadSize);
+  Encode(GMPVideoi420FrameData aInputFrame,
+         GMPCodecSpecificInfo aCodecSpecificInfo,
+         int[] aFrameTypes);
+  SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT);
+  SetRates(uint32_t aNewBitRate, uint32_t aFrameRate);
+  SetPeriodicKeyFrames(bool aEnable);
+  EncodingComplete();
+
+parent:
+  __delete__();
+  Encoded(GMPVideoEncodedFrameData aEncodedFrame,
+          GMPCodecSpecificInfo aCodecSpecificInfo);
+};
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/README.txt
@@ -0,0 +1,1 @@
+This directory contains code supporting Gecko Media Plugins (GMPs). The GMP API is not the same thing as the Media Plugin API (MPAPI).
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-entrypoints.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_ENTRYPOINTS_h_
+#define GMP_ENTRYPOINTS_h_
+
+#include "gmp-errors.h"
+#include "gmp-platform.h"
+
+/* C functions exposed by Gecko Media Plugin shared library. */
+
+// GMPInit
+// - Called once after plugin library is loaded, before GMPGetAPI or GMPShutdown are called.
+// - Called on main thread.
+// - 'aPlatformAPI' is a structure containing platform-provided APIs. It is valid until
+//   'GMPShutdown' is called. Owned and must be deleted by plugin.
+typedef GMPErr (*GMPInitFunc)(const GMPPlatformAPI* aPlatformAPI);
+
+// GMPGetAPI
+// - Called when host wants to use an API.
+// - Called on main thread.
+// - 'aAPIName' is a string indicating the API being requested.
+// - 'aHostAPI' is the host API which is specific to the API being requested
+//   from the plugin. It is valid so long as the API object requested from the
+//   plugin is valid. It is owned by the host, plugin should not attempt to delete.
+//   May be null.
+// - 'aPluginAPI' is for returning the requested API. Destruction of the requsted
+//   API object is defined by the API.
+typedef GMPErr (*GMPGetAPIFunc)(const char* aAPIName, void* aHostAPI, void** aPluginAPI);
+
+// GMPShutdown
+// - Called once before exiting process (unloading library).
+// - Called on main thread.
+typedef void   (*GMPShutdownFunc)(void);
+
+#endif // GMP_ENTRYPOINTS_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-errors.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_ERRORS_h_
+#define GMP_ERRORS_h_
+
+typedef enum {
+  GMPNoErr = 0,
+  GMPGenericErr = 1
+} GMPErr;
+
+#endif // GMP_ERRORS_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-platform.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_PLATFORM_h_
+#define GMP_PLATFORM_h_
+
+#include "gmp-errors.h"
+#include <stdint.h>
+
+/* Platform helper API. */
+
+class GMPTask {
+public:
+  virtual ~GMPTask() {}
+  virtual void Run() = 0;
+};
+
+class GMPThread {
+public:
+  virtual ~GMPThread() {}
+  virtual void Post(GMPTask* aTask) = 0;
+  virtual void Join() = 0;
+};
+
+class GMPMutex {
+public:
+  virtual ~GMPMutex() {}
+  virtual void Acquire() = 0;
+  virtual void Release() = 0;
+};
+
+typedef GMPErr (*GMPCreateThreadPtr)(GMPThread** aThread);
+typedef GMPErr (*GMPRunOnMainThreadPtr)(GMPTask* aTask);
+typedef GMPErr (*GMPSyncRunOnMainThreadPtr)(GMPTask* aTask);
+typedef GMPErr (*GMPCreateMutexPtr)(GMPMutex** aMutex);
+
+struct GMPPlatformAPI {
+  // Increment the version when things change. Can only add to the struct,
+  // do not change what already exists. Pointers to functions may be NULL
+  // when passed to plugins, but beware backwards compat implications of
+  // doing that.
+  uint16_t version; // Currently version 0
+
+  GMPCreateThreadPtr createthread;
+  GMPRunOnMainThreadPtr runonmainthread;
+  GMPSyncRunOnMainThreadPtr syncrunonmainthread;
+  GMPCreateMutexPtr createmutex;
+};
+
+#endif // GMP_PLATFORM_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-codec.h
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_CODEC_h_
+#define GMP_VIDEO_CODEC_h_
+
+#include <stdint.h>
+
+enum { kGMPPayloadNameSize = 32};
+enum { kGMPMaxSimulcastStreams = 4};
+
+enum GMPVideoCodecComplexity
+{
+  kGMPComplexityNormal = 0,
+  kGMPComplexityHigh = 1,
+  kGMPComplexityHigher = 2,
+  kGMPComplexityMax = 3,
+  kGMPComplexityInvalid // Should always be last
+};
+
+enum GMPVP8ResilienceMode {
+  kResilienceOff,    // The stream produced by the encoder requires a
+                     // recovery frame (typically a key frame) to be
+                     // decodable after a packet loss.
+  kResilientStream,  // A stream produced by the encoder is resilient to
+                     // packet losses, but packets within a frame subsequent
+                     // to a loss can't be decoded.
+  kResilientFrames,  // Same as kResilientStream but with added resilience
+                     // within a frame.
+  kResilienceInvalid // Should always be last.
+};
+
+// VP8 specific
+struct GMPVideoCodecVP8
+{
+  bool mPictureLossIndicationOn;
+  bool mFeedbackModeOn;
+  GMPVideoCodecComplexity mComplexity;
+  GMPVP8ResilienceMode mResilience;
+  uint32_t mNumberOfTemporalLayers;
+  bool mDenoisingOn;
+  bool mErrorConcealmentOn;
+  bool mAutomaticResizeOn;
+  bool mFrameDroppingOn;
+  int32_t mKeyFrameInterval;
+};
+
+enum GMPVideoCodecType
+{
+  kGMPVideoCodecVP8,
+  kGMPVideoCodecInvalid // Should always be last.
+};
+
+union GMPVideoCodecUnion
+{
+  GMPVideoCodecVP8 mVP8;
+};
+
+// Simulcast is when the same stream is encoded multiple times with different
+// settings such as resolution.
+struct GMPSimulcastStream
+{
+  uint32_t mWidth;
+  uint32_t mHeight;
+  uint32_t mNumberOfTemporalLayers;
+  uint32_t mMaxBitrate; // kilobits/sec.
+  uint32_t mTargetBitrate; // kilobits/sec.
+  uint32_t mMinBitrate; // kilobits/sec.
+  uint32_t mQPMax; // minimum quality
+};
+
+enum GMPVideoCodecMode {
+  kGMPRealtimeVideo,
+  kGMPScreensharing,
+  kGMPCodecModeInvalid // Should always be last.
+};
+
+struct GMPVideoCodec
+{
+  GMPVideoCodecType mCodecType;
+  char mPLName[kGMPPayloadNameSize]; // Must be NULL-terminated!
+  uint32_t mPLType;
+
+  uint32_t mWidth;
+  uint32_t mHeight;
+
+  uint32_t mStartBitrate; // kilobits/sec.
+  uint32_t mMaxBitrate; // kilobits/sec.
+  uint32_t mMinBitrate; // kilobits/sec.
+  uint32_t mMaxFramerate;
+
+  GMPVideoCodecUnion mCodecSpecific;
+
+  uint32_t mQPMax;
+  uint32_t mNumberOfSimulcastStreams;
+  GMPSimulcastStream mSimulcastStream[kGMPMaxSimulcastStreams];
+
+  GMPVideoCodecMode mMode;
+};
+
+struct GMPCodecSpecificInfoGeneric {
+  uint8_t mSimulcastIdx;
+};
+
+// Note: if any pointers are added to this struct, it must be fitted
+// with a copy-constructor. See below.
+struct GMPCodecSpecificInfoVP8
+{
+  bool mHasReceivedSLI;
+  uint8_t mPictureIdSLI;
+  bool mHasReceivedRPSI;
+  uint64_t mPictureIdRPSI;
+  int16_t mPictureId; // negative value to skip pictureId
+  bool mNonReference;
+  uint8_t mSimulcastIdx;
+  uint8_t mTemporalIdx;
+  bool mLayerSync;
+  int32_t mTL0PicIdx; // negative value to skip tl0PicIdx
+  int8_t mKeyIdx; // negative value to skip keyIdx
+};
+
+union GMPCodecSpecificInfoUnion
+{
+  GMPCodecSpecificInfoGeneric mGeneric;
+  GMPCodecSpecificInfoVP8 mVP8;
+};
+
+// Note: if any pointers are added to this struct or its sub-structs, it
+// must be fitted with a copy-constructor. This is because it is copied
+// in the copy-constructor of VCMEncodedFrame.
+struct GMPCodecSpecificInfo
+{
+  GMPVideoCodecType mCodecType;
+  GMPCodecSpecificInfoUnion mCodecSpecific;
+};
+
+#endif // GMP_VIDEO_CODEC_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-decode.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_DECODE_h_
+#define GMP_VIDEO_DECODE_h_
+
+#include "gmp-video-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+#include <stdint.h>
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPDecoderCallback
+{
+public:
+  virtual ~GMPDecoderCallback() {}
+
+  virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) = 0;
+
+  virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) = 0;
+
+  virtual void ReceivedDecodedFrame(const uint64_t aPictureId) = 0;
+
+  virtual void InputDataExhausted() = 0;
+};
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoDecoder
+{
+public:
+  virtual ~GMPVideoDecoder() {}
+
+  // aCallback: Subclass should retain reference to it until DecodingComplete
+  //            is called. Do not attempt to delete it, host retains ownership.
+  virtual GMPVideoErr InitDecode(const GMPVideoCodec& aCodecSettings,
+                                 GMPDecoderCallback* aCallback,
+                                 int32_t aCoreCount) = 0;
+
+  // Decode encoded frame (as a part of a video stream). The decoded frame
+  // will be returned to the user through the decode complete callback.
+  //
+  // inputFrame:        Frame to decode.
+  //
+  // missingFrames:     True if one or more frames have been lost since the previous decode call.
+  //
+  // fragmentation:     Specifies where the encoded frame can be split into separate fragments.
+  //                    The meaning of fragment is codec specific, but often means that each
+  //                    fragment is decodable by itself.
+  //
+  // codecSpecificInfo: Codec-specific data
+  //
+  // renderTimeMs :     System time to render in milliseconds. Only used by decoders with internal
+  //                    rendering.
+  virtual GMPVideoErr Decode(GMPVideoEncodedFrame* aInputFrame,
+                             bool aMissingFrames,
+                             const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                             int64_t aRenderTimeMs = -1) = 0;
+
+  // Reset decoder state and prepare for a new call to Decode(...). Flushes the decoder pipeline.
+  virtual GMPVideoErr Reset() = 0;
+
+  // Output decoded frames for any data in the pipeline, regardless of ordering.
+  virtual GMPVideoErr Drain() = 0;
+
+  // May free decoder memory.
+  virtual void DecodingComplete() = 0;
+};
+
+#endif // GMP_VIDEO_DECODE_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-encode.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_ENCODE_h_
+#define GMP_VIDEO_ENCODE_h_
+
+#include <vector>
+#include <stdint.h>
+
+#include "gmp-video-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPEncoderCallback
+{
+public:
+  virtual ~GMPEncoderCallback() {}
+
+  virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+                       const GMPCodecSpecificInfo& aCodecSpecificInfo) = 0;
+};
+
+// ALL METHODS MUST BE CALLED ON THE MAIN THREAD
+class GMPVideoEncoder
+{
+public:
+  virtual ~GMPVideoEncoder() {}
+
+  // Initialize the encoder with the information from the VideoCodec.
+  //
+  // Input:
+  // - codecSettings : Codec settings
+  // - aCallback: Subclass should retain reference to it until EncodingComplete
+  //              is called. Do not attempt to delete it, host retains ownership.
+  // - numberOfCores : Number of cores available for the encoder
+  // - maxPayloadSize : The maximum size each payload is allowed
+  //                    to have. Usually MTU - overhead.
+  virtual GMPVideoErr InitEncode(const GMPVideoCodec& aCodecSettings,
+                                 GMPEncoderCallback* aCallback,
+                                 int32_t aNumberOfCores,
+                                 uint32_t aMaxPayloadSize) = 0;
+
+  // Encode an I420 frame (as a part of a video stream). The encoded frame
+  // will be returned to the user through the encode complete callback.
+  //
+  // Input:
+  // - inputFrame : Frame to be encoded
+  // - codecSpecificInfo : Pointer to codec specific data
+  // - frame_types : The frame type to encode
+  virtual GMPVideoErr Encode(GMPVideoi420Frame* aInputFrame,
+                             const GMPCodecSpecificInfo& aCodecSpecificInfo,
+                             const std::vector<GMPVideoFrameType>& aFrameTypes) = 0;
+
+  // Inform the encoder about the packet loss and round trip time on the
+  // network used to decide the best pattern and signaling.
+  //
+  // - packetLoss : Fraction lost (loss rate in percent =
+  // 100 * packetLoss / 255)
+  // - rtt : Round-trip time in milliseconds
+  virtual GMPVideoErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
+
+  // Inform the encoder about the new target bit rate.
+  //
+  // - newBitRate : New target bit rate
+  // - frameRate : The target frame rate
+  virtual GMPVideoErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
+
+  // Use this function to enable or disable periodic key frames. Can be useful for codecs
+  // which have other ways of stopping error propagation.
+  //
+  // - enable : Enable or disable periodic key frames
+  virtual GMPVideoErr SetPeriodicKeyFrames(bool aEnable) = 0;
+
+  // May free Encoder memory.
+  virtual void EncodingComplete() = 0;
+};
+
+#endif // GMP_VIDEO_ENCODE_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-errors.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_ERRORS_h_
+#define GMP_VIDEO_ERRORS_h_
+
+enum GMPVideoErr {
+  GMPVideoNoErr = 0,
+  GMPVideoGenericErr = 1,
+  GMPVideoAllocErr = 2
+};
+
+#endif // GMP_VIDEO_ERRORS_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-frame-encoded.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_ENCODED_h_
+#define GMP_VIDEO_FRAME_ENCODED_h_
+
+#include <stdint.h>
+
+enum GMPVideoFrameType
+{
+  kGMPKeyFrame = 0,
+  kGMPDeltaFrame = 1,
+  kGMPGoldenFrame = 2,
+  kGMPAltRefFrame = 3,
+  kGMPSkipFrame = 4
+};
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPVideoEncodedFrame : public GMPVideoFrame
+{
+public:
+  // MAIN THREAD ONLY
+  virtual GMPVideoErr CreateEmptyFrame(uint32_t aSize) = 0;
+  // MAIN THREAD ONLY
+  virtual GMPVideoErr CopyFrame(const GMPVideoEncodedFrame& aVideoFrame) = 0;
+  virtual void     SetEncodedWidth(uint32_t aEncodedWidth) = 0;
+  virtual uint32_t EncodedWidth() = 0;
+  virtual void     SetEncodedHeight(uint32_t aEncodedHeight) = 0;
+  virtual uint32_t EncodedHeight() = 0;
+  virtual void     SetTimeStamp(uint32_t aTimeStamp) = 0;
+  virtual uint32_t TimeStamp() = 0;
+  virtual void     SetCaptureTime(int64_t aCaptureTime) = 0;
+  virtual int64_t  CaptureTime() = 0;
+  virtual void     SetFrameType(GMPVideoFrameType aFrameType) = 0;
+  virtual GMPVideoFrameType FrameType() = 0;
+  virtual void     SetAllocatedSize(uint32_t aNewSize) = 0;
+  virtual uint32_t AllocatedSize() = 0;
+  virtual void     SetSize(uint32_t aSize) = 0;
+  virtual uint32_t Size() = 0;
+  virtual void     SetCompleteFrame(bool aCompleteFrame) = 0;
+  virtual bool     CompleteFrame() = 0;
+  virtual const uint8_t* Buffer() const = 0;
+  virtual uint8_t*       Buffer() = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_ENCODED_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-frame-i420.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_I420_h_
+#define GMP_VIDEO_FRAME_I420_h_
+
+#include "gmp-video-errors.h"
+#include "gmp-video-frame.h"
+#include "gmp-video-plane.h"
+
+#include <stdint.h>
+
+enum GMPPlaneType {
+  kGMPYPlane = 0,
+  kGMPUPlane = 1,
+  kGMPVPlane = 2,
+  kGMPNumOfPlanes = 3
+};
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPVideoi420Frame : public GMPVideoFrame {
+public:
+  // MAIN THREAD ONLY
+  // CreateEmptyFrame: Sets frame dimensions and allocates buffers based
+  // on set dimensions - height and plane stride.
+  // If required size is bigger than the allocated one, new buffers of adequate
+  // size will be allocated.
+  virtual GMPVideoErr CreateEmptyFrame(int32_t aWidth, int32_t aHeight,
+                                       int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) = 0;
+
+  // MAIN THREAD ONLY
+  // CreateFrame: Sets the frame's members and buffers. If required size is
+  // bigger than allocated one, new buffers of adequate size will be allocated.
+  virtual GMPVideoErr CreateFrame(int32_t aSize_y, const uint8_t* aBuffer_y,
+                                  int32_t aSize_u, const uint8_t* aBuffer_u,
+                                  int32_t aSize_v, const uint8_t* aBuffer_v,
+                                  int32_t aWidth, int32_t aHeight,
+                                  int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) = 0;
+
+  // MAIN THREAD ONLY
+  // Copy frame: If required size is bigger than allocated one, new buffers of
+  // adequate size will be allocated.
+  virtual GMPVideoErr CopyFrame(const GMPVideoi420Frame& aVideoFrame) = 0;
+
+  // Swap Frame.
+  virtual void SwapFrame(GMPVideoi420Frame* aVideoFrame) = 0;
+
+  // Get pointer to buffer per plane.
+  virtual uint8_t* Buffer(GMPPlaneType aType) = 0;
+
+  // Overloading with const.
+  virtual const uint8_t* Buffer(GMPPlaneType aType) const = 0;
+
+  // Get allocated size per plane.
+  virtual int32_t AllocatedSize(GMPPlaneType aType) const = 0;
+
+  // Get allocated stride per plane.
+  virtual int32_t Stride(GMPPlaneType aType) const = 0;
+
+  // Set frame width.
+  virtual GMPVideoErr SetWidth(int32_t aWidth) = 0;
+
+  // Set frame height.
+  virtual GMPVideoErr SetHeight(int32_t aHeight) = 0;
+
+  // Get frame width.
+  virtual int32_t Width() const = 0;
+
+  // Get frame height.
+  virtual int32_t Height() const = 0;
+
+  // Set frame timestamp (90kHz).
+  virtual void SetTimestamp(uint32_t aTimestamp) = 0;
+
+  // Get frame timestamp (90kHz).
+  virtual uint32_t Timestamp() const = 0;
+
+  // Set render time in miliseconds.
+  virtual void SetRenderTime_ms(int64_t aRenderTime_ms) = 0;
+
+  // Get render time in miliseconds.
+  virtual int64_t RenderTime_ms() const = 0;
+
+  // Return true if underlying plane buffers are of zero size, false if not.
+  virtual bool IsZeroSize() const = 0;
+
+  // Reset underlying plane buffers sizes to 0. This function doesn't clear memory.
+  virtual void ResetSize() = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_I420_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-frame.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_FRAME_h_
+#define GMP_VIDEO_FRAME_h_
+
+#include "gmp-video-errors.h"
+#include "gmp-video-plane.h"
+
+enum GMPVideoFrameFormat {
+  kGMPEncodedVideoFrame = 0,
+  kGMPI420VideoFrame = 1
+};
+
+class GMPVideoFrame {
+public:
+  virtual GMPVideoFrameFormat GetFrameFormat() = 0;
+  // MAIN THREAD ONLY IF OWNING PROCESS
+  virtual void Destroy() = 0;
+};
+
+#endif // GMP_VIDEO_FRAME_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-host.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_HOST_h_
+#define GMP_VIDEO_HOST_h_
+
+#include "gmp-video-errors.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-codec.h"
+
+class GMPVideoHost
+{
+public:
+  // Construct various video API objects. Host does not retain reference,
+  // caller is owner and responsible for deleting.
+  virtual GMPVideoErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) = 0;
+  virtual GMPVideoErr CreatePlane(GMPPlane** aPlane) = 0;
+};
+
+#endif // GMP_VIDEO_HOST_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/gmp-api/gmp-video-plane.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright (c) 2011, The WebRTC project authors. All rights reserved.
+ * Copyright (c) 2014, Mozilla
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ ** Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ ** Neither the name of Google nor the names of its contributors may
+ *  be used to endorse or promote products derived from this software
+ *  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GMP_VIDEO_PLANE_h_
+#define GMP_VIDEO_PLANE_h_
+
+#include "gmp-video-errors.h"
+#include <stdint.h>
+
+// The implementation backing this interface uses shared memory for the
+// buffer(s). This means it can only be used by the "owning" process.
+// At first the process which created the object owns it. When the object
+// is passed to an interface the creator loses ownership and must Destroy()
+// the object. Further attempts to use it may fail due to not being able to
+// access the underlying buffer(s).
+//
+// Methods that create or destroy shared memory must be called on the main
+// thread. They are marked below.
+class GMPPlane {
+public:
+  // MAIN THREAD ONLY
+  // CreateEmptyPlane - set allocated size, actual plane size and stride:
+  // If current size is smaller than current size, then a buffer of sufficient
+  // size will be allocated.
+  virtual GMPVideoErr CreateEmptyPlane(int32_t aAllocatedSize,
+                                       int32_t aStride,
+                                       int32_t aPlaneSize) = 0;
+
+  // MAIN THREAD ONLY
+  // Copy the entire plane data.
+  virtual GMPVideoErr Copy(const GMPPlane& aPlane) = 0;
+
+  // MAIN THREAD ONLY
+  // Copy buffer: If current size is smaller
+  // than current size, then a buffer of sufficient size will be allocated.
+  virtual GMPVideoErr Copy(int32_t aSize, int32_t aStride, const uint8_t* aBuffer) = 0;
+
+  // Swap plane data.
+  virtual void Swap(GMPPlane& aPlane) = 0;
+
+  // Get allocated size.
+  virtual int32_t AllocatedSize() const = 0;
+
+  // Set actual size.
+  virtual void ResetSize() = 0;
+
+  // Return true is plane size is zero, false if not.
+  virtual bool IsZeroSize() const = 0;
+
+  // Get stride value.
+  virtual int32_t Stride() const = 0;
+
+  // Return data pointer.
+  virtual const uint8_t* Buffer() const = 0;
+
+  // Overloading with non-const.
+  virtual uint8_t* Buffer() = 0;
+
+  // MAIN THREAD ONLY IF OWNING PROCESS
+  // Call this when done with the object. This may delete it.
+  virtual void Destroy() = 0;
+};
+
+#endif // GMP_VIDEO_PLANE_h_
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/moz.build
@@ -0,0 +1,83 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_MODULE = 'content_geckomediaplugins'
+
+XPIDL_SOURCES += [
+    'mozIGeckoMediaPluginService.idl',
+]
+
+EXPORTS += [
+    'gmp-api/gmp-entrypoints.h',
+    'gmp-api/gmp-errors.h',
+    'gmp-api/gmp-platform.h',
+    'gmp-api/gmp-video-codec.h',
+    'gmp-api/gmp-video-decode.h',
+    'gmp-api/gmp-video-encode.h',
+    'gmp-api/gmp-video-errors.h',
+    'gmp-api/gmp-video-frame-encoded.h',
+    'gmp-api/gmp-video-frame-i420.h',
+    'gmp-api/gmp-video-frame.h',
+    'gmp-api/gmp-video-host.h',
+    'gmp-api/gmp-video-plane.h',
+    'GMPChild.h',
+    'GMPMessageUtils.h',
+    'GMPParent.h',
+    'GMPPlatform.h',
+    'GMPProcessChild.h',
+    'GMPProcessParent.h',
+    'GMPService.h',
+    'GMPSharedMemManager.h',
+    'GMPVideoDecoderChild.h',
+    'GMPVideoDecoderParent.h',
+    'GMPVideoEncodedFrameImpl.h',
+    'GMPVideoEncoderChild.h',
+    'GMPVideoEncoderParent.h',
+    'GMPVideoHost.h',
+    'GMPVideoi420FrameImpl.h',
+    'GMPVideoPlaneImpl.h',
+]
+
+UNIFIED_SOURCES += [
+    'GMPChild.cpp',
+    'GMPParent.cpp',
+    'GMPPlatform.cpp',
+    'GMPProcessChild.cpp',
+    'GMPProcessParent.cpp',
+    'GMPService.cpp',
+    'GMPVideoDecoderChild.cpp',
+    'GMPVideoDecoderParent.cpp',
+    'GMPVideoEncodedFrameImpl.cpp',
+    'GMPVideoEncoderChild.cpp',
+    'GMPVideoEncoderParent.cpp',
+    'GMPVideoHost.cpp',
+    'GMPVideoi420FrameImpl.cpp',
+    'GMPVideoPlaneImpl.cpp',
+]
+
+IPDL_SOURCES += [
+  'GMPTypes.ipdlh',
+  'PGMP.ipdl',
+  'PGMPVideoDecoder.ipdl',
+  'PGMPVideoEncoder.ipdl',
+]
+
+LIBRARY_NAME = 'mozgmp'
+
+if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
+    NO_VISIBILITY_FLAGS = True
+
+FAIL_ON_WARNINGS = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+    '../base',
+    '/xpcom/base',
+    '/xpcom/build',
+    '/xpcom/threads',
+]
new file mode 100644
--- /dev/null
+++ b/content/media/gmp/mozIGeckoMediaPluginService.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIThread.idl"
+
+%{C++
+class GMPVideoDecoder;
+class GMPVideoEncoder;
+class GMPVideoHost;
+%}
+
+[ptr] native GMPVideoDecoder(GMPVideoDecoder);
+[ptr] native GMPVideoEncoder(GMPVideoEncoder);
+[ptr] native GMPVideoHost(GMPVideoHost);
+[ptr] native MessageLoop(MessageLoop);
+
+[uuid(BF5A9086-70F5-4D38-832D-1609BBF963CD)]
+interface mozIGeckoMediaPluginService : nsISupports
+{
+  // Returns the GMP thread.
+  // Callable from any thread.
+  readonly attribute nsIThread thread;
+
+  // Returns a video decoder API object that should support VP8.
+  // Callable only on GMP thread.
+  GMPVideoDecoder getGMPVideoDecoderVP8(out GMPVideoHost outVideoHost);
+
+  // Returns a video encoder API object that should support VP8.
+  // Callable only on GMP thread.
+  GMPVideoEncoder getGMPVideoEncoderVP8(out GMPVideoHost outVideoHost);
+};
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 PARALLEL_DIRS += [
   'encoder',
+  'gmp',
   'mediasource',
   'ogg',
   'webaudio',
   'webvtt'
 ]
 
 TEST_TOOL_DIRS += ['compiledtest']
 
--- a/content/media/omx/OMXCodecWrapper.cpp
+++ b/content/media/omx/OMXCodecWrapper.cpp
@@ -438,17 +438,22 @@ OMXVideoEncoder::GetCodecConfig(nsTArray
 }
 
 // MediaCodec::setParameters() is available only after API level 18.
 #if ANDROID_VERSION >= 18
 nsresult
 OMXVideoEncoder::SetBitrate(int32_t aKbps)
 {
   sp<AMessage> msg = new AMessage();
+#if ANDROID_VERSION >= 19
+  // XXX Do we need a runtime check here?
+  msg->setInt32("video-bitrate", aKbps * 1000 /* kbps -> bps */);
+#else
   msg->setInt32("videoBitrate", aKbps * 1000 /* kbps -> bps */);
+#endif
   status_t result = mCodec->setParameters(msg);
   MOZ_ASSERT(result == OK);
   return result == OK ? NS_OK : NS_ERROR_FAILURE;
 }
 #endif
 
 nsresult
 OMXVideoEncoder::RequestIDRFrame()
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -8425,17 +8425,17 @@ nsGlobalWindow::ReallyCloseWindow()
 
     CleanUp();
   }
 }
 
 void
 nsGlobalWindow::EnterModalState()
 {
-  FORWARD_TO_OUTER_VOID(EnterModalState, ());
+  MOZ_ASSERT(IsOuterWindow(), "Modal state is maintained on outer windows");
 
   // GetScriptableTop, not GetTop, so that EnterModalState works properly with
   // <iframe mozbrowser>.
   nsGlobalWindow* topWin = GetScriptableTop();
 
   if (!topWin) {
     NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
     return;
@@ -8558,17 +8558,17 @@ public:
 
 private:
   nsRefPtr<nsGlobalWindow> mWindow;
 };
 
 void
 nsGlobalWindow::LeaveModalState()
 {
-  FORWARD_TO_OUTER_VOID(LeaveModalState, ());
+  MOZ_ASSERT(IsOuterWindow(), "Modal state is maintained on outer windows");
 
   nsGlobalWindow* topWin = GetScriptableTop();
 
   if (!topWin) {
     NS_ERROR("Uh, LeaveModalState() called w/o a reachable top window?");
     return;
   }
 
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2039,51 +2039,55 @@ nsJSContext::BeginCycleCollectionCallbac
   if (sICCTimer) {
     sICCTimer->InitWithFuncCallback(ICCTimerFired,
                                     nullptr,
                                     kICCIntersliceDelay,
                                     nsITimer::TYPE_REPEATING_SLACK);
   }
 }
 
+static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
+
 //static
 void
 nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsJSContext::KillICCTimer();
 
   // Update timing information for the current slice before we log it, if
   // we previously called PrepareForCycleCollectionSlice(). During shutdown
   // CCs, this won't happen.
   gCCStats.FinishCycleCollectionSlice();
 
   sCCollectedWaitingForGC += aResults.mFreedRefCounted + aResults.mFreedGCed;
 
+  TimeStamp endCCTimeStamp = TimeStamp::Now();
+  uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTimeStamp);
+
   if (NeedsGCAfterCC()) {
-    PokeGC(JS::gcreason::CC_WAITING);
+    PokeGC(JS::gcreason::CC_WAITING,
+           NS_GC_DELAY - std::min(ccNowDuration, kMaxICCDuration));
   }
 
-  TimeStamp endCCTimeStamp = TimeStamp::Now();
-
   PRTime endCCTime;
   if (sPostGCEventsToObserver) {
     endCCTime = PR_Now();
   }
 
   // Log information about the CC via telemetry, JSON and the console.
-  uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTimeStamp);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, gCCStats.mAnyLockedOut);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, gCCStats.mRanSyncForgetSkippable);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL, ccNowDuration);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE, gCCStats.mMaxSliceTime);
 
   if (!sLastCCEndTime.IsNull()) {
-    uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime);
+    // TimeBetween returns milliseconds, but we want to report seconds.
+    uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime) / 1000;
     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween);
   }
   sLastCCEndTime = endCCTimeStamp;
 
   Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
                         sMaxForgetSkippableTime / PR_USEC_PER_MSEC);
 
   PRTime delta = GetCollectionTimeDelta();
@@ -2224,37 +2228,26 @@ static bool
 ShouldTriggerCC(uint32_t aSuspected)
 {
   return sNeedsFullCC ||
          aSuspected > NS_CC_PURPLE_LIMIT ||
          (aSuspected > NS_CC_FORCED_PURPLE_LIMIT &&
           TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED);
 }
 
-static uint32_t
-TimeToNextCC()
-{
-  if (sIncrementalCC) {
-    return NS_CC_DELAY - kMaxICCDuration;
-  }
-  return NS_CC_DELAY;
-}
-
-static_assert(NS_CC_DELAY > kMaxICCDuration, "ICC shouldn't reduce CC delay to 0");
-
 static void
 CCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   if (sDidShutdown) {
     return;
   }
 
   static uint32_t ccDelay = NS_CC_DELAY;
   if (sCCLockedOut) {
-    ccDelay = TimeToNextCC() / 3;
+    ccDelay = NS_CC_DELAY / 3;
 
     PRTime now = PR_Now();
     if (sCCLockedOutTime == 0) {
       // Reset sCCTimerFireCount so that we run forgetSkippable
       // often enough before CC. Because of reduced ccDelay
       // forgetSkippable will be called just a few times.
       // NS_MAX_CC_LOCKEDOUT_TIME limit guarantees that we end up calling
       // forgetSkippable and CycleCollectNow eventually.
@@ -2291,17 +2284,17 @@ CCTimerFired(nsITimer *aTimer, void *aCl
       nsJSContext::RunCycleCollectorSlice();
     }
   } else if ((sPreviousSuspectedCount + 100) <= suspected) {
       // Only do a forget skippable if there are more than a few new objects.
       FireForgetSkippable(suspected, false);
   }
 
   if (isLateTimerFire) {
-    ccDelay = TimeToNextCC();
+    ccDelay = NS_CC_DELAY;
 
     // We have either just run the CC or decided we don't want to run the CC
     // next time, so kill the timer.
     sPreviousSuspectedCount = 0;
     nsJSContext::KillCCTimer();
   }
 }
 
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -45,16 +45,20 @@ support-files =
 [test_messagemanager_targetchain.html]
 [test_messageChannel_transferable.html]
 [test_messageChannel_unshipped.html]
 [test_named_frames.html]
 [test_navigator_resolve_identity.html]
 [test_navigator_language.html]
 [test_nondomexception.html]
 [test_openDialogChromeOnly.html]
+
+[test_open_null_features.html]
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Fails on b2g-desktop, tracked in bug 1011874
+
 [test_postMessage_solidus.html]
 [test_screen_orientation.html]
 [test_settimeout_extra_arguments.html]
 [test_settimeout_inner.html]
 [test_setting_opener.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_url.html]
 [test_url_empty_port.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_open_null_features.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1009529
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1009529</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1009529 **/
+  SimpleTest.waitForExplicitFinish();
+
+  var win1 = open("about:blank", "_blank", null);
+  var win2 = open("about:blank", "_blank", "");
+  for (var k in win1) {
+    var v;
+    try {
+      v = win1[k];
+    } catch (ex) {}
+    if (v instanceof win1.BarProp) {
+      is(v.visible, win2[k] && win2[k].visible, "Both windows should have the same value for " + k);
+    }
+  }
+
+  var closeCount = 0;
+  var closeInc = function(e) {
+    this.removeEventListener("unload", closeInc, true);
+    closeCount++;
+    if (closeCount == 2) {
+      SimpleTest.finish();
+    }
+  };
+  win1.addEventListener("unload", closeInc, true);
+  win2.addEventListener("unload", closeInc, true);
+  win1.close();
+  win2.close();
+  
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1009529">Mozilla Bug 1009529</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1902,19 +1902,19 @@ InterfaceHasInstance(JSContext* cx, JS::
 
   if (domClass &&
       domClass->mInterfaceChain[clasp->mDepth] == clasp->mPrototypeID) {
     *bp = true;
     return true;
   }
 
   JS::Rooted<JSObject*> unwrapped(cx, js::CheckedUnwrap(instance, true));
-  if (unwrapped && jsipc::JavaScriptParent::IsCPOW(unwrapped)) {
+  if (unwrapped && jsipc::IsCPOW(unwrapped)) {
     bool boolp = false;
-    if (!jsipc::JavaScriptParent::DOMInstanceOf(cx, unwrapped, clasp->mPrototypeID,
+    if (!jsipc::DOMInstanceOf(cx, unwrapped, clasp->mPrototypeID,
                                                 clasp->mDepth, &boolp)) {
       return false;
     }
     *bp = boolp;
     return true;
   }
 
   JS::Rooted<JS::Value> protov(cx);
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -50,18 +50,17 @@ def isTypeCopyConstructible(type):
             # interfaces.  SpiderMonkey interfaces are not copy-constructible
             # because of rooting issues.
             (type.isInterface() and type.isGeckoInterface()))
 
 
 def wantsAddProperty(desc):
     return (desc.concrete and
             desc.wrapperCache and
-            not (desc.workers and
-                 desc.interface.getExtendedAttribute("Global")))
+            not desc.interface.getExtendedAttribute("Global"))
 
 
 # We'll want to insert the indent at the beginnings of lines, but we
 # don't want to indent empty lines.  So only indent lines that have a
 # non-newline character on them.
 lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE)
 
 
@@ -7577,17 +7576,22 @@ def getEnumValueName(value):
         return '_' + value
     value = re.sub(r'[^0-9A-Za-z_]', '_', value)
     if re.match("^_[A-Z]|__", value):
         raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec')
     if value == "_empty":
         raise SyntaxError('"_empty" is not an IDL enum value we support yet')
     if value == "":
         return "_empty"
-    return MakeNativeName(value)
+    nativeName = MakeNativeName(value)
+    if nativeName == "EndGuard_":
+        raise SyntaxError('Enum value "' + value + '" cannot be used because it'
+                          ' collides with our internal EndGuard_ value.  Please'
+                          ' rename our internal EndGuard_ to something else')
+    return nativeName
 
 
 class CGEnum(CGThing):
     def __init__(self, enum):
         CGThing.__init__(self)
         self.enum = enum
 
     def stringsNamespace(self):
@@ -7597,20 +7601,21 @@ class CGEnum(CGThing):
         return len(self.enum.values()) + 1
 
     def declare(self):
         decl = fill(  # BOGUS extra newline at top
             """
 
             MOZ_BEGIN_ENUM_CLASS(${name}, uint32_t)
               $*{enums}
+              EndGuard_
             MOZ_END_ENUM_CLASS(${name})
             """,
             name=self.enum.identifier.name,
-            enums=",\n".join(map(getEnumValueName, self.enum.values())) + "\n")
+            enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n")
         strings = CGNamespace(self.stringsNamespace(),
                               CGGeneric(declare="extern const EnumEntry %s[%d];\n"
                                         % (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings())))
         return decl + "\n" + strings.declare()
 
     def define(self):
         strings = fill(  # BOGUS extra newline at top
             """
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -894,17 +894,17 @@ ContentChild::AllocPJavaScriptChild()
         return nullptr;
     }
     return child;
 }
 
 bool
 ContentChild::DeallocPJavaScriptChild(PJavaScriptChild *child)
 {
-    delete child;
+    static_cast<mozilla::jsipc::JavaScriptChild *>(child)->decref();
     return true;
 }
 
 PBrowserChild*
 ContentChild::AllocPBrowserChild(const IPCTabContext& aContext,
                                  const uint32_t& aChromeFlags)
 {
     // We'll happily accept any kind of IPCTabContext here; we don't need to
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -79,16 +79,17 @@
 #include "nsIClipboard.h"
 #include "nsICycleCollectorListener.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "mozilla/dom/WakeLock.h"
 #include "nsIDOMWindow.h"
 #include "nsIExternalProtocolService.h"
 #include "nsIGfxInfo.h"
 #include "nsIIdleService.h"
+#include "nsIJSRuntimeService.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIPresShell.h"
 #include "nsIRemoteBlob.h"
 #include "nsIScriptError.h"
@@ -1688,17 +1689,17 @@ ContentParent::InitInternal(ProcessPrior
         // PBrowsers are created, because they rely on the Compositor
         // already being around.  (Creation is async, so can't happen
         // on demand.)
         bool useOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
         if (useOffMainThreadCompositing) {
             DebugOnly<bool> opened = PCompositor::Open(this);
             MOZ_ASSERT(opened);
 
-            if (gfxPrefs::AsyncVideoEnabled()) {
+            if (gfxPrefs::AsyncVideoOOPEnabled()) {
                 opened = PImageBridge::Open(this);
                 MOZ_ASSERT(opened);
             }
         }
     }
 #ifdef MOZ_WIDGET_GONK
     DebugOnly<bool> opened = PSharedBufferManager::Open(this);
     MOZ_ASSERT(opened);
@@ -2350,17 +2351,25 @@ ContentParent::RecvGetXPCOMProcessAttrib
 
     return true;
 }
 
 mozilla::jsipc::PJavaScriptParent *
 ContentParent::AllocPJavaScriptParent()
 {
     MOZ_ASSERT(!ManagedPJavaScriptParent().Length());
-    mozilla::jsipc::JavaScriptParent *parent = new mozilla::jsipc::JavaScriptParent();
+
+    nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
+    NS_ENSURE_TRUE(svc, nullptr);
+
+    JSRuntime *rt;
+    svc->GetRuntime(&rt);
+    NS_ENSURE_TRUE(svc, nullptr);
+
+    mozilla::jsipc::JavaScriptParent *parent = new mozilla::jsipc::JavaScriptParent(rt);
     if (!parent->init()) {
         delete parent;
         return nullptr;
     }
     return parent;
 }
 
 bool
@@ -3344,17 +3353,17 @@ ContentParent::DoSendAsyncMessage(JSCont
                                   JS::Handle<JSObject *> aCpows,
                                   nsIPrincipal* aPrincipal)
 {
   ClonedMessageData data;
   if (!BuildClonedMessageDataForParent(this, aData, data)) {
     return false;
   }
   InfallibleTArray<CpowEntry> cpows;
-  if (!GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
+  if (aCpows && !GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
     return false;
   }
   return SendAsyncMessage(nsString(aMessage), data, cpows, aPrincipal);
 }
 
 bool
 ContentParent::CheckPermission(const nsAString& aPermission)
 {
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -830,19 +830,19 @@ RTCPeerConnection.prototype = {
   getStreamById: function(id) {
     throw new this._win.DOMError("", "getStreamById not yet implemented");
   },
 
   close: function() {
     if (this._closed) {
       return;
     }
+    this.changeIceConnectionState("closed");
     this._queueOrRun({ func: this._close, args: [false], wait: false });
     this._closed = true;
-    this.changeIceConnectionState("closed");
   },
 
   _close: function() {
     this._localIdp.close();
     this._remoteIdp.close();
     this._impl.close();
   },
 
--- a/dom/media/tests/identity/test_peerConnection_peerIdentity.html
+++ b/dom/media/tests/identity/test_peerConnection_peerIdentity.html
@@ -3,34 +3,35 @@
 <head>
   <meta charset="utf-8"/>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="../mochitest/head.js"></script>
   <script type="application/javascript" src="../mochitest/pc.js"></script>
   <script type="application/javascript" src="../mochitest/templates.js"></script>
   <script type="application/javascript" src="../mochitest/blacksilence.js"></script>
+  <script type="application/javascript" src="../mochitest/turnConfig.js"></script>
 </head>
 <body>
 <div id="display"></div>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     title: "setIdentityProvider leads to peerIdentity and assertions in SDP"
   });
 
 var test;
 function theTest() {
   var id1 = 'someone@test1.example.com';
   var id2 = 'someone@test2.example.com';
   test = new PeerConnectionTest({
-    config_pc1: {
+    config_local: {
       peerIdentity: id2
     },
-    config_pc2: {
+    config_remote: {
       peerIdentity: id1
     }
   });
   test.setMediaConstraints([{
     audio: true,
     peerIdentity: id2
   }, {
     video: true,
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -2,16 +2,17 @@
 support-files =
   head.js
   constraints.js
   mediaStreamPlayback.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
   blacksilence.js
+  turnConfig.js
 
 [test_dataChannel_basicAudio.html]
 skip-if = toolkit == 'gonk' #Bug 962984 for debug, bug 963244 for opt
 [test_dataChannel_basicAudioVideo.html]
 # Disabled on OS X for bug 930481 timeouts
 skip-if = os == 'mac' || toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_basicAudioVideoCombined.html]
 # Disabled on OS X for bug 930481 timeouts
@@ -54,16 +55,17 @@ skip-if = toolkit=='gonk' # b2g(Bug 9604
 [test_peerConnection_basicVideo.html]
 skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_bug822674.html]
 [test_peerConnection_bug825703.html]
 [test_peerConnection_bug827843.html]
 skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_bug834153.html]
 [test_peerConnection_bug835370.html]
+[test_peerConnection_close.html]
 [test_peerConnection_errorCallbacks.html]
 [test_peerConnection_offerRequiresReceiveAudio.html]
 skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_offerRequiresReceiveVideo.html]
 skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_offerRequiresReceiveVideoAudio.html]
 skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) b2g-debug(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_setLocalAnswerInHaveLocalOffer.html]
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -413,29 +413,48 @@ function getNetworkUtils() {
  * @param {object} [options={}]
  *        Optional options for the peer connection test
  * @param {object} [options.commands=commandsPeerConnection]
  *        Commands to run for the test
  * @param {bool}   [options.is_local=true]
  *        true if this test should run the tests for the "local" side.
  * @param {bool}   [options.is_remote=true]
  *        true if this test should run the tests for the "remote" side.
- * @param {object} [options.config_pc1=undefined]
+ * @param {object} [options.config_local=undefined]
  *        Configuration for the local peer connection instance
- * @param {object} [options.config_pc2=undefined]
+ * @param {object} [options.config_remote=undefined]
  *        Configuration for the remote peer connection instance. If not defined
  *        the configuration from the local instance will be used
  */
 function PeerConnectionTest(options) {
   // If no options are specified make it an empty object
   options = options || { };
   options.commands = options.commands || commandsPeerConnection;
   options.is_local = "is_local" in options ? options.is_local : true;
   options.is_remote = "is_remote" in options ? options.is_remote : true;
 
+  if (typeof turnServers !== "undefined") {
+    if ((!options.turn_disabled_local) && (turnServers.local)) {
+      if (!options.hasOwnProperty("config_local")) {
+        options.config_local = {};
+      }
+      if (!options.config_local.hasOwnProperty("iceServers")) {
+        options.config_local.iceServers = turnServers.local.iceServers;
+      }
+    }
+    if ((!options.turn_disabled_remote) && (turnServers.remote)) {
+      if (!options.hasOwnProperty("config_remote")) {
+        options.config_remote = {};
+      }
+      if (!options.config_remote.hasOwnProperty("iceServers")) {
+        options.config_remote.iceServers = turnServers.remote.iceServers;
+      }
+    }
+  }
+
   var netTeardownCommand = null;
   if (!isNetworkReady()) {
     var utils = getNetworkUtils();
     // Trigger network setup to obtain IP address before creating any PeerConnection.
     utils.prepareNetwork(function() {
       ok(isNetworkReady(),'setup network connection successfully');
     });
 
@@ -448,22 +467,22 @@ function PeerConnectionTest(options) {
             test.next();
           });
         }
       ]
     ];
   }
 
   if (options.is_local)
-    this.pcLocal = new PeerConnectionWrapper('pcLocal', options.config_pc1);
+    this.pcLocal = new PeerConnectionWrapper('pcLocal', options.config_local);
   else
     this.pcLocal = null;
 
   if (options.is_remote)
-    this.pcRemote = new PeerConnectionWrapper('pcRemote', options.config_pc2 || options.config_pc1);
+    this.pcRemote = new PeerConnectionWrapper('pcRemote', options.config_remote || options.config_local);
   else
     this.pcRemote = null;
 
   this.connected = false;
 
   // Create command chain instance and assign default commands
   this.chain = new CommandChain(this, options.commands);
   if (!options.is_local) {
@@ -747,19 +766,19 @@ PeerConnectionTest.prototype.teardown = 
 /**
  * This class handles tests for data channels.
  *
  * @constructor
  * @param {object} [options={}]
  *        Optional options for the peer connection test
  * @param {object} [options.commands=commandsDataChannel]
  *        Commands to run for the test
- * @param {object} [options.config_pc1=undefined]
+ * @param {object} [options.config_local=undefined]
  *        Configuration for the local peer connection instance
- * @param {object} [options.config_pc2=undefined]
+ * @param {object} [options.config_remote=undefined]
  *        Configuration for the remote peer connection instance. If not defined
  *        the configuration from the local instance will be used
  */
 function DataChannelTest(options) {
   options = options || { };
   options.commands = options.commands || commandsDataChannel;
 
   PeerConnectionTest.call(this, options);
--- a/dom/media/tests/mochitest/steeplechase.ini
+++ b/dom/media/tests/mochitest/steeplechase.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 support-files =
   head.js
   mediaStreamPlayback.js
   pc.js
   templates.js
+  turnConfig.js
 
 [test_peerConnection_basicAudio.html]
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -2,16 +2,25 @@
  * Default list of commands to execute for a PeerConnection test.
  */
 
 var STABLE = "stable";
 var HAVE_LOCAL_OFFER = "have-local-offer";
 var HAVE_REMOTE_OFFER = "have-remote-offer";
 var CLOSED = "closed";
 
+function dumpSdp(test) {
+  if (typeof test._local_offer !== 'undefined') {
+    dump("ERROR: SDP offer: " + test._local_offer.sdp.replace(/[\r]/g, ''));
+  }
+  if (typeof test._remote_answer !== 'undefined') {
+    dump("ERROR: SDP answer: " + test._remote_answer.sdp.replace(/[\r]/g, ''));
+  }
+}
+
 var commandsPeerConnection = [
   [
     'PC_LOCAL_GUM',
     function (test) {
       test.pcLocal.getAllUserMedia(function () {
         test.next();
       });
     }
@@ -148,60 +157,56 @@ var commandsPeerConnection = [
       var myTest = test;
       var myPc = myTest.pcLocal;
 
       function onIceConnectedSuccess () {
         ok(true, "pc_local: ICE switched to 'connected' state");
         myTest.next();
       };
       function onIceConnectedFailed () {
-        dump("ERROR: pc_local: SDP offer: " + myTest._local_offer.sdp.replace(/[\r]/g, ''));
-        dump("ERROR: pc_local: SDP answer: " + myTest._remote_answer.sdp.replace(/[\r]/g, ''));
+        dumpSdp(myTest);
         ok(false, "pc_local: ICE failed to switch to 'connected' state: " + myPc.iceConnectionState);
         myTest.next();
       };
 
       if (myPc.isIceConnected()) {
         ok(true, "pc_local: ICE is in connected state");
         myTest.next();
       } else if (myPc.isIceConnectionPending()) {
         myPc.waitForIceConnected(onIceConnectedSuccess, onIceConnectedFailed);
       } else {
-        dump("ERROR: pc_local: SDP offer: " + myTest._local_offer.sdp.replace(/[\r]/g, ''));
-        dump("ERROR: pc_local: SDP answer: " + myTest._remote_answer.sdp.replace(/[\r]/g, ''));
+        dumpSdp(myTest);
         ok(false, "pc_local: ICE is already in bad state: " + myPc.iceConnectionState);
         myTest.next();
       }
     }
   ],
   [
     'PC_REMOTE_WAIT_FOR_ICE_CONNECTED',
     function (test) {
       var myTest = test;
       var myPc = myTest.pcRemote;
 
       function onIceConnectedSuccess () {
         ok(true, "pc_remote: ICE switched to 'connected' state");
         myTest.next();
       };
       function onIceConnectedFailed () {
-        dump("ERROR: pc_remote: SDP offer: " + myTest._local_offer.sdp.replace(/[\r]/g, ''));
-        dump("ERROR: pc_remote: SDP answer: " + myTest._remote_answer.sdp.replace(/[\r]/g, ''));
+        dumpSdp(myTest);
         ok(false, "pc_remote: ICE failed to switch to 'connected' state: " + myPc.iceConnectionState);
         myTest.next();
       };
 
       if (myPc.isIceConnected()) {
         ok(true, "pc_remote: ICE is in connected state");
         myTest.next();
       } else if (myPc.isIceConnectionPending()) {
         myPc.waitForIceConnected(onIceConnectedSuccess, onIceConnectedFailed);
       } else {
-        dump("ERROR: pc_remote: SDP offer: " + myTest._local_offer.sdp.replace(/[\r]/g, ''));
-        dump("ERROR: pc_remote: SDP answer: " + myTest._remote_answer.sdp.replace(/[\r]/g, ''));
+        dumpSdp(myTest);
         ok(false, "pc_remote: ICE is already in bad state: " + myPc.iceConnectionState);
         myTest.next();
       }
     }
   ],
   [
     'PC_LOCAL_CHECK_MEDIA_TRACKS',
     function (test) {
--- a/dom/media/tests/mochitest/test_dataChannel_basicAudio.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudio.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796895",
     title: "Basic data channel audio connection"
   });
--- a/dom/media/tests/mochitest/test_dataChannel_basicAudioVideo.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudioVideo.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796891",
     title: "Basic data channel audio/video connection"
   });
--- a/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoCombined.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoCombined.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796891",
     title: "Basic data channel audio/video connection"
   });
--- a/dom/media/tests/mochitest/test_dataChannel_basicDataOnly.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicDataOnly.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796894",
     title: "Basic datachannel only connection"
   });
--- a/dom/media/tests/mochitest/test_dataChannel_basicVideo.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicVideo.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796889",
     title: "Basic data channel video connection"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_addCandidateInHaveLocalOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addCandidateInHaveLocalOffer.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "784519",
     title: "addCandidate (answer) in 'have-local-offer'"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudio.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796892",
     title: "Basic audio-only peer connection"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
 
   createHTML({
     bug: "796890",
     title: "Basic audio/video (separate) peer connection"
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoCombined.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioVideoCombined.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
 
   createHTML({
     bug: "796890",
     title: "Basic audio/video (combined) peer connection"
--- a/dom/media/tests/mochitest/test_peerConnection_basicVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicVideo.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796888",
     title: "Basic video-only peer connection"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_bug827843.html
+++ b/dom/media/tests/mochitest/test_peerConnection_bug827843.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "827843",
     title: "Ensure that localDescription and remoteDescription are null after close"
   });
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_close.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "991877",
+    title: "Basic RTCPeerConnection.close() tests"
+  });
+
+  runTest(function () {
+    var pc = new mozRTCPeerConnection();
+    var signalStateChanged = false;
+    var exception = null;
+    var eTimeout = null;
+
+    // everything should be in initial state
+    is(pc.signalingState, "stable", "Initial signalingState is 'stable'");
+    is(pc.iceConnectionState, "new", "Initial iceConnectionState is 'new'");
+    is(pc.iceGatheringState, "new", "Initial iceGatheringState is 'new'");
+
+    pc.onsignalingstatechange = function(aEvent) {
+      clearTimeout(eTimeout);
+      signalStateChanged = true;
+      is(aEvent, "closed", "onsignalingstatechange event is 'closed'");
+      is(pc.signalingState, "closed", "Event callback signalingState is 'closed'");
+      is(pc.iceConnectionState, "closed", "Event callback iceConnectionState is 'closed'");
+
+      try {
+        pc.close();
+      } catch (e) {
+        exception = e;
+      }
+      is(exception, null, "A second close() should not raise an exception");
+      is(pc.signalingState, "closed", "Final signalingState stays at 'closed'");
+      is(pc.iceConnectionState, "closed", "Final iceConnectionState stays at 'closed'");
+
+      // TODO: according to the webrtc spec all of these should throw InvalidStateError's
+      //       instead they seem to throw simple Error's
+      SimpleTest.doesThrow(function() {
+        pc.setLocalDescription(
+          "Invalid Session Description",
+          function() {},
+          function() {})},
+        "setLocalDescription() on closed PC raised expected exception");
+
+      SimpleTest.doesThrow(function() {
+        pc.setRemoteDescription(
+          "Invalid Session Description",
+          function() {},
+          function() {})},
+        "setRemoteDescription() on closed PC raised expected exception");
+
+      SimpleTest.doesThrow(function() {
+        pc.updateIce("Invalid RTC Configuration")},
+        "updateIce() on closed PC raised expected exception");
+
+      SimpleTest.doesThrow(function() {
+        pc.addStream("Invalid Media Stream")},
+        "addStream() on closed PC raised expected exception");
+
+      SimpleTest.doesThrow(function() {
+        pc.removeStream("Invalid Media Stream")},
+        "removeStream() on closed PC raised expected exception");
+
+      SimpleTest.doesThrow(function() {
+        pc.createDataChannel({})},
+        "createDataChannel() on closed PC raised expected exception");
+
+      // The spec says it has to throw, but it seems questionable why...
+      SimpleTest.doesThrow(function() {
+        pc.getStats()},
+        "getStats() on closed PC raised expected exception");
+
+      SimpleTest.doesThrow(function() {
+        pc.setIdentityProvider("Invalid Provider")},
+        "setIdentityProvider() on closed PC raised expected exception");
+
+      // in case we are ending the test from within here
+      if(pc !== null) {
+        pc = null;
+        SimpleTest.finish();
+      }
+    }
+
+    // This is only a shortcut to prevent a mochitest timeout in case the
+    // event does not fire
+    eTimeout = setTimeout(function() {
+      ok(signalStateChanged, "Failed to receive expected onsignalingstatechange event in 60s");
+
+      // in case we are ending the test in this timeout
+      if (pc !== null) {
+        pc = null;
+        SimpleTest.finish();
+      }
+    }, 60000);
+
+    try {
+      pc.close();
+    } catch (e) {
+      exception = e;
+    }
+    is(exception, null, "closing the connection raises no exception");
+    if (pc !== null) {
+      is(pc.signalingState, "closed", "Final signalingState is 'closed'");
+      is(pc.iceConnectionState, "closed", "Final iceConnectionState is 'closed'");
+    }
+
+    if (signalStateChanged) {
+      clearTimeout(eTimeout);
+      // in case we are ending the test outside the even handler
+      if (pc !== null) {
+        pc = null;
+        SimpleTest.finish();
+      }
+    }
+
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/test_peerConnection_offerRequiresReceiveAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_offerRequiresReceiveAudio.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "850275",
     title: "Simple offer media constraint test with audio"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_offerRequiresReceiveVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_offerRequiresReceiveVideo.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "850275",
     title: "Simple offer media constraint test with video"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_offerRequiresReceiveVideoAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_offerRequiresReceiveVideoAudio.html
@@ -1,16 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "850275",
     title: "Simple offer media constraint test with video/audio"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInHaveLocalOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInHaveLocalOffer.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "784519",
     title: "setLocalDescription (answer) in 'have-local-offer'"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInStable.html
+++ b/dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInStable.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "784519",
     title: "setLocalDescription (answer) in 'stable'"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_setLocalOfferInHaveRemoteOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_setLocalOfferInHaveRemoteOffer.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "784519",
     title: "setLocalDescription (offer) in 'have-remote-offer'"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "784519",
     title: "setRemoteDescription (answer) in 'have-remote-offer'"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInStable.html
+++ b/dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInStable.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "784519",
     title: "setRemoteDescription (answer) in 'stable'"
   });
--- a/dom/media/tests/mochitest/test_peerConnection_setRemoteOfferInHaveLocalOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_setRemoteOfferInHaveLocalOffer.html
@@ -2,16 +2,17 @@
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "784519",
     title: "setRemoteDescription (offer) in 'have-local-offer'"
   });
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/turnConfig.js
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* An example of how to specify two TURN server configs:
+ * var turnServers = {
+ *   local: { iceServers: [{"username":"mozilla","credential":"mozilla","url":"turn:10.0.0.1"}] },
+ *   remote: { iceServers: [{"username":"firefox","credential":"firefox","url":"turn:10.0.0.2"}] }
+ * };
+  */
+
+var turnServers = { };
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -120,16 +120,21 @@ using mozilla::TimeStamp;
 // this is the name of the directory which will be created
 // to cache temporary files.
 #define kPluginTmpDirName NS_LITERAL_CSTRING("plugtmp")
 
 static const char *kPrefWhitelist = "plugin.allowed_types";
 static const char *kPrefDisableFullPage = "plugin.disable_full_page_plugin_for_types";
 static const char *kPrefJavaMIME = "plugin.java.mime";
 
+// How long we wait before unloading an idle plugin process.
+// Defaults to 30 seconds.
+static const char *kPrefUnloadPluginTimeoutSecs = "dom.ipc.plugins.unloadTimeoutSecs";
+static const uint32_t kDefaultPluginUnloadingTimeout = 30;
+
 // Version of cached plugin info
 // 0.01 first implementation
 // 0.02 added caching of CanUnload to fix bug 105935
 // 0.03 changed name, description and mime desc from string to bytes, bug 108246
 // 0.04 added new mime entry point on Mac, bug 113464
 // 0.05 added new entry point check for the default plugin, bug 132430
 // 0.06 strip off suffixes in mime description strings, bug 53895
 // 0.07 changed nsIRegistry to flat file support for caching plugins info
@@ -221,17 +226,17 @@ bool ReadSectionHeader(nsPluginManifestL
       return true;
     }
   } while (reader.NextLine());
   return false;
 }
 
 static bool UnloadPluginsASAP()
 {
-  return Preferences::GetBool("dom.ipc.plugins.unloadASAP", false);
+  return (Preferences::GetUint(kPrefUnloadPluginTimeoutSecs, kDefaultPluginUnloadingTimeout) == 0);
 }
 
 nsPluginHost::nsPluginHost()
   // No need to initialize members to nullptr, false etc because this class
   // has a zeroing operator new.
 {
   // check to see if pref is set at startup to let plugins take over in
   // full page mode for certain image mime types that we handle internally
@@ -722,17 +727,21 @@ void nsPluginHost::OnPluginInstanceDestr
     if (UnloadPluginsASAP()) {
       aPluginTag->TryUnloadPlugin(false);
     } else {
       if (aPluginTag->mUnloadTimer) {
         aPluginTag->mUnloadTimer->Cancel();
       } else {
         aPluginTag->mUnloadTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
       }
-      aPluginTag->mUnloadTimer->InitWithCallback(this, 1000 * 60 * 3, nsITimer::TYPE_ONE_SHOT);
+      uint32_t unloadTimeout = Preferences::GetUint(kPrefUnloadPluginTimeoutSecs,
+                                                    kDefaultPluginUnloadingTimeout);
+      aPluginTag->mUnloadTimer->InitWithCallback(this,
+                                                 1000 * unloadTimeout,
+                                                 nsITimer::TYPE_ONE_SHOT);
     }
   }
 }
 
 nsresult
 nsPluginHost::GetPluginTempDir(nsIFile **aDir)
 {
   if (!sPluginTempDir) {
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2770,17 +2770,23 @@ NS_IMETHODIMP nsPluginInstanceOwner::Cre
   // Can't call this twice!
   if (mWidget) {
     NS_WARNING("Trying to create a plugin widget twice!");
     return NS_ERROR_FAILURE;
   }
   
   bool windowless = false;
   mInstance->IsWindowless(&windowless);
-  if (!windowless && !nsIWidget::UsePuppetWidgets()) {
+  if (!windowless
+// Mac plugins use the widget for setting up the plugin window and event handling.
+// We want to use the puppet widgets there with e10s
+#ifndef XP_MACOSX
+      && !nsIWidget::UsePuppetWidgets()
+#endif
+      ) {
     // Try to get a parent widget, on some platforms widget creation will fail without
     // a parent.
     nsCOMPtr<nsIWidget> parentWidget;
     nsIDocument *doc = nullptr;
     if (mContent) {
       doc = mContent->OwnerDoc();
       parentWidget = nsContentUtils::WidgetForDocument(doc);
     }
@@ -2885,60 +2891,77 @@ void* nsPluginInstanceOwner::FixUpPlugin
     SetPluginPortAndDetectChange();
   }
 
   // We'll need the top-level Cocoa window for the Cocoa event model.
   nsIWidget* widget = mObjectFrame->GetNearestWidget();
   if (!widget)
     return nullptr;
   void *cocoaTopLevelWindow = widget->GetNativeData(NS_NATIVE_WINDOW);
-  if (!cocoaTopLevelWindow)
+  // We don't expect to have a top level window in a content process
+  if (!cocoaTopLevelWindow && XRE_GetProcessType() == GeckoProcessType_Default) {
     return nullptr;
+  }
 
   nsIntPoint pluginOrigin;
   nsIntRect widgetClip;
   bool widgetVisible;
   pluginWidget->GetPluginClipRect(widgetClip, pluginOrigin, widgetVisible);
+  // TODO: Detect visibility for e10s mac plugins
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    widgetVisible = true;
+  }
   mWidgetVisible = widgetVisible;
 
   // printf("GetPluginClipRect returning visible %d\n", widgetVisible);
 
   // This would be a lot easier if we could use obj-c here,
   // but we can't. Since we have only nsIWidget and we can't
   // use its native widget (an obj-c object) we have to go
   // from the widget's screen coordinates to its window coords
   // instead of straight to window coords.
   nsIntPoint geckoScreenCoords = mWidget->WidgetToScreenOffset();
 
   nsRect windowRect;
-  NS_NPAPI_CocoaWindowFrame(cocoaTopLevelWindow, windowRect);
+  if (cocoaTopLevelWindow) {
+    NS_NPAPI_CocoaWindowFrame(cocoaTopLevelWindow, windowRect);
+  }
 
   double scaleFactor = 1.0;
   GetContentsScaleFactor(&scaleFactor);
   int intScaleFactor = ceil(scaleFactor);
 
   // Convert geckoScreenCoords from device pixels to "display pixels"
   // for HiDPI modes.
   mPluginWindow->x = geckoScreenCoords.x/intScaleFactor - windowRect.x;
   mPluginWindow->y = geckoScreenCoords.y/intScaleFactor - windowRect.y;
 
   NPRect oldClipRect = mPluginWindow->clipRect;
-  
+
   // fix up the clipping region
   mPluginWindow->clipRect.top    = widgetClip.y;
   mPluginWindow->clipRect.left   = widgetClip.x;
 
   if (!mWidgetVisible || inPaintState == ePluginPaintDisable) {
     mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top;
     mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left;
   }
+  else if (XRE_GetProcessType() != GeckoProcessType_Default)
+  {
+    // For e10s we only support async windowless plugin. This means that
+    // we're always going to allocate a full window for the plugin to draw
+    // for even if the plugin is mostly outside of the scroll port. Thus
+    // we never trim the window to the bounds of the widget.
+    mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top + mPluginWindow->height;
+    mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left + mPluginWindow->width;
+  }
   else if (inPaintState == ePluginPaintEnable)
   {
     mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top + widgetClip.height;
-    mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left + widgetClip.width; 
+    mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left + widgetClip.width;
   }
 
   // if the clip rect changed, call SetWindow()
   // (RealPlayer needs this to draw correctly)
   if (mPluginWindow->clipRect.left    != oldClipRect.left   ||
       mPluginWindow->clipRect.top     != oldClipRect.top    ||
       mPluginWindow->clipRect.right   != oldClipRect.right  ||
       mPluginWindow->clipRect.bottom  != oldClipRect.bottom ||
@@ -2958,17 +2981,17 @@ void* nsPluginInstanceOwner::FixUpPlugin
   if (!mSentInitialTopLevelWindowEvent) {
     // Set this before calling ProcessEvent to avoid endless recursion.
     mSentInitialTopLevelWindowEvent = true;
 
     WidgetPluginEvent pluginEvent(true, NS_PLUGIN_FOCUS_EVENT, nullptr);
     NPCocoaEvent cocoaEvent;
     InitializeNPCocoaEvent(&cocoaEvent);
     cocoaEvent.type = NPCocoaEventWindowFocusChanged;
-    cocoaEvent.data.focus.hasFocus = NS_NPAPI_CocoaWindowIsMain(cocoaTopLevelWindow);
+    cocoaEvent.data.focus.hasFocus = cocoaTopLevelWindow ? NS_NPAPI_CocoaWindowIsMain(cocoaTopLevelWindow) : true;
     pluginEvent.pluginEvent = &cocoaEvent;
     ProcessEvent(pluginEvent);
   }
 
   return nullptr;
 }
 
 void
--- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -666,27 +666,57 @@ GonkGPSGeolocationProvider::NetworkLocat
     GonkGPSGeolocationProvider::GetSingleton();
 
   nsCOMPtr<nsIDOMGeoPositionCoords> coords;
   position->GetCoords(getter_AddRefs(coords));
   if (!coords) {
     return NS_ERROR_FAILURE;
   }
 
-  // if we haven't seen anything from the GPS device for 1s,
-  // use this network derived location.
-  int64_t diff = PR_Now() - provider->mLastGPSDerivedLocationTime;
-  if (provider->mLocationCallback && diff > kDefaultPeriod) {
-    provider->mLocationCallback->Update(position);
-  }
-
   double lat, lon, acc;
   coords->GetLatitude(&lat);
   coords->GetLongitude(&lon);
   coords->GetAccuracy(&acc);
+
+  double delta = MAXFLOAT;
+
+  static double sLastMLSPosLat = 0;
+  static double sLastMLSPosLon = 0;
+
+  if (0 != sLastMLSPosLon || 0 != sLastMLSPosLat) {
+    // Use spherical law of cosines to calculate difference
+    // Not quite as correct as the Haversine but simpler and cheaper
+    // Should the following be a utility function? Others might need this calc.
+    const double radsInDeg = 3.14159265 / 180.0;
+    const double rNewLat = lat * radsInDeg;
+    const double rNewLon = lon * radsInDeg;
+    const double rOldLat = sLastMLSPosLat * radsInDeg;
+    const double rOldLon = sLastMLSPosLon * radsInDeg;
+    // WGS84 equatorial radius of earth = 6378137m
+    delta = acos( (sin(rNewLat) * sin(rOldLat)) +
+                  (cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon)) )
+                  * 6378137;
+  }
+
+  sLastMLSPosLat = lat;
+  sLastMLSPosLon = lon;
+
+  // if the MLS coord change is smaller than this arbitrarily small value
+  // assume the MLS coord is unchanged, and stick with the GPS location
+  const double kMinMLSCoordChangeInMeters = 10;
+
+  // if we haven't seen anything from the GPS device for 1s,
+  // use this network derived location.
+  int64_t diff = PR_Now() - provider->mLastGPSDerivedLocationTime;
+  if (provider->mLocationCallback && diff > kDefaultPeriod
+      && delta > kMinMLSCoordChangeInMeters)
+  {
+    provider->mLocationCallback->Update(position);
+  }
+
   provider->InjectLocation(lat, lon, acc);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GonkGPSGeolocationProvider::NetworkLocationUpdate::LocationUpdatePending()
 {
   return NS_OK;
--- a/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
+++ b/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
@@ -14,61 +14,58 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 /****************************************************************
  ****************** nsAutoWindowStateHelper *********************
  ****************************************************************/
 
-nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsIDOMWindow *aWindow)
+nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsPIDOMWindow *aWindow)
   : mWindow(aWindow),
     mDefaultEnabled(DispatchEventToChrome("DOMWillOpenModalDialog"))
 {
-  nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
-
-  if (window) {
-    window->EnterModalState();
+  if (mWindow) {
+    mWindow->EnterModalState();
   }
 }
 
 nsAutoWindowStateHelper::~nsAutoWindowStateHelper()
 {
-  nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mWindow));
-
-  if (window) {
-    window->LeaveModalState();
+  if (mWindow) {
+    mWindow->LeaveModalState();
   }
 
   if (mDefaultEnabled) {
     DispatchEventToChrome("DOMModalDialogClosed");
   }
 }
 
 bool
 nsAutoWindowStateHelper::DispatchEventToChrome(const char *aEventName)
 {
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mWindow);
-  if (!window || (window->IsInnerWindow() && !window->IsCurrentInnerWindow())) {
+  // XXXbz should we skip dispatching the event if the inner changed?
+  // That is, should we store both the inner and the outer?
+  if (!mWindow) {
     return true;
   }
 
   // The functions of nsContentUtils do not provide the required behavior,
   // so the following is inlined.
-  nsIDocument* doc = window->GetExtantDoc();
+  nsIDocument* doc = mWindow->GetExtantDoc();
   if (!doc) {
     return true;
   }
 
   ErrorResult rv;
   nsRefPtr<Event> event = doc->CreateEvent(NS_LITERAL_STRING("Events"), rv);
   if (rv.Failed()) {
     return false;
   }
   NS_ENSURE_TRUE(NS_SUCCEEDED(event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), true, true)), false);
   event->SetTrusted(true);
   event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
 
-  nsCOMPtr<EventTarget> target = do_QueryInterface(window);
+  nsCOMPtr<EventTarget> target = do_QueryInterface(mWindow);
   bool defaultActionEnabled;
   target->DispatchEvent(event, &defaultActionEnabled);
   return defaultActionEnabled;
 }
--- a/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.h
+++ b/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.h
@@ -2,36 +2,37 @@
 /* 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/. */