Bug 1333990: Part 1a - Add an async script pre-loading utility. r=billm,shu
authorKris Maglione <maglione.k@gmail.com>
Thu, 16 Mar 2017 19:10:40 -0700
changeset 348196 48f9ab3c59676f9a290e1a08f22e2d0ee945eb98
parent 348195 bb325d01c4219c4ecf49c2e8830eb762320358ec
child 348197 db8466a676a1c98e0ed7de06e07efe6821a04d40
push id31515
push userkwierso@gmail.com
push dateFri, 17 Mar 2017 18:04:10 +0000
treeherdermozilla-central@23a4b7430dd7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm, shu
bugs1333990
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1333990: Part 1a - Add an async script pre-loading utility. r=billm,shu MozReview-Commit-ID: 4vJF2drLeHS
dom/base/ChromeUtils.h
dom/base/nsScriptLoader.cpp
dom/base/nsScriptLoader.h
dom/webidl/ChromeUtils.webidl
dom/webidl/PrecompiledScript.webidl
dom/webidl/moz.build
js/xpconnect/loader/ChromeScriptLoader.cpp
js/xpconnect/loader/PrecompiledScript.h
js/xpconnect/loader/moz.build
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -16,16 +16,18 @@ namespace mozilla {
 
 namespace devtools {
 class HeapSnapshot;
 } // namespace devtools
 
 namespace dom {
 
 class ArrayBufferViewOrArrayBuffer;
+class PrecompiledScript;
+class Promise;
 
 class ThreadSafeChromeUtils
 {
 public:
   // Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
   static void SaveHeapSnapshot(GlobalObject& global,
                                const HeapSnapshotBoundaries& boundaries,
                                nsAString& filePath,
@@ -96,14 +98,21 @@ public:
   IsOriginAttributesEqualIgnoringFPD(const dom::OriginAttributesDictionary& aA,
                                      const dom::OriginAttributesDictionary& aB)
   {
     return aA.mAppId == aB.mAppId &&
            aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
            aA.mUserContextId == aB.mUserContextId &&
            aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
   }
+
+  // Implemented in js/xpconnect/loader/ChromeScriptLoader.cpp
+  static already_AddRefed<Promise>
+  CompileScript(GlobalObject& aGlobal,
+                const nsAString& aUrl,
+                const dom::CompileScriptOptionsDictionary& aOptions,
+                ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -8,16 +8,17 @@
  * A class that handles loading and evaluation of <script> elements.
  */
 
 #include "nsScriptLoader.h"
 
 #include "prsystem.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
+#include "js/Utility.h"
 #include "xpcpublic.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIContent.h"
 #include "nsJSUtils.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/SRILogHelper.h"
--- a/dom/base/nsScriptLoader.h
+++ b/dom/base/nsScriptLoader.h
@@ -402,16 +402,31 @@ public:
    *                     of char16_t code units.
    */
   static nsresult ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
                                  uint32_t aLength,
                                  const nsAString& aHintCharset,
                                  nsIDocument* aDocument,
                                  char16_t*& aBufOut, size_t& aLengthOut);
 
+  static inline nsresult
+  ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
+                 uint32_t aLength, const nsAString& aHintCharset,
+                 nsIDocument* aDocument,
+                 JS::UniqueTwoByteChars& aBufOut, size_t& aLengthOut)
+  {
+    char16_t* bufOut;
+    nsresult rv = ConvertToUTF16(aChannel, aData, aLength, aHintCharset, aDocument,
+                                 bufOut, aLengthOut);
+    if (NS_SUCCEEDED(rv)) {
+      aBufOut.reset(bufOut);
+    }
+    return rv;
+  };
+
   /**
    * Handle the completion of a stream.  This is called by the
    * nsScriptLoadHandler object which observes the IncrementalStreamLoader
    * loading the script. The streamed content is expected to be stored on the
    * aRequest argument.
    */
   nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
                             nsScriptLoadRequest* aRequest,
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -55,16 +55,25 @@ interface ChromeUtils : ThreadSafeChrome
   fillNonDefaultOriginAttributes(optional OriginAttributesDictionary originAttrs);
 
   /**
    * Returns true if the 2 OriginAttributes are equal.
    */
   static boolean
   isOriginAttributesEqual(optional OriginAttributesDictionary aA,
                           optional OriginAttributesDictionary aB);
+
+  /**
+   * Loads and compiles the script at the given URL and returns an object
+   * which may be used to execute it repeatedly, in different globals, without
+   * re-parsing.
+   */
+  [NewObject, Throws]
+  static Promise<PrecompiledScript>
+  compileScript(DOMString url, optional CompileScriptOptionsDictionary options);
 };
 
 /**
  * Used by principals and the script security manager to represent origin
  * attributes. The first dictionary is designed to contain the full set of
  * OriginAttributes, the second is used for pattern-matching (i.e. does this
  * OriginAttributesDictionary match the non-empty attributes in this pattern).
  *
@@ -83,8 +92,29 @@ dictionary OriginAttributesDictionary {
 };
 dictionary OriginAttributesPatternDictionary {
   unsigned long appId;
   unsigned long userContextId;
   boolean inIsolatedMozBrowser;
   unsigned long privateBrowsingId;
   DOMString firstPartyDomain;
 };
+
+dictionary CompileScriptOptionsDictionary {
+  /**
+   * The character set from which to decode the script.
+   */
+  DOMString charset = "utf-8";
+
+  /**
+   * If true, certain parts of the script may be parsed lazily, the first time
+   * they are used, rather than eagerly parsed at load time.
+   */
+  boolean lazilyParse = false;
+
+  /**
+   * If true, the script will be compiled so that its last expression will be
+   * returned as the value of its execution. This makes certain types of
+   * optimization impossible, and disables the JIT in many circumstances, so
+   * should not be used when not absolutely necessary.
+   */
+  boolean hasReturnValue = false;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PrecompiledScript.webidl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; 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/.
+ */
+
+/**
+ * Represents a pre-compiled JS script, which can be repeatedly exeuted in
+ * different globals without being re-parsed.
+ */
+[ChromeOnly, Exposed=(Window,System)]
+interface PrecompiledScript {
+  /**
+   * Executes the script in the global of the given object, and returns the
+   * value of its last expression, if compiled with a return value.
+   */
+  [Throws]
+  any executeInGlobal(object global);
+
+  /**
+   * The URL that the script was loaded from.
+   */
+  [Pure]
+  readonly attribute DOMString url;
+
+  /**
+   * True if the script was compiled with a return value, and will return the
+   * value of its last expression when executed.
+   */
+  [Pure]
+  readonly attribute boolean hasReturnValue;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -732,16 +732,17 @@ WEBIDL_FILES = [
     'Permissions.webidl',
     'PermissionStatus.webidl',
     'Plugin.webidl',
     'PluginArray.webidl',
     'PointerEvent.webidl',
     'PopupBoxObject.webidl',
     'Position.webidl',
     'PositionError.webidl',
+    'PrecompiledScript.webidl',
     'Presentation.webidl',
     'PresentationAvailability.webidl',
     'PresentationConnection.webidl',
     'PresentationConnectionList.webidl',
     'PresentationDeviceInfoManager.webidl',
     'PresentationReceiver.webidl',
     'PresentationRequest.webidl',
     'ProcessingInstruction.webidl',
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/ChromeScriptLoader.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 "PrecompiledScript.h"
+
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "nsThreadUtils.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Utility.h"
+
+#include "mozilla/dom/ChromeUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/SystemGroup.h"
+#include "nsCycleCollectionParticipant.h"
+
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class AsyncScriptCompiler final : public nsIIncrementalStreamLoaderObserver
+                                , public Runnable
+{
+public:
+    NS_DECL_ISUPPORTS_INHERITED
+    NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
+    NS_DECL_NSIRUNNABLE
+
+    AsyncScriptCompiler(JSContext* aCx, nsIGlobalObject* aGlobal,
+                        const nsACString& aURL,
+                        const CompileScriptOptionsDictionary& aOptions,
+                        Promise* aPromise)
+        : mOptions(aCx)
+        , mURL(aURL)
+        , mGlobalObject(aGlobal)
+        , mPromise(aPromise)
+        , mCharset(aOptions.mCharset)
+    {
+        mOptions.setVersion(JSVERSION_DEFAULT)
+                .setNoScriptRval(!aOptions.mHasReturnValue)
+                .setCanLazilyParse(aOptions.mLazilyParse)
+                .setFile(aCx, mURL.get());
+    }
+
+    nsresult Start(nsIPrincipal* aPrincipal);
+
+    inline void
+    SetToken(void* aToken)
+    {
+        mToken = aToken;
+    }
+
+protected:
+    virtual ~AsyncScriptCompiler() {
+        if (mPromise->State() == Promise::PromiseState::Pending) {
+            mPromise->MaybeReject(NS_ERROR_FAILURE);
+        }
+    }
+
+private:
+    void Reject(JSContext* aCx);
+    void Reject(JSContext* aCx, const char* aMxg);
+
+    bool StartCompile(JSContext* aCx);
+    void FinishCompile(JSContext* aCx);
+    void Finish(JSContext* aCx, Handle<JSScript*> script);
+
+    OwningCompileOptions        mOptions;
+    nsCString                   mURL;
+    nsCOMPtr<nsIGlobalObject>   mGlobalObject;
+    RefPtr<Promise>             mPromise;
+    nsString                    mCharset;
+    void*                       mToken;
+    UniqueTwoByteChars          mScriptText;
+    size_t                      mScriptLength;
+};
+
+NS_IMPL_QUERY_INTERFACE_INHERITED(AsyncScriptCompiler, Runnable, nsIIncrementalStreamLoaderObserver)
+NS_IMPL_ADDREF_INHERITED(AsyncScriptCompiler, Runnable)
+NS_IMPL_RELEASE_INHERITED(AsyncScriptCompiler, Runnable)
+
+nsresult
+AsyncScriptCompiler::Start(nsIPrincipal* aPrincipal)
+{
+    nsCOMPtr<nsIURI> uri;
+    nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIChannel> channel;
+    rv = NS_NewChannel(getter_AddRefs(channel),
+                       uri, aPrincipal,
+                       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                       nsIContentPolicy::TYPE_OTHER);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIIncrementalStreamLoader> loader;
+    rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), this);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return channel->AsyncOpen2(loader);
+}
+
+static void
+OffThreadScriptLoaderCallback(void* aToken, void* aCallbackData)
+{
+    RefPtr<AsyncScriptCompiler> scriptCompiler = dont_AddRef(
+        static_cast<AsyncScriptCompiler*>(aCallbackData));
+
+    scriptCompiler->SetToken(aToken);
+
+    SystemGroup::Dispatch("ScriptLoader::FinishCompile",
+                          TaskCategory::Other,
+                          scriptCompiler.forget());
+}
+
+bool
+AsyncScriptCompiler::StartCompile(JSContext* aCx)
+{
+    Rooted<JSObject*> global(aCx, mGlobalObject->GetGlobalJSObject());
+
+    if (JS::CanCompileOffThread(aCx, mOptions, mScriptLength)) {
+        if (!JS::CompileOffThread(aCx, mOptions, mScriptText.get(), mScriptLength,
+                                  OffThreadScriptLoaderCallback,
+                                  static_cast<void*>(this))) {
+            return false;
+        }
+
+        NS_ADDREF(this);
+        return true;
+    }
+
+    Rooted<JSScript*> script(aCx);
+    if (!JS::Compile(aCx, mOptions, mScriptText.get(), mScriptLength, &script)) {
+        return false;
+    }
+
+    Finish(aCx, script);
+    return true;
+}
+
+NS_IMETHODIMP
+AsyncScriptCompiler::Run()
+{
+    AutoJSAPI jsapi;
+    if (jsapi.Init(mGlobalObject)) {
+        FinishCompile(jsapi.cx());
+    } else {
+        jsapi.Init();
+        JS::CancelOffThreadScript(jsapi.cx(), mToken);
+
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+    }
+
+    return NS_OK;
+}
+
+void
+AsyncScriptCompiler::FinishCompile(JSContext* aCx)
+{
+    Rooted<JSScript*> script(aCx, JS::FinishOffThreadScript(aCx, mToken));
+
+    Finish(aCx, script);
+}
+
+
+void
+AsyncScriptCompiler::Finish(JSContext* aCx, Handle<JSScript*> aScript)
+{
+    RefPtr<PrecompiledScript> result = new PrecompiledScript(mGlobalObject, aScript, mOptions);
+
+    mPromise->MaybeResolve(result);
+}
+
+void
+AsyncScriptCompiler::Reject(JSContext* aCx)
+{
+    RootedValue value(aCx, JS::UndefinedValue());
+    if (JS_GetPendingException(aCx, &value)) {
+        JS_ClearPendingException(aCx);
+    }
+    mPromise->MaybeReject(aCx, value);
+}
+
+void
+AsyncScriptCompiler::Reject(JSContext* aCx, const char* aMsg)
+{
+    nsAutoCString msg(aMsg);
+    msg.Append(": ");
+    msg.Append(mURL);
+
+    RootedValue exn(aCx, StringValue(JS_NewStringCopyZ(aCx, msg.get())));
+    JS_SetPendingException(aCx, exn);
+
+    Reject(aCx);
+}
+
+NS_IMETHODIMP
+AsyncScriptCompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
+                                       nsISupports* aContext,
+                                       uint32_t aDataLength,
+                                       const uint8_t* aData,
+                                       uint32_t *aConsumedData)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncScriptCompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+                                      nsISupports* aContext,
+                                      nsresult aStatus,
+                                      uint32_t aLength,
+                                      const uint8_t* aBuf)
+{
+    AutoJSAPI jsapi;
+    if (!jsapi.Init(mGlobalObject)) {
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+        return NS_OK;
+    }
+
+    JSContext* cx = jsapi.cx();
+
+    if (NS_FAILED(aStatus)) {
+        Reject(cx, "Unable to load script");
+        return NS_OK;
+    }
+
+    nsresult rv = nsScriptLoader::ConvertToUTF16(
+        nullptr, aBuf, aLength, mCharset, nullptr, mScriptText, mScriptLength);
+    if (NS_FAILED(rv)) {
+        Reject(cx, "Unable to decode script");
+        return NS_OK;
+    }
+
+    if (!StartCompile(cx)) {
+        Reject(cx);
+    }
+
+    return NS_OK;
+}
+
+
+namespace mozilla {
+namespace dom {
+
+/* static */ already_AddRefed<Promise>
+ChromeUtils::CompileScript(GlobalObject& aGlobal,
+                           const nsAString& aURL,
+                           const CompileScriptOptionsDictionary& aOptions,
+                           ErrorResult& aRv)
+{
+    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+    MOZ_ASSERT(global);
+
+    RefPtr<Promise> promise = Promise::Create(global, aRv);
+    if (aRv.Failed()) {
+        return nullptr;
+    }
+
+    NS_ConvertUTF16toUTF8 url(aURL);
+    RefPtr<AsyncScriptCompiler> compiler = new AsyncScriptCompiler(aGlobal.Context(), global, url, aOptions, promise);
+
+    nsresult rv = compiler->Start(aGlobal.GetSubjectPrincipal());
+    if (NS_FAILED(rv)) {
+        promise->MaybeReject(rv);
+    }
+
+    return promise.forget();
+}
+
+PrecompiledScript::PrecompiledScript(nsISupports* aParent, Handle<JSScript*> aScript,
+                                     JS::ReadOnlyCompileOptions& aOptions)
+    : mParent(aParent)
+    , mScript(aScript)
+    , mURL(aOptions.filename())
+    , mHasReturnValue(!aOptions.noScriptRval)
+{
+    MOZ_ASSERT(aParent);
+    MOZ_ASSERT(aScript);
+
+    mozilla::HoldJSObjects(this);
+};
+
+PrecompiledScript::~PrecompiledScript()
+{
+    mozilla::DropJSObjects(this);
+}
+
+void
+PrecompiledScript::ExecuteInGlobal(JSContext* aCx, HandleObject aGlobal,
+                                   MutableHandleValue aRval,
+                                   ErrorResult& aRv)
+{
+    {
+        RootedObject targetObj(aCx, JS_FindCompilationScope(aCx, aGlobal));
+        JSAutoCompartment ac(aCx, targetObj);
+
+        Rooted<JSScript*> script(aCx, mScript);
+        if (!JS::CloneAndExecuteScript(aCx, script, aRval)) {
+            aRv.NoteJSContextException(aCx);
+        }
+    }
+
+    JS_WrapValue(aCx, aRval);
+}
+
+void
+PrecompiledScript::GetUrl(nsAString& aUrl)
+{
+    CopyUTF8toUTF16(mURL, aUrl);
+}
+
+bool
+PrecompiledScript::HasReturnValue()
+{
+    return mHasReturnValue;
+}
+
+JSObject*
+PrecompiledScript::WrapObject(JSContext* aCx, HandleObject aGivenProto)
+{
+    return PrecompiledScriptBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PrecompiledScript)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrecompiledScript)
+    NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+    NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PrecompiledScript)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+
+    tmp->mScript = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PrecompiledScript)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PrecompiledScript)
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript)
+    NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PrecompiledScript)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PrecompiledScript)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/PrecompiledScript.h
@@ -0,0 +1,56 @@
+/* -*-  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 mozilla_dom_PrecompiledScript_h
+#define mozilla_dom_PrecompiledScript_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/PrecompiledScriptBinding.h"
+
+#include "jsapi.h"
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+class PrecompiledScript : public nsISupports
+                        , public nsWrapperCache
+{
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PrecompiledScript)
+
+  explicit PrecompiledScript(nsISupports* aParent, JS::Handle<JSScript*> aScript, JS::ReadOnlyCompileOptions& aOptions);
+
+  void ExecuteInGlobal(JSContext* aCx, JS::HandleObject aGlobal,
+                       JS::MutableHandleValue aRval,
+                       ErrorResult& aRv);
+
+  void GetUrl(nsAString& aUrl);
+
+  bool HasReturnValue();
+
+  nsISupports* GetParentObject() const { return mParent; }
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+  virtual ~PrecompiledScript();
+
+private:
+  nsCOMPtr<nsISupports> mParent;
+
+  JS::Heap<JSScript*> mScript;
+  nsCString mURL;
+  const bool mHasReturnValue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PrecompiledScript_h
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -1,21 +1,26 @@
 # -*- Mode: python; 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/.
 
 # These files cannot be built in unified mode because they rely on plarena.h
 SOURCES += [
+    'ChromeScriptLoader.cpp',
     'mozJSComponentLoader.cpp',
     'mozJSLoaderUtils.cpp',
     'mozJSSubScriptLoader.cpp',
 ]
 
+EXPORTS.mozilla.dom += [
+    'PrecompiledScript.h',
+]
+
 EXTRA_JS_MODULES += [
     'ISO8601DateUtils.jsm',
     'XPCOMUtils.jsm',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [