Merge m-c to fx-team.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 19 Aug 2013 11:54:12 -0400
changeset 143073 5e29096a0d1243482ec16d8c1fbf3d7afe97218c
parent 143072 7d3631d1c8cf1b169c51b9037cae925e97af96b2 (current diff)
parent 143051 9b4c4e56f4bb76d04bd2119bd08d05cd03316823 (diff)
child 143074 c1fb5aec320129a2248bef42481ee5795aab4286
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
milestone26.0a1
Merge m-c to fx-team.
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -30,16 +30,17 @@ pref("metro.debug.selection.dumpEvents",
 
 // Enable tab-modal prompts
 pref("prompts.tab_modal.enabled", true);
 
 
 // Enable off main thread compositing
 pref("layers.offmainthreadcomposition.enabled", true);
 pref("layers.async-pan-zoom.enabled", false);
+pref("layers.componentalpha.enabled", false);
 pref("gfx.axis.fling_friction", "0.002");
 
 // Enable Microsoft TSF support by default for imes.
 pref("intl.enable_tsf_support", true);
 
 pref("general.autoScroll", true);
 pref("general.smoothScroll", true);
 pref("general.smoothScroll.durationToIntervalRatio", 200);
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -610,16 +610,18 @@ MOCHITEST_FILES_C= \
 		bug803225_test_mailto.html \
 		test_mixed_content_blocker_frameNavigation.html \
 		file_mixed_content_frameNavigation.html \
 		file_mixed_content_frameNavigation_innermost.html \
 		file_mixed_content_frameNavigation_grandchild.html \
 		file_mixed_content_frameNavigation_secure.html \
 		file_mixed_content_frameNavigation_secure_grandchild.html \
 		file_mixed_content_frameNavigation_blankTarget.html \
+		file_bug902350.html \
+		file_bug902350_frame.html \
 		test_bug789856.html \
 		file_bug804395.jar \
 		test_bug804395.html \
 		test_bug809003.html \
 		test_bug810494.html \
 		test_bug819051.html \
 		bug819051.sjs \
 		test_textnode_split_in_selection.html \
@@ -700,12 +702,13 @@ MOCHITEST_FILES_PARTS = $(foreach s,A B 
 
 # Disabled for frequent failures (bug 841505, bug 842344, etc)
 #		test_XHR_timeout.html \
 #		test_XHR_timeout.js \
 #		file_XHR_timeout.sjs \
 
 MOCHITEST_BROWSER_FILES = \
 		browser_bug593387.js \
+		browser_bug902350.js \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/content/base/test/browser_bug902350.js
@@ -0,0 +1,65 @@
+/*
+ * Mixed Content Block frame navigates for target="_top" - Test for Bug 902350
+ */
+
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+const gHttpTestRoot = "https://example.com/tests/content/base/test/";
+var origBlockActive;
+var gTestBrowser = null;
+
+registerCleanupFunction(function() {
+  // Set preferences back to their original values
+  Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+});
+
+function MixedTestsCompleted() {
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
+
+function test() {
+  waitForExplicitFinish();
+
+  origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+
+  Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+  var newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  gTestBrowser = gBrowser.selectedBrowser;
+  newTab.linkedBrowser.stop()
+
+  gTestBrowser.addEventListener("load", MixedTest1A, true);
+  var url = gHttpTestRoot + "file_bug902350.html";
+  gTestBrowser.contentWindow.location = url;
+}
+
+// Need to capture 2 loads, one for the main page and one for the iframe
+function MixedTest1A() {
+  gTestBrowser.removeEventListener("load", MixedTest1A, true);
+  gTestBrowser.addEventListener("load", MixedTest1B, true);
+}
+
+// Find the iframe and click the link in it
+function MixedTest1B() {
+  gTestBrowser.removeEventListener("load", MixedTest1B, true);
+  gTestBrowser.addEventListener("load", MixedTest1C, true);
+  var frame = content.document.getElementById("testing_frame");
+  var topTarget = frame.contentWindow.document.getElementById("topTarget");
+  topTarget.click();
+
+  // The link click should have caused a load and should not invoke the Mixed Content Blocker
+  var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+  ok(!notification, "Mixed Content Doorhanger appears when trying to navigate top");
+
+}
+
+function MixedTest1C() {
+  gTestBrowser.removeEventListener("load", MixedTest1C, true);
+  ok(gTestBrowser.contentWindow.location == "http://example.com/", "Navigating to insecure domain through target='_top' failed.")
+  MixedTestsCompleted();
+}
+
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_bug902350.html
@@ -0,0 +1,19 @@
+<DOCTYPE HTML>
+<html>
+<!--
+Test for Mixed Content Blocker target="_top" frame navigation
+https://bugzilla.mozilla.org/show_bug.cgi?id=902350
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 902350</title>
+</head>
+
+<body>
+  <div id="framediv">
+    <iframe src="https://example.com/tests/content/base/test/file_bug902350_frame.html" id="testing_frame"></iframe>
+  </div>
+</body>
+</html>
+
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_bug902350_frame.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker - Opening link with _top target from an https iframe.
+https://bugzilla.mozilla.org/show_bug.cgi?id=902350
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Tests for Mixed Content Frame Navigation</title>
+</head>
+<body>
+<a href="http://example.com/" id="topTarget" target="_top">Go to http site</a>
+</body>
+</html>
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -8630,29 +8630,45 @@ nsDocShell::InternalLoad(nsIURI * aURI,
     if (mScriptGlobal)
         requestingElement = mScriptGlobal->GetFrameElementInternal();
 
     nsRefPtr<nsGlobalWindow> MMADeathGrip = mScriptGlobal;
 
     int16_t shouldLoad = nsIContentPolicy::ACCEPT;
     uint32_t contentType;
     bool isNewDocShell = false;
+    bool isTargetTopLevelDocShell = false;
     nsCOMPtr<nsIDocShell> targetDocShell;
     if (aWindowTarget && *aWindowTarget) {
         // Locate the target DocShell.
         nsCOMPtr<nsIDocShellTreeItem> targetItem;
         FindItemWithName(aWindowTarget, nullptr, this,
                          getter_AddRefs(targetItem));
 
         targetDocShell = do_QueryInterface(targetItem);
         // If the targetDocShell doesn't exist, then this is a new docShell
         // and we should consider this a TYPE_DOCUMENT load
         isNewDocShell = !targetDocShell;
-    }
-    if (IsFrame() && !isNewDocShell) {
+
+        // If the targetDocShell and the rootDocShell are the same, then the
+        // targetDocShell is the top level document and hence we should
+        // consider this TYPE_DOCUMENT
+        if (targetDocShell) {
+          nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
+          targetDocShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
+          NS_ASSERTION(sameTypeRoot, "No document shell root tree item from targetDocShell!");
+          nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(sameTypeRoot);
+          NS_ASSERTION(rootShell, "No root docshell from document shell root tree item.");
+
+          if (targetDocShell == rootShell) {
+            isTargetTopLevelDocShell = true;
+          }
+        }
+    }
+    if (IsFrame() && !isNewDocShell && !isTargetTopLevelDocShell) {
         NS_ASSERTION(requestingElement, "A frame but no DOM element!?");
         contentType = nsIContentPolicy::TYPE_SUBDOCUMENT;
     } else {
         contentType = nsIContentPolicy::TYPE_DOCUMENT;
     }
 
     nsISupports* context = requestingElement;
     if (!context) {
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -184,16 +184,37 @@ nsJSUtils::CompileFunction(JSContext* aC
     ReportPendingException(aCx);
     return NS_ERROR_FAILURE;
   }
 
   *aFunctionObject = JS_GetFunctionObject(fun);
   return NS_OK;
 }
 
+class MOZ_STACK_CLASS AutoDontReportUncaught {
+  JSContext* mContext;
+  bool mWasSet;
+
+public:
+  AutoDontReportUncaught(JSContext* aContext) : mContext(aContext) {
+    MOZ_ASSERT(aContext);
+    uint32_t oldOptions = JS_GetOptions(mContext);
+    mWasSet = oldOptions & JSOPTION_DONT_REPORT_UNCAUGHT;
+    if (!mWasSet) {
+      JS_SetOptions(mContext, oldOptions | JSOPTION_DONT_REPORT_UNCAUGHT);
+    }
+  }
+  ~AutoDontReportUncaught() {
+    if (!mWasSet) {
+      JS_SetOptions(mContext,
+                    JS_GetOptions(mContext) & ~JSOPTION_DONT_REPORT_UNCAUGHT);
+    }
+  }
+};
+
 nsresult
 nsJSUtils::EvaluateString(JSContext* aCx,
                           const nsAString& aScript,
                           JS::Handle<JSObject*> aScopeObject,
                           JS::CompileOptions& aCompileOptions,
                           EvaluateOptions& aEvaluateOptions,
                           JS::Value* aRetValue)
 {
@@ -219,16 +240,23 @@ nsJSUtils::EvaluateString(JSContext* aCx
   aCompileOptions.setPrincipals(p);
 
   bool ok = false;
   nsresult rv = nsContentUtils::GetSecurityManager()->
                   CanExecuteScripts(aCx, nsJSPrincipals::get(p), &ok);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(ok, NS_OK);
 
+  mozilla::Maybe<AutoDontReportUncaught> dontReport;
+  if (!aEvaluateOptions.reportUncaught) {
+    // We need to prevent AutoLastFrameCheck from reporting and clearing
+    // any pending exceptions.
+    dontReport.construct(aCx);
+  }
+
   // Scope the JSAutoCompartment so that we can later wrap the return value
   // into the caller's cx.
   {
     JSAutoCompartment ac(aCx, aScopeObject);
 
     JS::RootedObject rootedScope(aCx, aScopeObject);
     ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
                       PromiseFlatString(aScript).get(),
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -179,16 +179,17 @@ AppendToString(nsACString& s, const Matr
 
 nsACString&
 AppendToString(nsACString& s, const Filter filter,
                const char* pfx, const char* sfx)
 {
   s += pfx;
 
   switch (filter) {
+    case FILTER_GOOD: s += "FILTER_GOOD"; break;
     case FILTER_LINEAR: s += "FILTER_LINEAR"; break;
     case FILTER_POINT: s += "FILTER_POINT"; break;
   }
   return s += sfx;
 }
 
 nsACString&
 AppendToString(nsACString& s, TextureFlags flags,
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -315,38 +315,43 @@ DeprecatedTextureClientShmem::EnsureAllo
     mContentType = aContentType;
     mSize = aSize;
 
     if (!mForwarder->AllocSurfaceDescriptor(gfxIntSize(mSize.width, mSize.height),
                                             mContentType, &mDescriptor)) {
       NS_WARNING("creating SurfaceDescriptor failed!");
     }
     if (mContentType == gfxASurface::CONTENT_COLOR_ALPHA) {
-      nsRefPtr<gfxContext> context = new gfxContext(GetSurface());
+      gfxASurface* surface = GetSurface();
+      if (!surface) {
+        return false;
+      }
+      nsRefPtr<gfxContext> context = new gfxContext(surface);
       context->SetColor(gfxRGBA(0, 0, 0, 0));
       context->SetOperator(gfxContext::OPERATOR_SOURCE);
       context->Paint();
     }
   }
   return true;
 }
 
 void
 DeprecatedTextureClientShmem::SetDescriptor(const SurfaceDescriptor& aDescriptor)
 {
-  if (IsSurfaceDescriptorValid(aDescriptor)) {
-    ReleaseResources();
-    mDescriptor = aDescriptor;
-  } else {
+  if (aDescriptor.type() == SurfaceDescriptor::Tnull_t) {
     EnsureAllocated(mSize, mContentType);
+    return;
   }
 
-  MOZ_ASSERT(!mSurface);
+  ReleaseResources();
+  mDescriptor = aDescriptor;
 
-  NS_ASSERTION(mDescriptor.type() == SurfaceDescriptor::TSurfaceDescriptorGralloc ||
+  MOZ_ASSERT(!mSurface);
+  NS_ASSERTION(mDescriptor.type() == SurfaceDescriptor::T__None ||
+               mDescriptor.type() == SurfaceDescriptor::TSurfaceDescriptorGralloc ||
                mDescriptor.type() == SurfaceDescriptor::TShmem ||
                mDescriptor.type() == SurfaceDescriptor::TMemoryImage ||
                mDescriptor.type() == SurfaceDescriptor::TRGBImage,
                "Invalid surface descriptor");
 }
 
 gfxASurface*
 DeprecatedTextureClientShmem::GetSurface()
@@ -370,16 +375,20 @@ DeprecatedTextureClientShmem::GetSurface
 gfx::DrawTarget*
 DeprecatedTextureClientShmem::LockDrawTarget()
 {
   if (mDrawTarget) {
     return mDrawTarget;
   }
 
   gfxASurface* surface = GetSurface();
+  if (!surface) {
+    return nullptr;
+  }
+
   mDrawTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, mSize);
 
   return mDrawTarget;
 }
 
 void
 DeprecatedTextureClientShmem::Unlock()
 {
@@ -389,17 +398,21 @@ DeprecatedTextureClientShmem::Unlock()
 
   ShadowLayerForwarder::CloseDescriptor(mDescriptor);
 }
 
 gfxImageSurface*
 DeprecatedTextureClientShmem::LockImageSurface()
 {
   if (!mSurfaceAsImage) {
-    mSurfaceAsImage = GetSurface()->GetAsImageSurface();
+    gfxASurface* surface = GetSurface();
+    if (!surface) {
+      return nullptr;
+    }
+    mSurfaceAsImage = surface->GetAsImageSurface();
   }
 
   return mSurfaceAsImage.get();
 }
 
 DeprecatedTextureClientTile::DeprecatedTextureClientTile(const DeprecatedTextureClientTile& aOther)
   : DeprecatedTextureClient(aOther.mForwarder, aOther.mTextureInfo)
   , mSurface(aOther.mSurface)
@@ -412,23 +425,23 @@ void
 DeprecatedTextureClientShmemYCbCr::ReleaseResources()
 {
   GetForwarder()->DestroySharedSurface(&mDescriptor);
 }
 
 void
 DeprecatedTextureClientShmemYCbCr::SetDescriptor(const SurfaceDescriptor& aDescriptor)
 {
-  MOZ_ASSERT(aDescriptor.type() == SurfaceDescriptor::TYCbCrImage);
+  MOZ_ASSERT(aDescriptor.type() == SurfaceDescriptor::TYCbCrImage ||
+             aDescriptor.type() == SurfaceDescriptor::T__None);
 
   if (IsSurfaceDescriptorValid(mDescriptor)) {
     GetForwarder()->DestroySharedSurface(&mDescriptor);
   }
   mDescriptor = aDescriptor;
-  MOZ_ASSERT(IsSurfaceDescriptorValid(mDescriptor));
 }
 
 void
 DeprecatedTextureClientShmemYCbCr::SetDescriptorFromReply(const SurfaceDescriptor& aDescriptor)
 {
   MOZ_ASSERT(aDescriptor.type() == SurfaceDescriptor::TYCbCrImage);
   DeprecatedSharedPlanarYCbCrImage* shYCbCr = DeprecatedSharedPlanarYCbCrImage::FromSurfaceDescriptor(aDescriptor);
   if (shYCbCr) {
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -357,27 +357,28 @@ gfxPlatform::Init()
 #else
     #error "No gfxPlatform implementation available"
 #endif
 
 #ifdef DEBUG
     mozilla::gl::GLContext::StaticInit();
 #endif
 
-    bool useOffMainThreadCompositing = GetPrefLayersOffMainThreadCompositionEnabled() ||
-        Preferences::GetBool("browser.tabs.remote", false);
-    useOffMainThreadCompositing &= GetPlatform()->SupportsOffMainThreadCompositing();
+    bool useOffMainThreadCompositing = OffMainThreadCompositionRequired() ||
+                                       GetPrefLayersOffMainThreadCompositionEnabled();
 
-    if (useOffMainThreadCompositing && (XRE_GetProcessType() ==
-                                        GeckoProcessType_Default)) {
+    if (!OffMainThreadCompositionRequired()) {
+      useOffMainThreadCompositing &= GetPlatform()->SupportsOffMainThreadCompositing();
+    }
+
+    if (useOffMainThreadCompositing && (XRE_GetProcessType() == GeckoProcessType_Default)) {
         CompositorParent::StartUp();
-        if (Preferences::GetBool("layers.async-video.enabled",false)) {
+        if (Preferences::GetBool("layers.async-video.enabled", false)) {
             ImageBridgeChild::StartUp();
         }
-
     }
 
     nsresult rv;
 
 #if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) // temporary, until this is implemented on others
     rv = gfxPlatformFontList::Init();
     if (NS_FAILED(rv)) {
         NS_RUNTIMEABORT("Could not initialize gfxPlatformFontList");
@@ -1881,16 +1882,17 @@ static bool sPrefLayersOffMainThreadComp
 static bool sPrefLayersAccelerationForceEnabled = false;
 static bool sPrefLayersAccelerationDisabled = false;
 static bool sPrefLayersPreferOpenGL = false;
 static bool sPrefLayersPreferD3D9 = false;
 static bool sLayersSupportsD3D9 = true;
 static int  sPrefLayoutFrameRate = -1;
 static bool sBufferRotationEnabled = false;
 static bool sComponentAlphaEnabled = true;
+static bool sPrefBrowserTabsRemote = false;
 
 static bool sLayersAccelerationPrefsInitialized = false;
 
 void
 InitLayersAccelerationPrefs()
 {
   if (!sLayersAccelerationPrefsInitialized)
   {
@@ -1899,16 +1901,17 @@ InitLayersAccelerationPrefs()
     sPrefLayersOffMainThreadCompositionForceEnabled = Preferences::GetBool("layers.offmainthreadcomposition.force-enabled", false);
     sPrefLayersAccelerationForceEnabled = Preferences::GetBool("layers.acceleration.force-enabled", false);
     sPrefLayersAccelerationDisabled = Preferences::GetBool("layers.acceleration.disabled", false);
     sPrefLayersPreferOpenGL = Preferences::GetBool("layers.prefer-opengl", false);
     sPrefLayersPreferD3D9 = Preferences::GetBool("layers.prefer-d3d9", false);
     sPrefLayoutFrameRate = Preferences::GetInt("layout.frame_rate", -1);
     sBufferRotationEnabled = Preferences::GetBool("layers.bufferrotation.enabled", true);
     sComponentAlphaEnabled = Preferences::GetBool("layers.componentalpha.enabled", true);
+    sPrefBrowserTabsRemote = Preferences::GetBool("browser.tabs.remote", false);
 
     nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
     if (gfxInfo) {
       int32_t status;
       if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status))) {
         if (status != nsIGfxInfo::FEATURE_NO_INFO && !sPrefLayersAccelerationForceEnabled) {
           sLayersSupportsD3D9 = false;
         }
@@ -1937,16 +1940,22 @@ gfxPlatform::GetPrefLayersOffMainThreadC
 
 bool
 gfxPlatform::GetPrefLayersAccelerationForceEnabled()
 {
   InitLayersAccelerationPrefs();
   return sPrefLayersAccelerationForceEnabled;
 }
 
+bool gfxPlatform::OffMainThreadCompositionRequired()
+{
+  InitLayersAccelerationPrefs();
+  return sPrefBrowserTabsRemote;
+}
+
 bool
 gfxPlatform::GetPrefLayersAccelerationDisabled()
 {
   InitLayersAccelerationPrefs();
   return sPrefLayersAccelerationDisabled;
 }
 
 bool
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -482,16 +482,18 @@ public:
     static bool GetPrefLayersOffMainThreadCompositionForceEnabled();
     static bool GetPrefLayersAccelerationForceEnabled();
     static bool GetPrefLayersAccelerationDisabled();
     static bool GetPrefLayersPreferOpenGL();
     static bool GetPrefLayersPreferD3D9();
     static bool CanUseDirect3D9();
     static int  GetPrefLayoutFrameRate();
 
+    static bool OffMainThreadCompositionRequired();
+
     /**
      * Is it possible to use buffer rotation
      */
     static bool BufferRotationEnabled();
     static void DisableBufferRotation();
 
     static bool ComponentAlphaEnabled();
 
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -497,17 +497,18 @@ gfxPlatformGtk::GetScreenDepth() const
     }
 
     return sDepth;
 }
 
 bool
 gfxPlatformGtk::SupportsOffMainThreadCompositing()
 {
-#ifdef MOZ_X11
+  // Nightly builds have OMTC support by default for Electrolysis testing.
+#if defined(MOZ_X11) && !defined(NIGHTLY_BUILD)
   return (PR_GetEnv("MOZ_USE_OMTC") != nullptr) ||
          (PR_GetEnv("MOZ_OMTC_ENABLED") != nullptr);
 #else
   return true;
 #endif
 }
 
 qcms_profile *
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -223,19 +223,22 @@ inline void
 InterpreterStack::purge(JSRuntime *rt)
 {
     rt->freeLifoAlloc.transferUnusedFrom(&allocator_);
 }
 
 uint8_t *
 InterpreterStack::allocateFrame(JSContext *cx, size_t size)
 {
-    size_t maxFrames = cx->compartment()->principals == cx->runtime()->trustedPrincipals()
-                       ? MAX_FRAMES_TRUSTED
-                       : MAX_FRAMES;
+    size_t maxFrames;
+    if (cx->compartment()->principals == cx->runtime()->trustedPrincipals())
+        maxFrames = MAX_FRAMES_TRUSTED;
+    else
+        maxFrames = MAX_FRAMES;
+
     if (JS_UNLIKELY(frameCount_ >= maxFrames)) {
         js_ReportOverRecursed(cx);
         return NULL;
     }
 
     uint8_t *buffer = reinterpret_cast<uint8_t *>(allocator_.alloc(size));
     if (!buffer)
         return NULL;
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -32,16 +32,18 @@
 #include "nsPrincipal.h"
 #include "mozilla/Attributes.h"
 #include "nsIScriptContext.h"
 #include "nsJSEnvironment.h"
 #include "nsXMLHttpRequest.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/XPTInterfaceInfoManager.h"
 #include "nsDOMClassInfoID.h"
+#include "nsGlobalWindow.h"
+
 
 using namespace mozilla;
 using namespace js;
 using namespace xpc;
 
 using mozilla::dom::DestroyProtoAndIfaceCache;
 
 /***************************************************************************/
@@ -2930,16 +2932,363 @@ CreateXMLHttpRequest(JSContext *cx, unsi
 
     rv = nsContentUtils::WrapNative(cx, global, xhr, vp);
     if (NS_FAILED(rv))
         return false;
 
     return true;
 }
 
+bool
+NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable,
+                     bool doclone, MutableHandleValue vp);
+
+/*
+ * Instead of simply wrapping a function into another compartment,
+ * this helper function creates a native function in the target
+ * compartment and forwards the call to the original function.
+ * That call will be different than a regular JS function call in
+ * that, the |this| is left unbound, and all the non-native JS
+ * object arguments will be cloned using the structured clone
+ * algorithm.
+ * The return value is the new forwarder function, wrapped into
+ * the caller's compartment.
+ * The 3rd argument is the name of the property that will
+ * be set on the target scope, with the forwarder function as
+ * the value.
+ * The principal of the caller must subsume that of the target.
+ *
+ * Expected type of the arguments and the return value:
+ * function exportFunction(function funToExport,
+ *                         object targetScope,
+ *                         string name)
+ */
+static bool
+ExportFunction(JSContext *cx, unsigned argc, jsval *vp)
+{
+    MOZ_ASSERT(cx);
+    if (argc < 3) {
+        JS_ReportError(cx, "Function requires at least 3 arguments");
+        return false;
+    }
+
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args[0].isObject() || !args[1].isObject() || !args[2].isString()) {
+        JS_ReportError(cx, "Invalid argument");
+        return false;
+    }
+
+    RootedObject funObj(cx, &args[0].toObject());
+    RootedObject targetScope(cx, &args[1].toObject());
+    RootedString funName(cx, args[2].toString());
+
+    // We can only export functions to scopes those are transparent for us,
+    // so if there is a security wrapper around targetScope we must throw.
+    targetScope = CheckedUnwrap(targetScope);
+    if (!targetScope) {
+        JS_ReportError(cx, "Permission denied to export function into scope");
+        return false;
+    }
+
+    if (JS_GetStringLength(funName) == 0) {
+        JS_ReportError(cx, "3rd argument should be a non-empty string");
+        return false;
+    }
+
+    {
+        // We need to operate in the target scope from here on, let's enter
+        // its compartment.
+        JSAutoCompartment ac(cx, targetScope);
+
+        // Unwrapping to see if we have a callable.
+        funObj = UncheckedUnwrap(funObj);
+        if (!JS_ObjectIsCallable(cx, funObj)) {
+            JS_ReportError(cx, "First argument must be a function");
+            return false;
+        }
+
+        // The function forwarder will live in the target compartment. Since
+        // this function will be referenced from its private slot, to avoid a
+        // GC hazard, we must wrap it to the same compartment.
+        if (!JS_WrapObject(cx, funObj.address()))
+            return false;
+
+        RootedId id(cx);
+        if (!JS_ValueToId(cx, args[2], id.address()))
+            return false;
+
+        // And now, let's create the forwarder function in the target compartment
+        // for the function the be exported.
+        if (!NewFunctionForwarder(cx, id, funObj, /* doclone = */ true, args.rval())) {
+            JS_ReportError(cx, "Exporting function failed");
+            return false;
+        }
+
+        // We have the forwarder function in the target compartment, now
+        // we have to add it to the target scope as a property.
+        if (!JS_DefinePropertyById(cx, targetScope, id, args.rval(),
+                                   JS_PropertyStub, JS_StrictPropertyStub,
+                                   JSPROP_ENUMERATE))
+            return false;
+    }
+
+    // Finally we have to re-wrap the exported function back to the caller compartment.
+    if (!JS_WrapValue(cx, args.rval().address()))
+        return false;
+
+    return true;
+}
+
+static bool
+GetFilenameAndLineNumber(JSContext *cx, nsACString &filename, unsigned &lineno)
+{
+    JSScript *script;
+    if (JS_DescribeScriptedCaller(cx, &script, &lineno)) {
+        if (const char *cfilename = JS_GetScriptFilename(cx, script)) {
+            filename.Assign(nsDependentCString(cfilename));
+            return true;
+        }
+    }
+    return false;
+}
+
+namespace xpc {
+bool
+IsReflector(JSObject *obj)
+{
+    return IS_WN_REFLECTOR(obj) || dom::IsDOMObject(obj);
+}
+} /* namespace xpc */
+
+enum ForwarderCloneTags {
+    SCTAG_BASE = JS_SCTAG_USER_MIN,
+    SCTAG_REFLECTOR
+};
+
+static JSObject *
+CloneNonReflectorsRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t tag,
+                       uint32_t data, void *closure)
+{
+    MOZ_ASSERT(closure, "Null pointer!");
+    AutoObjectVector *reflectors = static_cast<AutoObjectVector *>(closure);
+    if (tag == SCTAG_REFLECTOR) {
+        MOZ_ASSERT(!data);
+
+        size_t idx;
+        if (JS_ReadBytes(reader, &idx, sizeof(size_t))) {
+            RootedObject reflector(cx, reflectors->handleAt(idx));
+            MOZ_ASSERT(reflector, "No object pointer?");
+            MOZ_ASSERT(IsReflector(reflector), "Object pointer must be a reflector!");
+
+            JS_WrapObject(cx, reflector.address());
+            JS_ASSERT(WrapperFactory::IsXrayWrapper(reflector) ||
+                      IsReflector(reflector));
+
+            return reflector;
+        }
+    }
+
+    JS_ReportError(cx, "CloneNonReflectorsRead error");
+    return nullptr;
+}
+
+static bool
+CloneNonReflectorsWrite(JSContext *cx, JSStructuredCloneWriter *writer,
+                        Handle<JSObject *> obj, void *closure)
+{
+    MOZ_ASSERT(closure, "Null pointer!");
+
+    // We need to maintain a list of reflectors to make sure all these objects
+    // are properly rooter. Only their indices will be serialized.
+    AutoObjectVector *reflectors = static_cast<AutoObjectVector *>(closure);
+    if (IsReflector(obj)) {
+        if (!reflectors->append(obj))
+            return false;
+
+        size_t idx = reflectors->length()-1;
+        if (JS_WriteUint32Pair(writer, SCTAG_REFLECTOR, 0) &&
+            JS_WriteBytes(writer, &idx, sizeof(size_t))) {
+            return true;
+        }
+    }
+
+    JS_ReportError(cx, "CloneNonReflectorsWrite error");
+    return false;
+}
+
+JSStructuredCloneCallbacks gForwarderStructuredCloneCallbacks = {
+    CloneNonReflectorsRead,
+    CloneNonReflectorsWrite,
+    nullptr
+};
+
+/*
+ * This is a special structured cloning, that clones only non-reflectors.
+ * The function assumes the cx is already entered the compartment we want
+ * to clone to, and that if val is an object is from the compartment we
+ * clone from.
+ */
+bool
+CloneNonReflectors(JSContext *cx, MutableHandleValue val)
+{
+    JSAutoStructuredCloneBuffer buffer;
+    AutoObjectVector rootedReflectors(cx);
+    {
+        // For parsing val we have to enter its compartment.
+        // (unless it's a primitive)
+        Maybe<JSAutoCompartment> ac;
+        if (val.isObject()) {
+            ac.construct(cx, &val.toObject());
+        }
+
+        if (!buffer.write(cx, val,
+            &gForwarderStructuredCloneCallbacks,
+            &rootedReflectors))
+        {
+            return false;
+        }
+    }
+
+    // Now recreate the clones in the target compartment.
+    RootedValue rval(cx);
+    if (!buffer.read(cx, val.address(),
+        &gForwarderStructuredCloneCallbacks,
+        &rootedReflectors))
+    {
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * Similar to evalInSandbox except this one is used to eval a script in the
+ * scope of a window. Also note, that the return value and the possible exceptions
+ * in the script are structured cloned, unless they are natives (then they are just
+ * wrapped).
+ * Principal of the caller must subsume the target's.
+ *
+ * Expected type of the arguments:
+ * value evalInWindow(string script,
+ *                    object window)
+ */
+static bool
+EvalInWindow(JSContext *cx, unsigned argc, jsval *vp)
+{
+    MOZ_ASSERT(cx);
+    if (argc < 2) {
+        JS_ReportError(cx, "Function requires two arguments");
+        return false;
+    }
+
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args[0].isString() || !args[1].isObject()) {
+        JS_ReportError(cx, "Invalid arguments");
+        return false;
+    }
+
+    RootedString srcString(cx, args[0].toString());
+    RootedObject targetScope(cx, &args[1].toObject());
+
+    // If we cannot unwrap we must not eval in it.
+    targetScope = CheckedUnwrap(targetScope);
+    if (!targetScope) {
+        JS_ReportError(cx, "Permission denied to eval in target scope");
+        return false;
+    }
+
+    // Make sure that we have a window object.
+    RootedObject inner(cx, CheckedUnwrap(targetScope, /* stopAtOuter = */ false));
+    nsCOMPtr<nsIGlobalObject> global;
+    nsCOMPtr<nsPIDOMWindow> window;
+    if (!JS_IsGlobalObject(inner) ||
+        !(global = GetNativeForGlobal(inner)) ||
+        !(window = do_QueryInterface(global)))
+    {
+        JS_ReportError(cx, "Second argument must be a window");
+        return false;
+    }
+
+    nsCOMPtr<nsIScriptContext> context =
+        (static_cast<nsGlobalWindow*>(window.get()))->GetScriptContext();
+    if (!context) {
+        JS_ReportError(cx, "Script context needed");
+        return false;
+    }
+
+    if (!context->GetScriptsEnabled()) {
+        JS_ReportError(cx, "Scripts are disabled in this window");
+        return false;
+    }
+
+    nsCString filename;
+    unsigned lineNo;
+    if (!GetFilenameAndLineNumber(cx, filename, lineNo)) {
+        // Default values for non-scripted callers.
+        filename.Assign("Unknown");
+        lineNo = 0;
+    }
+
+    nsDependentJSString srcDepString;
+    srcDepString.init(cx, srcString);
+
+    {
+        // CompileOptions must be created from the context
+        // we will execute this script in.
+        JSContext *wndCx = context->GetNativeContext();
+        AutoCxPusher pusher(wndCx);
+        JS::CompileOptions compileOptions(wndCx);
+        compileOptions.setFileAndLine(filename.get(), lineNo);
+
+        // We don't want the JS engine to automatically report
+        // uncaught exceptions.
+        nsJSUtils::EvaluateOptions evaluateOptions;
+        evaluateOptions.setReportUncaught(false);
+
+        nsresult rv = nsJSUtils::EvaluateString(wndCx,
+                                                srcDepString,
+                                                targetScope,
+                                                compileOptions,
+                                                evaluateOptions,
+                                                args.rval().address());
+
+        if (NS_FAILED(rv)) {
+            // If there was an exception we get it as a return value, if
+            // the evaluation failed for some other reason, then a default
+            // exception is raised.
+            MOZ_ASSERT(!JS_IsExceptionPending(wndCx),
+                       "Exception should be delivered as return value.");
+            if (args.rval().isUndefined()) {
+                MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY);
+                return false;
+            }
+
+            // If there was an exception thrown we should set it
+            // on the calling context.
+            RootedValue exn(wndCx, args.rval());
+            // First we should reset the return value.
+            args.rval().set(UndefinedValue());
+
+            // Then clone the exception.
+            if (CloneNonReflectors(cx, &exn))
+                JS_SetPendingException(cx, exn);
+
+            return false;
+        }
+    }
+
+    // Let's clone the return value back to the callers compartment.
+    if (!CloneNonReflectors(cx, args.rval())) {
+        args.rval().set(UndefinedValue());
+        return false;
+    }
+
+    return true;
+}
+
 static bool
 sandbox_enumerate(JSContext *cx, HandleObject obj)
 {
     return JS_EnumerateStandardClasses(cx, obj);
 }
 
 static bool
 sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id)
@@ -3345,16 +3694,22 @@ xpc_CreateSandboxObject(JSContext *cx, j
           return NS_ERROR_XPC_UNEXPECTED;
 
         if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions))
             return NS_ERROR_XPC_UNEXPECTED;
 
         if (options.wantXHRConstructor &&
             !JS_DefineFunction(cx, sandbox, "XMLHttpRequest", CreateXMLHttpRequest, 0, JSFUN_CONSTRUCTOR))
             return NS_ERROR_XPC_UNEXPECTED;
+
+        if (options.wantExportHelpers &&
+            (!JS_DefineFunction(cx, sandbox, "exportFunction", ExportFunction, 3, 0) ||
+             !JS_DefineFunction(cx, sandbox, "evalInWindow", EvalInWindow, 2, 0)))
+            return NS_ERROR_XPC_UNEXPECTED;
+
     }
 
     if (vp) {
         // We have this crazy behavior where wantXrays=false also implies that the
         // returned sandbox is implicitly waived. We've stopped advertising it, but
         // keep supporting it for now.
         *vp = OBJECT_TO_JSVAL(sandbox);
         if (options.wantXrays && !JS_WrapValue(cx, vp))
@@ -3615,16 +3970,20 @@ ParseOptionsObject(JSContext *cx, jsval 
     rv = GetBoolPropFromOptions(cx, optionsObject,
                                 "wantComponents", &options.wantComponents);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = GetBoolPropFromOptions(cx, optionsObject,
                                 "wantXHRConstructor", &options.wantXHRConstructor);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    rv = GetBoolPropFromOptions(cx, optionsObject,
+                                "wantExportHelpers", &options.wantExportHelpers);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     rv = GetStringPropFromOptions(cx, optionsObject,
                                   "sandboxName", options.sandboxName);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = GetObjPropFromOptions(cx, optionsObject,
                                "sameZoneAs", options.sameZoneAs.address());
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -4200,41 +4559,78 @@ nsXPCComponents_Utils::CreateDateIn(cons
 
     if (!JS_WrapObject(cx, obj.address()))
         return NS_ERROR_FAILURE;
     *rval = ObjectValue(*obj);
     return NS_OK;
 }
 
 bool
-FunctionWrapper(JSContext *cx, unsigned argc, Value *vp)
+NonCloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
     MOZ_ASSERT(v.isObject(), "weird function");
 
     JSObject *obj = JS_THIS_OBJECT(cx, vp);
     if (!obj) {
         return false;
     }
     return JS_CallFunctionValue(cx, obj, v, args.length(), args.array(), vp);
 }
 
+/*
+ * Forwards the call to the exported function. Clones all the non reflectors, ignores
+ * the |this| argument.
+ */
 bool
-WrapCallable(JSContext *cx, HandleObject obj, HandleId id, HandleObject propobj,
-             MutableHandleValue vp)
-{
-    JSFunction *fun = js::NewFunctionByIdWithReserved(cx, FunctionWrapper, 0, 0,
-                                                      JS_GetGlobalForObject(cx, obj), id);
+CloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
+    NS_ASSERTION(v.isObject(), "weird function");
+    RootedObject origFunObj(cx, UncheckedUnwrap(&v.toObject()));
+    {
+        JSAutoCompartment ac(cx, origFunObj);
+        // Note: only the arguments are cloned not the |this| or the |callee|.
+        // Function forwarder does not use those.
+        for (unsigned i = 0; i < args.length(); i++) {
+            if (!CloneNonReflectors(cx, args[i])) {
+                return false;
+            }
+        }
+
+        // JS API does not support any JSObject to JSFunction conversion,
+        // so let's use JS_CallFunctionValue instead.
+        RootedValue functionVal(cx);
+        functionVal.setObject(*origFunObj);
+
+        if (!JS_CallFunctionValue(cx, nullptr, functionVal, args.length(), args.array(), vp))
+            return false;
+    }
+
+    // Return value must be wrapped.
+    return JS_WrapValue(cx, vp);
+}
+
+bool
+NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, bool doclone,
+                     MutableHandleValue vp)
+{
+    JSFunction *fun = js::NewFunctionByIdWithReserved(cx, doclone ? CloningFunctionForwarder :
+                                                                    NonCloningFunctionForwarder,
+                                                                    0,0, JS::CurrentGlobalOrNull(cx), id);
+
     if (!fun)
         return false;
 
     JSObject *funobj = JS_GetFunctionObject(fun);
-    js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*propobj));
+    js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable));
     vp.setObject(*funobj);
     return true;
 }
 
 /* void makeObjectPropsNormal(jsval vobj); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::MakeObjectPropsNormal(const Value &vobj, JSContext *cx)
 {
@@ -4262,17 +4658,17 @@ nsXPCComponents_Utils::MakeObjectPropsNo
         if (v.isPrimitive())
             continue;
 
         RootedObject propobj(cx, &v.toObject());
         // TODO Deal with non-functions.
         if (!js::IsWrapper(propobj) || !JS_ObjectIsCallable(cx, propobj))
             continue;
 
-        if (!WrapCallable(cx, obj, id, propobj, &v) ||
+        if (!NewFunctionForwarder(cx, id, propobj, /* doclone = */ false, &v) ||
             !JS_SetPropertyById(cx, obj, id, v))
             return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -3695,23 +3695,25 @@ xpc_GetSafeJSContext()
 }
 
 namespace xpc {
 struct SandboxOptions {
     SandboxOptions(JSContext *cx)
         : wantXrays(true)
         , wantComponents(true)
         , wantXHRConstructor(false)
+        , wantExportHelpers(false)
         , proto(xpc_GetSafeJSContext())
         , sameZoneAs(xpc_GetSafeJSContext())
     { }
 
     bool wantXrays;
     bool wantComponents;
     bool wantXHRConstructor;
+    bool wantExportHelpers;
     JS::RootedObject proto;
     nsCString sandboxName;
     JS::RootedObject sameZoneAs;
 };
 
 JSObject *
 CreateGlobalObject(JSContext *cx, JSClass *clasp, nsIPrincipal *principal,
                    JS::CompartmentOptions& aOptions);
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -61,16 +61,18 @@ GetXBLScope(JSContext *cx, JSObject *con
 // Returns whether XBL scopes have been explicitly disabled for code running
 // in this compartment. See the comment around mAllowXBLScope.
 bool
 AllowXBLScope(JSCompartment *c);
 
 bool
 IsSandboxPrototypeProxy(JSObject *obj);
 
+bool
+IsReflector(JSObject *obj);
 } /* namespace xpc */
 
 namespace JS {
 
 struct RuntimeStats;
 
 }
 
--- a/js/xpconnect/tests/chrome/Makefile.in
+++ b/js/xpconnect/tests/chrome/Makefile.in
@@ -56,16 +56,17 @@ MOCHITEST_CHROME_FILES = \
 		outoflinexulscript.js \
 		subscript.js \
 		utf8_subscript.js \
 		test_cows.xul \
 		test_documentdomain.xul \
 		test_doublewrappedcompartments.xul \
 		test_evalInSandbox.xul \
 		file_evalInSandbox.html \
+		test_evalInWindow.xul \
 		test_exnstack.xul \
 		test_expandosharing.xul \
 		file_expandosharing.jsm \
 		test_exposeInDerived.xul \
 		test_getweakmapkeys.xul \
 		test_mozMatchesSelector.xul \
 		test_nodelists.xul \
 		test_precisegc.xul \
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/chrome/test_evalInWindow.xul
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=877673
+-->
+<window title="Mozilla Bug 877673"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript"><![CDATA[
+      SimpleTest.waitForExplicitFinish();
+      const Cu = Components.utils;
+      var sb = new Cu.Sandbox("http://example.org", {wantExportHelpers: true});
+      sb.ok = ok;
+
+      function executeIn(frame, script, exceptionCb) {
+        sb.frame = frame;
+        sb.exceptionCb = exceptionCb;
+        if (exceptionCb) {
+          return Cu.evalInSandbox("try {evalInWindow('" + script + "',frame); ok(false, 'Exception should have been thrown.')} catch(e) {exceptionCb(e)}", sb);
+        }
+
+        return Cu.evalInSandbox("evalInWindow('" + script + "',frame)", sb);
+      }
+
+      function testSameOrigin(frame) {
+        frame.contentWindow.document.wrappedJSObject.str = "foobar";
+        is(executeIn(frame.contentWindow, "document.str"), "foobar",
+           "Same origin string property access.");
+
+        executeIn(frame.contentWindow, 'document.obj = {prop: "foobar"}');
+        is((executeIn(frame.contentWindow, "document.obj")).prop, "foobar",
+           "Same origin object property access (cloning).");
+        isnot(executeIn(frame.contentWindow, "document.obj"), frame.contentWindow.document.wrappedJSObject.obj,
+              "Ensure cloning for js objects.");
+        is(executeIn(frame.contentWindow, "document"), frame.contentWindow.document,
+           "Xrayables should just pass without cloning.");
+        is( executeIn(frame.contentWindow, "({a:{doc: document}})").a.doc, frame.contentWindow.document,
+           "Deep cloning works.");
+
+        executeIn(frame.contentWindow, "throw 42", function(e){is(e, 42,
+                                                                  "Exception was thrown from script.")});
+
+        executeIn(frame.contentDocument, "var a = 42;", function(e){ok(e.toString().indexOf("Second argument must be a window") > -1,
+                                                                       "Passing non-window to evalInWindow should throw.");});
+        testDone();
+      }
+
+      function testCrossOrigin(frame) {
+        executeIn(frame.contentWindow, "var a = 42;", function(e){ok(e.toString().indexOf("Permission denied") > -1,
+                                                                     "Executing script in a window from cross origin should throw.");});
+        testDone();
+      }
+
+      var testsRun = 0;
+      function testDone() {
+        if (++testsRun == 2)
+          SimpleTest.finish();
+      }
+  ]]></script>
+  <iframe src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"
+          onload="testSameOrigin(this)">
+  </iframe>
+  <iframe src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_empty.html"
+          onload="testCrossOrigin(this)">
+  </iframe>
+</window>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_exportFunction.js
@@ -0,0 +1,73 @@
+function run_test() {
+  var Cu = Components.utils;
+  var epsb = new Cu.Sandbox(["http://example.com", "http://example.org"], { wantExportHelpers: true });
+  subsb = new Cu.Sandbox("http://example.com", { wantXHRConstructor: true });
+  subsb2 = new Cu.Sandbox("http://example.com", { wantXHRConstructor: true });
+  xorigsb = new Cu.Sandbox("http://test.com");
+
+  epsb.subsb = subsb;
+  epsb.xorigsb = xorigsb;
+  epsb.do_check_true = do_check_true;
+  epsb.do_check_eq = do_check_eq;
+  epsb.do_check_neq = do_check_neq;
+
+  // Exporting should work if prinicipal of the source sandbox
+  // subsumes the principal of the target sandbox.
+  Cu.evalInSandbox("(" + function() {
+    Object.prototype.protoProp = "common";
+    var wasCalled = false;
+    var _this = this;
+    var funToExport = function(a, obj, native, mixed) {
+      do_check_eq(a, 42);
+      do_check_neq(obj, subsb.tobecloned);
+      do_check_eq(obj.cloned, "cloned");
+      do_check_eq(obj.protoProp, "common");
+      do_check_eq(native, subsb.native);
+      do_check_eq(_this, this);
+      do_check_eq(mixed.xrayed, subsb.xrayed);
+      do_check_eq(mixed.xrayed2, subsb.xrayed2);
+      wasCalled = true;
+    };
+    this.checkIfCalled = function() {
+      do_check_true(wasCalled);
+      wasCalled = false;
+    }
+    exportFunction(funToExport, subsb, "imported");
+  }.toSource() + ")()", epsb);
+
+  subsb.xrayed = Cu.evalInSandbox("(" + function () {
+      return new XMLHttpRequest();
+  }.toSource() + ")()", subsb2);
+
+  // Exported function should be able to be call from the
+  // target sandbox. Native arguments should be just wrapped
+  // every other argument should be cloned.
+  Cu.evalInSandbox("(" + function () {
+    native = new XMLHttpRequest();
+    xrayed2 = XPCNativeWrapper(new XMLHttpRequest());
+    mixed = { xrayed: xrayed, xrayed2: xrayed2 };
+    tobecloned = { cloned: "cloned" };
+    imported(42,tobecloned, native, mixed);
+  }.toSource() + ")()", subsb);
+
+  // Apply should work but the |this| argument should not be
+  // possible to be changed.
+  Cu.evalInSandbox("(" + function() {
+    imported.apply("something", [42, tobecloned, native, mixed]);
+  }.toSource() + ")()", subsb);
+
+  Cu.evalInSandbox("(" + function() {
+    checkIfCalled();
+  }.toSource() + ")()", epsb);
+
+  // Exporting should throw if princpal of the source sandbox does
+  // not subsume the principal of the target.
+  Cu.evalInSandbox("(" + function() {
+    try{
+      exportFunction(function(){}, this.xorigsb, "denied");
+      do_check_true(false);
+    } catch (e) {
+      do_check_true(e.toString().indexOf('Permission denied') > -1);
+    }
+  }.toSource() + ")()", epsb);
+}
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -42,9 +42,10 @@ fail-if = os == "android"
 [test_attributes.js]
 [test_params.js]
 [test_tearoffs.js]
 [test_want_components.js]
 [test_components.js]
 [test_allowedDomains.js]
 [test_allowedDomainsXHR.js]
 [test_nuke_sandbox.js]
+[test_exportFunction.js]
 [test_watchdog.js]
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -563,18 +563,19 @@ Declaration::GetValue(nsCSSProperty aPro
         data->ValueFor(eCSSProperty_font_variant_ligatures);
       const nsCSSValue *fontVariantNumeric =
         data->ValueFor(eCSSProperty_font_variant_numeric);
       const nsCSSValue *fontVariantPosition =
         data->ValueFor(eCSSProperty_font_variant_position);
 
       // if font features are not enabled, pointers for fontVariant
       // values above may be null since the shorthand check ignores them
+      // font-variant-alternates enabled ==> layout.css.font-features.enabled is true
       bool fontFeaturesEnabled =
-        mozilla::Preferences::GetBool("layout.css.font-features.enabled");
+        nsCSSProps::IsEnabled(eCSSProperty_font_variant_alternates);
 
       if (systemFont &&
           systemFont->GetUnit() != eCSSUnit_None &&
           systemFont->GetUnit() != eCSSUnit_Null) {
         if (style->GetUnit() != eCSSUnit_System_Font ||
             variant->GetUnit() != eCSSUnit_System_Font ||
             weight->GetUnit() != eCSSUnit_System_Font ||
             size->GetUnit() != eCSSUnit_System_Font ||
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -8510,18 +8510,19 @@ bool
 CSSParserImpl::ParseFont()
 {
   static const nsCSSProperty fontIDs[] = {
     eCSSProperty_font_style,
     eCSSProperty_font_variant,
     eCSSProperty_font_weight
   };
 
+  // font-variant-alternates enabled ==> layout.css.font-features.enabled is true
   bool featuresEnabled =
-    mozilla::Preferences::GetBool("layout.css.font-features.enabled");
+    nsCSSProps::IsEnabled(eCSSProperty_font_variant_alternates);
   nsCSSValue  family;
   if (ParseVariant(family, VARIANT_HK, nsCSSProps::kFontKTable)) {
     if (ExpectEndProperty()) {
       if (eCSSUnit_Inherit == family.GetUnit() ||
           eCSSUnit_Initial == family.GetUnit()) {
         AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
         AppendValue(eCSSProperty_font_family, family);
         AppendValue(eCSSProperty_font_style, family);
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -407,19 +407,22 @@ nsCSSProps::LookupFontDesc(const nsACStr
 }
 
 nsCSSFontDesc
 nsCSSProps::LookupFontDesc(const nsAString& aFontDesc)
 {
   NS_ABORT_IF_FALSE(gFontDescTable, "no lookup table, needs addref");
   nsCSSFontDesc which = nsCSSFontDesc(gFontDescTable->Lookup(aFontDesc));
 
+  // font-variant-alternates enabled ==> layout.css.font-features.enabled is true
+  bool fontFeaturesEnabled =
+    nsCSSProps::IsEnabled(eCSSProperty_font_variant_alternates);
+
   // check for unprefixed font-feature-settings/font-language-override
-  if (which == eCSSFontDesc_UNKNOWN &&
-      mozilla::Preferences::GetBool("layout.css.font-features.enabled")) {
+  if (which == eCSSFontDesc_UNKNOWN && fontFeaturesEnabled) {
     nsAutoString prefixedProp;
     prefixedProp.AppendLiteral("-moz-");
     prefixedProp.Append(aFontDesc);
     which = nsCSSFontDesc(gFontDescTable->Lookup(prefixedProp));
   }
   return which;
 }
 
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -330,17 +330,21 @@ public:
   {
     return mFeatureValues;
   }
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
 
   static bool PrefEnabled()
   {
-    return mozilla::Preferences::GetBool("layout.css.font-features.enabled");
+    // font-variant-alternates enabled ==> layout.css.font-features.enabled is true
+    bool fontFeaturesEnabled =
+      nsCSSProps::IsEnabled(eCSSProperty_font_variant_alternates);
+
+    return fontFeaturesEnabled;
   }
 
 protected:
   nsTArray<nsString> mFamilyList;
   nsTArray<gfxFontFeatureValueSet::FeatureValues> mFeatureValues;
 };
 
 namespace mozilla {
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -3365,21 +3365,29 @@ XREMain::XRE_mainStartup(bool* aExitFlag
       return 1;
   }
 #endif
 #ifdef MOZ_X11
   // Init X11 in thread-safe mode. Must be called prior to the first call to XOpenDisplay 
   // (called inside gdk_display_open). This is a requirement for off main tread compositing.
   // This is done only on X11 platforms if the environment variable MOZ_USE_OMTC is set so 
   // as to avoid overhead when omtc is not used. 
+  //
+  // On nightly builds, we call this by default to enable OMTC for Electrolysis testing. On
+  // aurora, beta, and release builds, there is a small tpaint regression from enabling this
+  // call, so it sits behind an environment variable.
+  //
   // An environment variable is used instead of a pref on X11 platforms because we start having 
   // access to prefs long after the first call to XOpenDisplay which is hard to change due to 
   // interdependencies in the initialization.
+# ifndef NIGHTLY_BUILD
   if (PR_GetEnv("MOZ_USE_OMTC") ||
-      PR_GetEnv("MOZ_OMTC_ENABLED")) {
+      PR_GetEnv("MOZ_OMTC_ENABLED"))
+# endif
+  {
     XInitThreads();
   }
 #endif
 #if defined(MOZ_WIDGET_GTK)
   mGdkDisplay = gdk_display_open(display_name);
   if (!mGdkDisplay) {
     PR_fprintf(PR_STDERR, "Error: cannot open display: %s\n", display_name);
     return 1;
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -1529,18 +1529,17 @@ nsChildView::ComputeShouldAccelerate(boo
     return false;
 
   return nsBaseWidget::ComputeShouldAccelerate(aDefault);
 }
 
 bool
 nsChildView::ShouldUseOffMainThreadCompositing()
 {
-  // Don't use OMTC (which requires OpenGL) for transparent windows or for
-  // popup windows.
+  // Don't use OMTC for transparent windows or for popup windows.
   if (!mView || ![[mView window] isOpaque] ||
       [[mView window] isKindOfClass:[PopupWindow class]])
     return false;
 
   return nsBaseWidget::ShouldUseOffMainThreadCompositing();
 }
 
 inline uint16_t COLOR8TOCOLOR16(uint8_t color8)