--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -5,27 +5,29 @@
#ifndef mozilla_dom_ChromeUtils__
#define mozilla_dom_ChromeUtils__
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/ChromeUtilsBinding.h"
#include "mozilla/dom/ThreadSafeChromeUtilsBinding.h"
+#include "mozilla/dom/Promise.h"
#include "mozilla/ErrorResult.h"
namespace mozilla {
namespace devtools {
class HeapSnapshot;
} // namespace devtools
namespace dom {
class ArrayBufferViewOrArrayBuffer;
+class PrecompiledScript;
class ThreadSafeChromeUtils
{
public:
// Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
static void SaveHeapSnapshot(GlobalObject& global,
const HeapSnapshotBoundaries& boundaries,
nsAString& filePath,
@@ -90,14 +92,21 @@ public:
static bool
IsOriginAttributesEqual(const dom::OriginAttributesDictionary& aA,
const dom::OriginAttributesDictionary& aB);
static bool
IsOriginAttributesEqualIgnoringFPD(const dom::OriginAttributesDictionary& aA,
const dom::OriginAttributesDictionary& aB);
+
+ // 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
@@ -735,16 +735,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 += [