author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Fri, 09 Oct 2015 11:43:07 +0200 | |
changeset 267023 | d01dd42e654b8735d86f9e7c723cc869a3b56798 |
parent 266954 | 462074ffada4969135709b86de53210a277b11d3 (current diff) |
parent 267022 | 4fed2969228466303141ed72f29cba5d123ea079 (diff) |
child 267028 | 3540caeae07fcc81c94de4af7dae732847b7253d |
child 267039 | 9ac872ca63fafbf1f9513064a38b0f700b2f07f9 |
child 267051 | 814b316fd833702970f0d73e3cda34032689d2c1 |
push id | 29504 |
push user | cbook@mozilla.com |
push date | Fri, 09 Oct 2015 09:43:23 +0000 |
treeherder | mozilla-central@d01dd42e654b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 44.0a1 |
first release with | nightly linux32
d01dd42e654b
/
44.0a1
/
20151009030230
/
files
nightly linux64
d01dd42e654b
/
44.0a1
/
20151009030230
/
files
nightly mac
d01dd42e654b
/
44.0a1
/
20151009030230
/
files
nightly win32
d01dd42e654b
/
44.0a1
/
20151009030230
/
files
nightly win64
d01dd42e654b
/
44.0a1
/
20151009030230
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
44.0a1
/
20151009030230
/
pushlog to previous
nightly linux64
44.0a1
/
20151009030230
/
pushlog to previous
nightly mac
44.0a1
/
20151009030230
/
pushlog to previous
nightly win32
44.0a1
/
20151009030230
/
pushlog to previous
nightly win64
44.0a1
/
20151009030230
/
pushlog to previous
|
mobile/android/base/GeckoAppShell.java | file | annotate | diff | comparison | revisions | |
mobile/android/base/gfx/InputConnectionHandler.java | file | annotate | diff | comparison | revisions | |
mobile/android/base/moz.build | file | annotate | diff | comparison | revisions |
new file mode 100644 --- /dev/null +++ b/browser/config/mozconfigs/macosx64/opt-static-analysis @@ -0,0 +1,19 @@ +MOZ_AUTOMATION_BUILD_SYMBOLS=0 +MOZ_AUTOMATION_PACKAGE_TESTS=0 +MOZ_AUTOMATION_L10N_CHECK=0 + +. $topsrcdir/build/macosx/mozconfig.common + +ac_add_options --disable-debug +ac_add_options --enable-optimize +ac_add_options --enable-dmd + +# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS). +ac_add_options --enable-warnings-as-errors + +ac_add_options --enable-clang-plugin + +. "$topsrcdir/build/macosx/mozconfig.rust" +. "$topsrcdir/build/mozconfig.common.override" +. "$topsrcdir/build/mozconfig.cache" +
--- a/browser/config/tooltool-manifests/linux32/clang.manifest +++ b/browser/config/tooltool-manifests/linux32/clang.manifest @@ -1,12 +1,12 @@ [ { "clang_version": "r247539" }, { "size": 93197192, "digest": "6ebd8994ac76cf6694c3d9054104219836f47578223c799cb9ba9669cfdee00112e9de56aea9d1e6d9d50ee94a201970555de19794b5fbb7546f58fdf8e59a99", "algorithm": "sha512", -"filename": "clang.tar.bz2", +"filename": "clang.tar.xz", "unpack": true, } ]
--- a/browser/config/tooltool-manifests/macosx64/clang.manifest +++ b/browser/config/tooltool-manifests/macosx64/clang.manifest @@ -1,17 +1,17 @@ [ { "clang_version": "r247539" }, { "size": 97314461, "digest": "9a74670fa917f760a4767923485d5166bbd258a8023c8aeb899b8c4d22f2847be76508ac5f26d7d2193318a2bb368a71bc62888d1bfe9d81eb45329a60451aa4", "algorithm": "sha512", -"filename": "clang.tar.xz", +"filename": "clang.tar.bz2", "unpack": true }, { "size": 167175, "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831", "algorithm": "sha512", "filename": "sccache.tar.bz2", "unpack": true
--- a/build/annotationProcessors/CodeGenerator.java +++ b/build/annotationProcessors/CodeGenerator.java @@ -61,25 +61,47 @@ public class CodeGenerator { "public mozilla::jni::NativeImpl<" + unqualifiedName + ", Impl>\n" + "{\n"); } private String getTraitsName(String uniqueName, boolean includeScope) { return (includeScope ? clsName + "::" : "") + uniqueName + "_t"; } + /** + * Return the C++ type name for this class or any class within the chain + * of declaring classes, if the target class matches the given type. + * + * Return null if the given type does not match any class searched. + */ + private String getMatchingClassType(final Class<?> type) { + Class<?> cls = this.cls; + String clsName = this.clsName; + + while (cls != null) { + if (type == cls) { + return clsName; + } + cls = cls.getDeclaringClass(); + clsName = clsName.substring(0, Math.max(0, clsName.lastIndexOf("::"))); + } + return null; + } + private String getNativeParameterType(Class<?> type, AnnotationInfo info) { - if (type == cls) { + final String clsName = getMatchingClassType(type); + if (clsName != null) { return Utils.getUnqualifiedName(clsName) + "::Param"; } return Utils.getNativeParameterType(type, info); } private String getNativeReturnType(Class<?> type, AnnotationInfo info) { - if (type == cls) { + final String clsName = getMatchingClassType(type); + if (clsName != null) { return Utils.getUnqualifiedName(clsName) + "::LocalRef"; } return Utils.getNativeReturnType(type, info); } private void generateMember(AnnotationInfo info, Member member, String uniqueName, Class<?> type, Class<?>[] argTypes) { final StringBuilder args = new StringBuilder(); @@ -365,16 +387,17 @@ public class CodeGenerator { } final boolean isStatic = Utils.isStatic(field); final boolean isFinal = Utils.isFinal(field); if (isStatic && isFinal && (type.isPrimitive() || type == String.class)) { Object val = null; try { + field.setAccessible(true); val = field.get(null); } catch (final IllegalAccessException e) { } if (val != null && type.isPrimitive()) { // For static final primitive fields, we can use a "static const" declaration. header.append( "public:\n" +
--- a/build/unix/build-clang/build-clang.py +++ b/build/unix/build-clang/build-clang.py @@ -59,17 +59,17 @@ def updated_env(env): yield os.environ.clear() os.environ.update(old_env) def build_tar_package(tar, name, base, directory): name = os.path.realpath(name) run_in(base, [tar, - "-c -%s -f" % "J" if ".xz" in name else "j", + "-c -%s -f" % ("J" if ".xz" in name else "j"), name, directory]) def copy_dir_contents(src, dest): for f in glob.glob("%s/*" % src): try: destname = "%s/%s" % (dest, os.path.basename(f)) shutil.copytree(f, destname)
--- a/devtools/client/styleinspector/test/browser_ruleview_authored.js +++ b/devtools/client/styleinspector/test/browser_ruleview_authored.js @@ -50,26 +50,27 @@ function* basicTest() { let prop = rule.textProps[i]; is(prop.name, expected[i].name, "test name for prop " + i); is(prop.overridden, expected[i].overridden, "test overridden for prop " + i); } } function* overrideTest() { - let gradientText = "(45deg, rgba(255,255,255,0.2) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0.2) 75%, transparent 75%, transparent);"; + let gradientText1 = "(orange, blue);"; + let gradientText2 = "(pink, teal);"; let view = yield createTestContent("#testid {" + - " background-image: -moz-linear-gradient" + - gradientText + - " background-image: -webkit-linear-gradient" + - gradientText + " background-image: linear-gradient" + - gradientText + + gradientText1 + + " background-image: -ms-linear-gradient" + + gradientText2 + + " background-image: linear-gradient" + + gradientText2 + "} "); let elementStyle = view._elementStyle; let rule = elementStyle.rules[1]; // Initially the last property should be active. for (let i = 0; i < 3; ++i) { let prop = rule.textProps[i];
--- a/dom/apps/Webapps.jsm +++ b/dom/apps/Webapps.jsm @@ -4101,21 +4101,16 @@ this.DOMApplicationRegistry = { eventType: "downloaderror", manifestURL: aNewApp.manifestURL }); }); AppDownloadManager.remove(aNewApp.manifestURL); }, doUninstall: Task.async(function*(aData, aMm) { - // The yields here could get stuck forever, so we only hold - // a weak reference to the message manager while yielding, to avoid - // leaking the whole page associationed with the message manager. - aMm = Cu.getWeakReference(aMm); - let response = "Webapps:Uninstall:Return:OK"; try { aData.app = yield this._getAppWithManifest(aData.manifestURL); if (this.kAndroid == aData.app.kind) { debug("Uninstalling android app " + aData.app.origin); let [packageName, className] = @@ -4135,19 +4130,17 @@ this.DOMApplicationRegistry = { yield this._promptForUninstall(aData); } } } catch (error) { aData.error = error; response = "Webapps:Uninstall:Return:KO"; } - if ((aMm = aMm.get())) { - aMm.sendAsyncMessage(response, this.formatMessage(aData)); - } + aMm.sendAsyncMessage(response, this.formatMessage(aData)); }), uninstall: function(aManifestURL) { return this._getAppWithManifest(aManifestURL) .then(this._uninstallApp.bind(this)); }, _uninstallApp: Task.async(function*(aApp) {
--- a/dom/base/EventSource.cpp +++ b/dom/base/EventSource.cpp @@ -109,19 +109,17 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_E NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(EventSource, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrc) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadGroup) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHttpChannel) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnicodeDecoder) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource, DOMEventTargetHelper) tmp->Close(); @@ -485,58 +483,16 @@ EventSource::OnStopRequest(nsIRequest *a NS_ENSURE_STATE(event); rv = NS_DispatchToMainThread(event); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } -/** - * Simple helper class that just forwards the redirect callback back - * to the EventSource. - */ -class AsyncVerifyRedirectCallbackFwr final : public nsIAsyncVerifyRedirectCallback -{ -public: - explicit AsyncVerifyRedirectCallbackFwr(EventSource* aEventsource) - : mEventSource(aEventsource) - { - } - - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackFwr) - - // nsIAsyncVerifyRedirectCallback implementation - NS_IMETHOD OnRedirectVerifyCallback(nsresult aResult) override - { - nsresult rv = mEventSource->OnRedirectVerifyCallback(aResult); - if (NS_FAILED(rv)) { - mEventSource->mErrorLoadOnRedirect = true; - mEventSource->DispatchFailConnection(); - } - - return NS_OK; - } - -private: - ~AsyncVerifyRedirectCallbackFwr() {} - nsRefPtr<EventSource> mEventSource; -}; - -NS_IMPL_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackFwr, mEventSource) - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackFwr) - NS_INTERFACE_MAP_ENTRY(nsISupports) - NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) -NS_INTERFACE_MAP_END - -NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncVerifyRedirectCallbackFwr) -NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncVerifyRedirectCallbackFwr) - //----------------------------------------------------------------------------- // EventSource::nsIChannelEventSink //----------------------------------------------------------------------------- NS_IMETHODIMP EventSource::AsyncOnChannelRedirect(nsIChannel *aOldChannel, nsIChannel *aNewChannel, uint32_t aFlags, @@ -559,99 +515,48 @@ EventSource::AsyncOnChannelRedirect(nsIC (NS_SUCCEEDED(newURI->SchemeIs("https", &isValidScheme)) && isValidScheme); rv = CheckInnerWindowCorrectness(); if (NS_FAILED(rv) || !isValidScheme) { DispatchFailConnection(); return NS_ERROR_DOM_SECURITY_ERR; } - // Prepare to receive callback - mRedirectFlags = aFlags; - mRedirectCallback = aCallback; - mNewRedirectChannel = aNewChannel; - - if (mChannelEventSink) { - nsRefPtr<AsyncVerifyRedirectCallbackFwr> fwd = - new AsyncVerifyRedirectCallbackFwr(this); - - rv = mChannelEventSink->AsyncOnChannelRedirect(aOldChannel, - aNewChannel, - aFlags, fwd); - if (NS_FAILED(rv)) { - mRedirectCallback = nullptr; - mNewRedirectChannel = nullptr; - mErrorLoadOnRedirect = true; - DispatchFailConnection(); - } - return rv; - } - OnRedirectVerifyCallback(NS_OK); - return NS_OK; -} - -nsresult -EventSource::OnRedirectVerifyCallback(nsresult aResult) -{ - MOZ_ASSERT(mRedirectCallback, "mRedirectCallback not set in callback"); - MOZ_ASSERT(mNewRedirectChannel, - "mNewRedirectChannel not set in callback"); - - NS_ENSURE_SUCCESS(aResult, aResult); - // update our channel - mHttpChannel = do_QueryInterface(mNewRedirectChannel); + mHttpChannel = do_QueryInterface(aNewChannel); NS_ENSURE_STATE(mHttpChannel); - nsresult rv = SetupHttpChannel(); + rv = SetupHttpChannel(); NS_ENSURE_SUCCESS(rv, rv); - if ((mRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) { + if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) { rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc)); NS_ENSURE_SUCCESS(rv, rv); } - mNewRedirectChannel = nullptr; - - mRedirectCallback->OnRedirectVerifyCallback(aResult); - mRedirectCallback = nullptr; + aCallback->OnRedirectVerifyCallback(NS_OK); return NS_OK; } //----------------------------------------------------------------------------- // EventSource::nsIInterfaceRequestor //----------------------------------------------------------------------------- NS_IMETHODIMP EventSource::GetInterface(const nsIID & aIID, void **aResult) { - // Make sure to return ourselves for the channel event sink interface, - // no matter what. We can forward these to mNotificationCallbacks - // if it wants to get notifications for them. But we - // need to see these notifications for proper functioning. if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { - mChannelEventSink = do_GetInterface(mNotificationCallbacks); *aResult = static_cast<nsIChannelEventSink*>(this); NS_ADDREF_THIS(); return NS_OK; } - // Now give mNotificationCallbacks (if non-null) a chance to return the - // desired interface. - if (mNotificationCallbacks) { - nsresult rv = mNotificationCallbacks->GetInterface(aIID, aResult); - if (NS_SUCCEEDED(rv)) { - NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!"); - return rv; - } - } - if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { nsresult rv = CheckInnerWindowCorrectness(); NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); nsCOMPtr<nsIPromptFactory> wwatch = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); @@ -802,22 +707,24 @@ EventSource::InitChannelAndRequestEventS NS_ENSURE_SUCCESS(rv, rv); mHttpChannel = do_QueryInterface(channel); NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE); rv = SetupHttpChannel(); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks; - mHttpChannel->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks)); - if (notificationCallbacks != this) { - mNotificationCallbacks = notificationCallbacks; - mHttpChannel->SetNotificationCallbacks(this); +#ifdef DEBUG + { + nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks; + mHttpChannel->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks)); + MOZ_ASSERT(!notificationCallbacks); } +#endif + mHttpChannel->SetNotificationCallbacks(this); // Start reading from the channel rv = mHttpChannel->AsyncOpen2(this); if (NS_FAILED(rv)) { DispatchFailConnection(); return rv; } mWaitingForOnStopRequest = true; @@ -873,21 +780,17 @@ EventSource::ResetConnection() } if (mUnicodeDecoder) { mUnicodeDecoder->Reset(); } mLastConvertionResult = NS_OK; mHttpChannel = nullptr; - mNotificationCallbacks = nullptr; - mChannelEventSink = nullptr; mStatus = PARSE_STATE_OFF; - mRedirectCallback = nullptr; - mNewRedirectChannel = nullptr; mReadyState = CONNECTING; return NS_OK; } void EventSource::ReestablishConnection()
--- a/dom/base/EventSource.h +++ b/dom/base/EventSource.h @@ -29,28 +29,25 @@ class nsPIDOMWindow; namespace mozilla { class ErrorResult; namespace dom { -class AsyncVerifyRedirectCallbackFwr; struct EventSourceInit; class EventSource final : public DOMEventTargetHelper , public nsIObserver , public nsIStreamListener , public nsIChannelEventSink , public nsIInterfaceRequestor , public nsSupportsWeakReference { -friend class AsyncVerifyRedirectCallbackFwr; - public: explicit EventSource(nsPIDOMWindow* aOwnerWindow); NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED( EventSource, DOMEventTargetHelper) NS_DECL_NSIOBSERVER NS_DECL_NSISTREAMLISTENER @@ -227,37 +224,26 @@ protected: // used while reading the input streams nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder; nsresult mLastConvertionResult; nsString mLastFieldName; nsString mLastFieldValue; nsCOMPtr<nsILoadGroup> mLoadGroup; - /** - * The notification callbacks the channel had initially. - * We want to forward things here as needed. - */ - nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks; - nsCOMPtr<nsIChannelEventSink> mChannelEventSink; - nsCOMPtr<nsIHttpChannel> mHttpChannel; nsCOMPtr<nsITimer> mTimer; uint16_t mReadyState; nsString mOriginalURL; nsCOMPtr<nsIPrincipal> mPrincipal; nsString mOrigin; - uint32_t mRedirectFlags; - nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; - nsCOMPtr<nsIChannel> mNewRedirectChannel; - // Event Source owner information: // - the script file name // - source code line number and column number where the Event Source object // was constructed. // - the ID of the inner window where the script lives. Note that this may not // be the same as the Event Source owner window. // These attributes are used for error reporting. nsString mScriptFile;
--- a/dom/base/WindowNamedPropertiesHandler.cpp +++ b/dom/base/WindowNamedPropertiesHandler.cpp @@ -269,19 +269,33 @@ static const DOMIfaceAndProtoJSClass Win // static JSObject* WindowNamedPropertiesHandler::Create(JSContext* aCx, JS::Handle<JSObject*> aProto) { // Note: since the scope polluter proxy lives on the window's prototype // chain, it needs a singleton type to avoid polluting type information // for properties on the window. - JS::Rooted<JSObject*> gsp(aCx); js::ProxyOptions options; options.setSingleton(true); options.setClass(&WindowNamedPropertiesClass.mBase); - return js::NewProxyObject(aCx, WindowNamedPropertiesHandler::getInstance(), - JS::NullHandleValue, aProto, - options); + + JS::Rooted<JSObject*> gsp(aCx); + gsp = js::NewProxyObject(aCx, WindowNamedPropertiesHandler::getInstance(), + JS::NullHandleValue, aProto, + options); + if (!gsp) { + return nullptr; + } + + bool succeeded; + if (!JS_SetImmutablePrototype(aCx, gsp, &succeeded)) { + return nullptr; + } + MOZ_ASSERT(succeeded, + "errors making the [[Prototype]] of the named properties object " + "immutable should have been JSAPI failures, not !succeeded"); + + return gsp; } } // namespace dom } // namespace mozilla
--- a/dom/events/MessageEvent.cpp +++ b/dom/events/MessageEvent.cpp @@ -195,16 +195,59 @@ MessageEvent::InitMessageEvent(const nsA mOrigin = aOrigin; mLastEventId = aLastEventId; mWindowSource = aSource; return NS_OK; } void +MessageEvent::InitMessageEvent(JSContext* aCx, const nsAString& aType, + bool aCanBubble, bool aCancelable, + JS::Handle<JS::Value> aData, + const nsAString& aOrigin, + const nsAString& aLastEventId, + const Nullable<WindowProxyOrMessagePort>& aSource, + const Nullable<Sequence<OwningNonNull<MessagePort>>>& aPorts, + ErrorResult& aRv) +{ + aRv = Event::InitEvent(aType, aCanBubble, aCancelable); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + mData = aData; + mozilla::HoldJSObjects(this); + mOrigin = aOrigin; + mLastEventId = aLastEventId; + + mWindowSource = nullptr; + mPortSource = nullptr; + + if (!aSource.IsNull()) { + if (aSource.Value().IsWindowProxy()) { + mWindowSource = aSource.Value().GetAsWindowProxy(); + } else { + mPortSource = &aSource.Value().GetAsMessagePort(); + } + } + + mPorts = nullptr; + + if (!aPorts.IsNull()) { + nsTArray<nsRefPtr<MessagePort>> ports; + for (uint32_t i = 0, len = aPorts.Value().Length(); i < len; ++i) { + ports.AppendElement(aPorts.Value()[i]); + } + + mPorts = new MessagePortList(static_cast<Event*>(this), ports); + } +} + +void MessageEvent::SetPorts(MessagePortList* aPorts) { MOZ_ASSERT(!mPorts && aPorts); mPorts = aPorts; } void MessageEvent::SetSource(mozilla::dom::MessagePort* aPort) @@ -222,13 +265,13 @@ MessageEvent::SetSource(mozilla::dom::wo } // namespace mozilla using namespace mozilla; using namespace mozilla::dom; already_AddRefed<MessageEvent> NS_NewDOMMessageEvent(EventTarget* aOwner, nsPresContext* aPresContext, - WidgetEvent* aEvent) + WidgetEvent* aEvent) { nsRefPtr<MessageEvent> it = new MessageEvent(aOwner, aPresContext, aEvent); return it.forget(); }
--- a/dom/events/MessageEvent.h +++ b/dom/events/MessageEvent.h @@ -3,27 +3,29 @@ /* 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_MessageEvent_h_ #define mozilla_dom_MessageEvent_h_ #include "mozilla/dom/Event.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/MessagePortList.h" #include "nsCycleCollectionParticipant.h" #include "nsIDOMMessageEvent.h" -#include "mozilla/dom/MessagePortList.h" namespace mozilla { namespace dom { struct MessageEventInit; class MessagePort; class MessagePortList; class OwningWindowProxyOrMessagePortOrClient; +class WindowProxyOrMessagePort; namespace workers { class ServiceWorkerClient; } // namespace workers /** @@ -80,16 +82,23 @@ public: ErrorResult& aRv); static already_AddRefed<MessageEvent> Constructor(EventTarget* aEventTarget, const nsAString& aType, const MessageEventInit& aEventInit, ErrorResult& aRv); + void InitMessageEvent(JSContext* aCx, const nsAString& aType, bool aCanBubble, + bool aCancelable, JS::Handle<JS::Value> aData, + const nsAString& aOrigin, const nsAString& aLastEventId, + const Nullable<WindowProxyOrMessagePort>& aSource, + const Nullable<Sequence<OwningNonNull<MessagePort>>>& aPorts, + ErrorResult& aRv); + protected: ~MessageEvent(); private: JS::Heap<JS::Value> mData; nsString mOrigin; nsString mLastEventId; nsCOMPtr<nsIDOMWindow> mWindowSource;
--- a/dom/events/test/test_messageEvent.html +++ b/dom/events/test/test_messageEvent.html @@ -8,16 +8,33 @@ https://bugzilla.mozilla.org/show_bug.cg <title>Test for Bug 848294</title> <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> </head> <body> <script type="application/javascript"> + function testMessageEvent(e, test) { + ok(e, "MessageEvent created"); + is(e.type, 'message', 'MessageEvent.type is right'); + + is(e.data, 'data' in test ? test.data : undefined, 'MessageEvent.data is ok'); + is(e.origin, 'origin' in test ? test.origin : '', 'MessageEvent.origin is ok'); + is(e.lastEventId, 'lastEventId' in test ? test.lastEventId : '', 'MessageEvent.lastEventId is ok'); + is(e.source, 'source' in test ? test.source : null, 'MessageEvent.source is ok'); + + if (test.ports != undefined) { + is(e.ports.length, test.ports.length, 'MessageEvent.ports is ok'); + is(e.ports, e.ports, 'MessageEvent.ports is ok'); + } else { + ok(!('ports' in test) || test.ports == null, 'MessageEvent.ports is ok'); + } + } + function runTest() { var channel = new MessageChannel(); var tests = [ {}, { data: 42 }, { data: {} }, { data: true, origin: 'wow' }, @@ -28,30 +45,26 @@ https://bugzilla.mozilla.org/show_bug.cg { data: window, source: channel.port1, ports: [ channel.port1, channel.port2 ] }, { data: null, ports: null }, ]; while (tests.length) { var test = tests.shift(); var e = new MessageEvent('message', test); - ok(e, "MessageEvent created"); - is(e.type, 'message', 'MessageEvent.type is right'); + testMessageEvent(e, test); - is(e.data, 'data' in test ? test.data : undefined, 'MessageEvent.data is ok'); - is(e.origin, 'origin' in test ? test.origin : '', 'MessageEvent.origin is ok'); - is(e.lastEventId, 'lastEventId' in test ? test.lastEventId : '', 'MessageEvent.lastEventId is ok'); - is(e.source, 'source' in test ? test.source : null, 'MessageEvent.source is ok'); - - if (test.ports != undefined) { - is(e.ports.length, test.ports.length, 'MessageEvent.ports is ok'); - is(e.ports, e.ports, 'MessageEvent.ports is ok'); - } else { - ok(!('ports' in test) || test.ports == null, 'MessageEvent.ports is ok'); - } + e = new MessageEvent('message'); + e.initMessageEvent('message', true, true, + 'data' in test ? test.data : undefined, + 'origin' in test ? test.origin : '', + 'lastEventId' in test ? test.lastEventId : '', + 'source' in test ? test.source : null, + 'ports' in test ? test.ports : null); + testMessageEvent(e, test); } try { var e = new MessageEvent('foobar', { source: 42 }); ok(false, "Source has to be a window or a port"); } catch(e) { ok(true, "Source has to be a window or a port"); }
--- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -2,16 +2,17 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/DebugOnly.h" #include "mozilla/dom/FetchDriver.h" +#include "nsIAsyncVerifyRedirectCallback.h" #include "nsIDocument.h" #include "nsIInputStream.h" #include "nsIOutputStream.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIHttpHeaderVisitor.h" #include "nsIJARChannel.h" #include "nsIScriptSecurityManager.h" @@ -37,17 +38,17 @@ #include "InternalRequest.h" #include "InternalResponse.h" namespace mozilla { namespace dom { NS_IMPL_ISUPPORTS(FetchDriver, nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor, - nsIAsyncVerifyRedirectCallback, nsIThreadRetargetableStreamListener) + nsIThreadRetargetableStreamListener) FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) : mPrincipal(aPrincipal) , mLoadGroup(aLoadGroup) , mRequest(aRequest) , mFetchRecursionCount(0) , mCORSFlagEverSet(false) @@ -454,17 +455,23 @@ FetchDriver::HttpFetch(bool aCORSFlag, b mLoadGroup = nullptr; if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Insert ourselves into the notification callbacks chain so we can handle // cross-origin redirects. - chan->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks)); +#ifdef DEBUG + { + nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks; + chan->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks)); + MOZ_ASSERT(!notificationCallbacks); + } +#endif chan->SetNotificationCallbacks(this); // FIXME(nsm): Bug 1120715. // Step 3.4 "If request's cache mode is default and request's header list // contains a header named `If-Modified-Since`, `If-None-Match`, // `If-Unmodified-Since`, `If-Match`, or `If-Range`, set request's cache mode // to no-store." @@ -910,18 +917,16 @@ FetchDriver::OnStopRequest(nsIRequest* a NS_IMETHODIMP FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags, nsIAsyncVerifyRedirectCallback *aCallback) { NS_PRECONDITION(aNewChannel, "Redirect without a channel?"); - nsresult rv; - // HTTP Fetch step 5, "redirect status", step 1 if (NS_WARN_IF(mRequest->GetRedirectMode() == RequestRedirect::Error)) { aOldChannel->Cancel(NS_BINDING_FAILED); return NS_BINDING_FAILED; } // HTTP Fetch step 5, "redirect status", steps 2 through 6 are automatically // handled by necko before calling AsyncOnChannelRedirect() with the new @@ -965,37 +970,95 @@ FetchDriver::AsyncOnChannelRedirect(nsIC // The following steps are from HTTP Fetch step 5, "redirect status", step 11 // which requires the RequestRedirect to be "follow". MOZ_ASSERT(mRequest->GetRedirectMode() == RequestRedirect::Follow); // HTTP Fetch step 5, "redirect status", steps 11.1 and 11.2 block redirecting // to a URL with credentials in CORS mode. This is implemented in // nsCORSListenerProxy. - mRedirectCallback = aCallback; - mOldRedirectChannel = aOldChannel; - mNewRedirectChannel = aNewChannel; + // On a successful redirect we perform the following substeps of HTTP Fetch, + // step 5, "redirect status", step 11. - nsCOMPtr<nsIChannelEventSink> outer = - do_GetInterface(mNotificationCallbacks); - if (outer) { - // The callee is supposed to call OnRedirectVerifyCallback() on success, - // and nobody has to call it on failure, so we can just return after this - // block. - rv = outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, this); - if (NS_FAILED(rv)) { - aOldChannel->Cancel(rv); - mRedirectCallback = nullptr; - mOldRedirectChannel = nullptr; - mNewRedirectChannel = nullptr; - } + // Step 11.5 "Append locationURL to request's url list." so that when we set the + // Response's URL from the Request's URL in Main Fetch, step 15, we get the + // final value. Note, we still use a single URL value instead of a list. + nsCOMPtr<nsIURI> newURI; + nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI)); + if (NS_FAILED(rv)) { + aOldChannel->Cancel(rv); return rv; } - (void) OnRedirectVerifyCallback(NS_OK); + // We need to update our request's URL. + nsAutoCString newUrl; + newURI->GetSpec(newUrl); + mRequest->SetURL(newUrl); + + // Implement Main Fetch step 8 again on redirect. + MainFetchOp nextOp = SetTaintingAndGetNextOp(mCORSFlagEverSet); + + if (nextOp.mType == NETWORK_ERROR) { + // Cancel the channel if Main Fetch blocks the redirect from continuing. + aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI); + return NS_ERROR_DOM_BAD_URI; + } + + // Otherwise, we rely on necko and the CORS proxy to do the right thing + // as the redirect is followed. In general this means basic or http + // fetch. If we've ever been CORS, we need to stay CORS. + MOZ_ASSERT(nextOp.mType == BASIC_FETCH || nextOp.mType == HTTP_FETCH); + MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mType == HTTP_FETCH); + MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mCORSFlag); + + // Examine and possibly set the LOAD_ANONYMOUS flag on the channel. + nsLoadFlags flags; + rv = aNewChannel->GetLoadFlags(&flags); + if (NS_FAILED(rv)) { + aOldChannel->Cancel(rv); + return rv; + } + + if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && + mRequest->GetResponseTainting() == InternalRequest::RESPONSETAINT_OPAQUE) { + // In the case of a "no-cors" mode request with "same-origin" credentials, + // we have to set LOAD_ANONYMOUS manually here in order to avoid sending + // credentials on a cross-origin redirect. + flags |= nsIRequest::LOAD_ANONYMOUS; + rv = aNewChannel->SetLoadFlags(flags); + if (NS_FAILED(rv)) { + aOldChannel->Cancel(rv); + return rv; + } + + } else if (mRequest->GetCredentialsMode() == RequestCredentials::Omit) { + // Make sure nothing in the redirect chain screws up our credentials + // settings. LOAD_ANONYMOUS must be set if we RequestCredentials is "omit". + MOZ_ASSERT(flags & nsIRequest::LOAD_ANONYMOUS); + + } else if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && + nextOp.mCORSFlag) { + // We also want to verify the LOAD_ANONYMOUS flag is set when we are in + // "same-origin" credentials mode and the CORS flag is set. We can't + // unconditionally assert here, however, because the nsCORSListenerProxy + // will set the flag later in the redirect callback chain. Instead, + // perform a weaker assertion here by checking if CORS flag was set + // before this redirect. In that case LOAD_ANONYMOUS must still be set. + MOZ_ASSERT_IF(mCORSFlagEverSet, flags & nsIRequest::LOAD_ANONYMOUS); + + } else { + // Otherwise, we should be sending credentials + MOZ_ASSERT(!(flags & nsIRequest::LOAD_ANONYMOUS)); + } + + // Track the CORSFlag through redirects. + mCORSFlagEverSet = mCORSFlagEverSet || nextOp.mCORSFlag; + + aCallback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; } NS_IMETHODIMP FetchDriver::CheckListenerChain() { return NS_OK; } @@ -1031,123 +1094,30 @@ NS_IMETHODIMP FetchDriver::GetInterface(const nsIID& aIID, void **aResult) { if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { *aResult = static_cast<nsIChannelEventSink*>(this); NS_ADDREF_THIS(); return NS_OK; } - nsresult rv; - - if (mNotificationCallbacks) { - rv = mNotificationCallbacks->GetInterface(aIID, aResult); - if (NS_SUCCEEDED(rv)) { - NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!"); - return rv; - } - } - else if (aIID.Equals(NS_GET_IID(nsIStreamListener))) { + if (aIID.Equals(NS_GET_IID(nsIStreamListener))) { *aResult = static_cast<nsIStreamListener*>(this); NS_ADDREF_THIS(); return NS_OK; } - else if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) { + if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) { *aResult = static_cast<nsIRequestObserver*>(this); NS_ADDREF_THIS(); return NS_OK; } return QueryInterface(aIID, aResult); } -NS_IMETHODIMP -FetchDriver::OnRedirectVerifyCallback(nsresult aResult) -{ - // On a successful redirect we perform the following substeps of HTTP Fetch, - // step 5, "redirect status", step 11. - if (NS_SUCCEEDED(aResult)) { - // Step 11.5 "Append locationURL to request's url list." so that when we set the - // Response's URL from the Request's URL in Main Fetch, step 15, we get the - // final value. Note, we still use a single URL value instead of a list. - nsCOMPtr<nsIURI> newURI; - nsresult rv = NS_GetFinalChannelURI(mNewRedirectChannel, getter_AddRefs(newURI)); - if (NS_WARN_IF(NS_FAILED(rv))) { - aResult = rv; - } else { - // We need to update our request's URL. - nsAutoCString newUrl; - newURI->GetSpec(newUrl); - mRequest->SetURL(newUrl); - } - } - - if (NS_FAILED(aResult)) { - mOldRedirectChannel->Cancel(aResult); - } - - // Implement Main Fetch step 8 again on redirect. - MainFetchOp nextOp = SetTaintingAndGetNextOp(mCORSFlagEverSet); - - if (nextOp.mType == NETWORK_ERROR) { - // Cancel the channel if Main Fetch blocks the redirect from continuing. - aResult = NS_ERROR_DOM_BAD_URI; - mOldRedirectChannel->Cancel(aResult); - } else { - // Otherwise, we rely on necko and the CORS proxy to do the right thing - // as the redirect is followed. In general this means basic or http - // fetch. If we've ever been CORS, we need to stay CORS. - MOZ_ASSERT(nextOp.mType == BASIC_FETCH || nextOp.mType == HTTP_FETCH); - MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mType == HTTP_FETCH); - MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mCORSFlag); - - // Examine and possibly set the LOAD_ANONYMOUS flag on the channel. - nsLoadFlags flags; - aResult = mNewRedirectChannel->GetLoadFlags(&flags); - if (NS_SUCCEEDED(aResult)) { - if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && - mRequest->GetResponseTainting() == InternalRequest::RESPONSETAINT_OPAQUE) { - // In the case of a "no-cors" mode request with "same-origin" credentials, - // we have to set LOAD_ANONYMOUS manually here in order to avoid sending - // credentials on a cross-origin redirect. - flags |= nsIRequest::LOAD_ANONYMOUS; - aResult = mNewRedirectChannel->SetLoadFlags(flags); - - } else if (mRequest->GetCredentialsMode() == RequestCredentials::Omit) { - // Make sure nothing in the redirect chain screws up our credentials - // settings. LOAD_ANONYMOUS must be set if we RequestCredentials is "omit". - MOZ_ASSERT(flags & nsIRequest::LOAD_ANONYMOUS); - - } else if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && - nextOp.mCORSFlag) { - // We also want to verify the LOAD_ANONYMOUS flag is set when we are in - // "same-origin" credentials mode and the CORS flag is set. We can't - // unconditionally assert here, however, because the nsCORSListenerProxy - // will set the flag later in the redirect callback chain. Instead, - // perform a weaker assertion here by checking if CORS flag was set - // before this redirect. In that case LOAD_ANONYMOUS must still be set. - MOZ_ASSERT_IF(mCORSFlagEverSet, flags & nsIRequest::LOAD_ANONYMOUS); - - } else { - // Otherwise, we should be sending credentials - MOZ_ASSERT(!(flags & nsIRequest::LOAD_ANONYMOUS)); - } - } - - // Track the CORSFlag through redirects. - mCORSFlagEverSet = mCORSFlagEverSet || nextOp.mCORSFlag; - } - - mOldRedirectChannel = nullptr; - mNewRedirectChannel = nullptr; - mRedirectCallback->OnRedirectVerifyCallback(aResult); - mRedirectCallback = nullptr; - return NS_OK; -} - void FetchDriver::SetDocument(nsIDocument* aDocument) { // Cannot set document after Fetch() has been called. MOZ_ASSERT(mFetchRecursionCount == 0); mDocument = aDocument; }
--- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -3,17 +3,16 @@ /* 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_FetchDriver_h #define mozilla_dom_FetchDriver_h #include "nsAutoPtr.h" -#include "nsIAsyncVerifyRedirectCallback.h" #include "nsIChannelEventSink.h" #include "nsIInterfaceRequestor.h" #include "nsIStreamListener.h" #include "nsIThreadRetargetableStreamListener.h" #include "mozilla/nsRefPtr.h" #include "mozilla/DebugOnly.h" #include "mozilla/net/ReferrerPolicy.h" @@ -53,46 +52,40 @@ protected: private: bool mGotResponseAvailable; }; class FetchDriver final : public nsIStreamListener, public nsIChannelEventSink, public nsIInterfaceRequestor, - public nsIAsyncVerifyRedirectCallback, public nsIThreadRetargetableStreamListener { public: NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSICHANNELEVENTSINK NS_DECL_NSIINTERFACEREQUESTOR - NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER explicit FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup); NS_IMETHOD Fetch(FetchDriverObserver* aObserver); void SetDocument(nsIDocument* aDocument); private: nsCOMPtr<nsIPrincipal> mPrincipal; nsCOMPtr<nsILoadGroup> mLoadGroup; nsRefPtr<InternalRequest> mRequest; nsRefPtr<InternalResponse> mResponse; nsCOMPtr<nsIOutputStream> mPipeOutputStream; nsRefPtr<FetchDriverObserver> mObserver; - nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks; - nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; - nsCOMPtr<nsIChannel> mOldRedirectChannel; - nsCOMPtr<nsIChannel> mNewRedirectChannel; nsCOMPtr<nsIDocument> mDocument; uint32_t mFetchRecursionCount; bool mCORSFlagEverSet; DebugOnly<bool> mResponseAvailableCalled; FetchDriver() = delete; FetchDriver(const FetchDriver&) = delete;
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -1438,27 +1438,28 @@ TabParent::RecvPDocAccessibleConstructor } #endif return true; } a11y::DocAccessibleParent* TabParent::GetTopLevelDocAccessible() const { +#ifdef ACCESSIBILITY // XXX Consider managing non top level PDocAccessibles with their parent // document accessible. const nsTArray<PDocAccessibleParent*>& docs = ManagedPDocAccessibleParent(); size_t docCount = docs.Length(); for (size_t i = 0; i < docCount; i++) { auto doc = static_cast<a11y::DocAccessibleParent*>(docs[i]); if (!doc->ParentDoc()) { return doc; } } - +#endif return nullptr; } PDocumentRendererParent* TabParent::AllocPDocumentRendererParent(const nsRect& documentRect, const gfx::Matrix& transform, const nsString& bgcolor, const uint32_t& renderFlags,
--- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -34,19 +34,19 @@ class nsIFrameLoader; class nsIContent; class nsIPrincipal; class nsIURI; class nsILoadContext; class nsIDocShell; namespace mozilla { - namespace a11y { +namespace a11y { class DocAccessibleParent; - } +} namespace jsipc { class CpowHolder; } // namespace jsipc namespace layers { class AsyncDragMetrics; struct FrameMetrics; @@ -261,17 +261,17 @@ public: virtual bool RecvPDocAccessibleConstructor(PDocAccessibleParent* aDoc, PDocAccessibleParent* aParentDoc, const uint64_t& aParentID) override; /** * Return the top level doc accessible parent for this tab. */ - a11y::DocAccessibleParent* GetTopLevelDocAccessible() const; + a11y::DocAccessibleParent* GetTopLevelDocAccessible() const; void LoadURL(nsIURI* aURI); // XXX/cjones: it's not clear what we gain by hiding these // message-sending functions under a layer of indirection and // eating the return values void Show(const ScreenIntSize& size, bool aParentIsActive); void UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size); void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
--- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -844,16 +844,20 @@ MediaDecoderStateMachine::OnVideoDecoded aVideoSample->AdjustForStartTime(StartTime()); mDecodedVideoEndTime = video ? video->GetEndTime() : mDecodedVideoEndTime; SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d", (video ? video->mTime : -1), (video ? video->GetEndTime() : -1), (video ? video->mDiscontinuity : 0)); + // Check frame validity here for every decoded frame in order to have a + // better chance to make the decision of turning off HW acceleration. + CheckFrameValidity(aVideoSample->As<VideoData>()); + switch (mState) { case DECODER_STATE_BUFFERING: { // If we're buffering, this may be the sample we need to stop buffering. // Save it and schedule the state machine. Push(video, MediaData::VIDEO_DATA); ScheduleStateMachine(); return; } @@ -2443,26 +2447,21 @@ MediaDecoderStateMachine::Reset() mPlaybackOffset = 0; nsCOMPtr<nsIRunnable> resetTask = NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode); DecodeTaskQueue()->Dispatch(resetTask.forget()); } -bool MediaDecoderStateMachine::CheckFrameValidity(VideoData* aData) +void +MediaDecoderStateMachine::CheckFrameValidity(VideoData* aData) { MOZ_ASSERT(OnTaskQueue()); - // If we've sent this frame before then only return the valid state, - // don't update the statistics. - if (aData->mSentToCompositor) { - return !aData->mImage || aData->mImage->IsValid(); - } - // Update corrupt-frames statistics if (aData->mImage && !aData->mImage->IsValid()) { FrameStatistics& frameStats = mDecoder->GetFrameStatistics(); frameStats.NotifyCorruptFrame(); // If more than 10% of the last 30 frames have been corrupted, then try disabling // hardware acceleration. We use 10 as the corrupt value because RollingMean<> // only supports integer types. mCorruptFrames.insert(10); @@ -2470,20 +2469,18 @@ bool MediaDecoderStateMachine::CheckFram frameStats.GetPresentedFrames() > 60 && mCorruptFrames.mean() >= 2 /* 20% */) { nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(mReader, &MediaDecoderReader::DisableHardwareAcceleration); DecodeTaskQueue()->Dispatch(task.forget()); mCorruptFrames.clear(); gfxCriticalNote << "Too many dropped/corrupted frames, disabling DXVA"; } - return false; } else { mCorruptFrames.insert(0); - return true; } } void MediaDecoderStateMachine::RenderVideoFrames(int32_t aMaxFrames, int64_t aClockTime, const TimeStamp& aClockTimeStamp) { MOZ_ASSERT(OnTaskQueue()); @@ -2495,17 +2492,17 @@ void MediaDecoderStateMachine::RenderVid return; } nsAutoTArray<ImageContainer::NonOwningImage,16> images; TimeStamp lastFrameTime; for (uint32_t i = 0; i < frames.Length(); ++i) { VideoData* frame = frames[i]->As<VideoData>(); - bool valid = CheckFrameValidity(frame); + bool valid = !frame->mImage || frame->mImage->IsValid(); frame->mSentToCompositor = true; if (!valid) { continue; } int64_t frameTime = frame->mTime; if (frameTime < 0) {
--- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -458,18 +458,19 @@ protected: void SetStartTime(int64_t aStartTimeUsecs); // Update only the state machine's current playback position (and duration, // if unknown). Does not update the playback position on the decoder or // media element -- use UpdatePlaybackPosition for that. Called on the state // machine thread, caller must hold the decoder lock. void UpdatePlaybackPositionInternal(int64_t aTime); - // Decode monitor must be held. - bool CheckFrameValidity(VideoData* aData); + // Decode monitor must be held. To determine if MDSM needs to turn off HW + // acceleration. + void CheckFrameValidity(VideoData* aData); // Sets VideoQueue images into the VideoFrameContainer. Called on the shared // state machine thread. Decode monitor must be held. The first aMaxFrames // (at most) are set. // aClockTime and aClockTimeStamp are used as the baseline for deriving // timestamps for the frames; when omitted, aMaxFrames must be 1 and // a null timestamp is passed to the VideoFrameContainer. // If the VideoQueue is empty, this does nothing.
--- a/dom/media/mediasource/test/mochitest.ini +++ b/dom/media/mediasource/test/mochitest.ini @@ -1,10 +1,10 @@ [DEFAULT] -skip-if = e10s || buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined) +skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined) support-files = mediasource.js seek.webm seek.webm^headers^ seek_lowres.webm seek_lowres.webm^headers^ bipbop/bipbop2s.mp4 bipbop/bipbop2s.mp4^headers^ bipbop/bipbopinit.mp4 bipbop/bipbop_audioinit.mp4 bipbop/bipbop_videoinit.mp4 bipbop/bipbop1.m4s bipbop/bipbop_audio1.m4s bipbop/bipbop_video1.m4s bipbop/bipbop2.m4s bipbop/bipbop_audio2.m4s bipbop/bipbop_video2.m4s
--- a/dom/messagechannel/MessagePortList.h +++ b/dom/messagechannel/MessagePortList.h @@ -61,17 +61,17 @@ public: { aFound = aIndex < mPorts.Length(); if (!aFound) { return nullptr; } return mPorts[aIndex]; } -public: +private: nsCOMPtr<nsISupports> mOwner; nsTArray<nsRefPtr<MessagePort>> mPorts; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_MessagePortList_h
--- a/dom/webidl/MessageEvent.webidl +++ b/dom/webidl/MessageEvent.webidl @@ -39,16 +39,22 @@ interface MessageEvent : Event { readonly attribute (WindowProxy or MessagePort or Client)? source; /** * Initializes this event with the given data, in a manner analogous to * the similarly-named method on the nsIDOMEvent interface, also setting the * data, origin, source, and lastEventId attributes of this appropriately. */ readonly attribute MessagePortList? ports; + + [Throws] + void initMessageEvent(DOMString type, boolean bubbles, boolean cancelable, + any data, DOMString origin, DOMString lastEventId, + (WindowProxy or MessagePort)? source, + sequence<MessagePort>? ports); }; dictionary MessageEventInit : EventInit { any data; DOMString origin; DOMString lastEventId; (Window or MessagePort)? source = null; sequence<MessagePort>? ports;
--- a/editor/libeditor/nsHTMLEditRules.cpp +++ b/editor/libeditor/nsHTMLEditRules.cpp @@ -6388,35 +6388,35 @@ nsHTMLEditRules::IsInListItem(nsINode* a // ReturnInHeader: do the right thing for returns pressed in headers // nsresult nsHTMLEditRules::ReturnInHeader(Selection* aSelection, nsIDOMNode *aHeader, nsIDOMNode *aNode, int32_t aOffset) { - NS_ENSURE_TRUE(aSelection && aHeader && aNode, NS_ERROR_NULL_POINTER); + nsCOMPtr<Element> header = do_QueryInterface(aHeader); + nsCOMPtr<nsINode> node = do_QueryInterface(aNode); + NS_ENSURE_TRUE(aSelection && header && node, NS_ERROR_NULL_POINTER); // remeber where the header is int32_t offset; nsCOMPtr<nsIDOMNode> headerParent = nsEditor::GetNodeLocation(aHeader, &offset); // get ws code to adjust any ws - nsCOMPtr<nsINode> selNode(do_QueryInterface(aNode)); NS_ENSURE_STATE(mHTMLEditor); nsresult res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, - address_of(selNode), + address_of(node), &aOffset); NS_ENSURE_SUCCESS(res, res); // split the header - int32_t newOffset; + NS_ENSURE_STATE(node->IsContent()); NS_ENSURE_STATE(mHTMLEditor); - res = mHTMLEditor->SplitNodeDeep(aHeader, GetAsDOMNode(selNode), aOffset, &newOffset); - NS_ENSURE_SUCCESS(res, res); + mHTMLEditor->SplitNodeDeep(*header, *node->AsContent(), aOffset); // if the leftand heading is empty, put a mozbr in it nsCOMPtr<nsIDOMNode> prevItem; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetPriorHTMLSibling(aHeader, address_of(prevItem)); if (prevItem && nsHTMLEditUtils::IsHeader(prevItem)) { bool bIsEmptyNode; @@ -6593,52 +6593,55 @@ nsHTMLEditRules::ReturnInParagraph(Selec // nsresult nsHTMLEditRules::SplitParagraph(nsIDOMNode *aPara, nsIDOMNode *aBRNode, Selection* aSelection, nsCOMPtr<nsIDOMNode> *aSelNode, int32_t *aOffset) { - NS_ENSURE_TRUE(aPara && aBRNode && aSelNode && *aSelNode && aOffset && aSelection, NS_ERROR_NULL_POINTER); + nsCOMPtr<Element> para = do_QueryInterface(aPara); + NS_ENSURE_TRUE(para && aBRNode && aSelNode && *aSelNode && aOffset && + aSelection, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; // split para - int32_t newOffset; // get ws code to adjust any ws - nsCOMPtr<nsIDOMNode> leftPara, rightPara; + nsCOMPtr<nsIContent> leftPara, rightPara; NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr<nsINode> selNode(do_QueryInterface(*aSelNode)); res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), aOffset); *aSelNode = GetAsDOMNode(selNode); NS_ENSURE_SUCCESS(res, res); // split the paragraph NS_ENSURE_STATE(mHTMLEditor); - res = mHTMLEditor->SplitNodeDeep(aPara, *aSelNode, *aOffset, &newOffset, false, - address_of(leftPara), address_of(rightPara)); - NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_STATE(selNode->IsContent()); + mHTMLEditor->SplitNodeDeep(*para, *selNode->AsContent(), *aOffset, + nsHTMLEditor::EmptyContainers::yes, + getter_AddRefs(leftPara), + getter_AddRefs(rightPara)); // get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p) NS_ENSURE_STATE(mHTMLEditor); if (mHTMLEditor->IsVisBreak(aBRNode)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(aBRNode); NS_ENSURE_SUCCESS(res, res); } // remove ID attribute on the paragraph we just created nsCOMPtr<nsIDOMElement> rightElt = do_QueryInterface(rightPara); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveAttribute(rightElt, NS_LITERAL_STRING("id")); NS_ENSURE_SUCCESS(res, res); // check both halves of para to see if we need mozBR - res = InsertMozBRIfNeeded(leftPara); + res = InsertMozBRIfNeeded(GetAsDOMNode(leftPara)); NS_ENSURE_SUCCESS(res, res); - res = InsertMozBRIfNeeded(rightPara); + res = InsertMozBRIfNeeded(GetAsDOMNode(rightPara)); NS_ENSURE_SUCCESS(res, res); // selection to beginning of right hand para; // look inside any containers that are up front. nsCOMPtr<nsINode> rightParaNode = do_QueryInterface(rightPara); NS_ENSURE_STATE(mHTMLEditor && rightParaNode); nsCOMPtr<nsIDOMNode> child = GetAsDOMNode(mHTMLEditor->GetLeftmostChild(rightParaNode, true)); @@ -6746,20 +6749,19 @@ nsHTMLEditRules::ReturnInListItem(Select // else we want a new list item at the same list level. // get ws code to adjust any ws nsCOMPtr<nsINode> selNode(do_QueryInterface(aNode)); NS_ENSURE_STATE(mHTMLEditor); res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset); NS_ENSURE_SUCCESS(res, res); // now split list item - int32_t newOffset; NS_ENSURE_STATE(mHTMLEditor); - res = mHTMLEditor->SplitNodeDeep(aListItem, GetAsDOMNode(selNode), aOffset, &newOffset, false); - NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_STATE(selNode->IsContent()); + mHTMLEditor->SplitNodeDeep(*listItem, *selNode->AsContent(), aOffset); // hack: until I can change the damaged doc range code back to being // extra inclusive, I have to manually detect certain list items that // may be left empty. nsCOMPtr<nsIDOMNode> prevItem; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetPriorHTMLSibling(aListItem, address_of(prevItem)); if (prevItem && nsHTMLEditUtils::IsListItem(prevItem)) @@ -7151,24 +7153,25 @@ nsHTMLEditRules::SplitAsNeeded(nsIAtom& } splitNode = parent; } if (!tagParent) { // Could not find a place to build tag! return NS_ERROR_FAILURE; } - if (splitNode) { + if (splitNode && splitNode->IsContent() && inOutParent->IsContent()) { // We found a place for block, but above inOutParent. We need to split. NS_ENSURE_STATE(mHTMLEditor); - nsresult res = mHTMLEditor->SplitNodeDeep(splitNode->AsDOMNode(), - inOutParent->AsDOMNode(), - inOutOffset, &inOutOffset); - NS_ENSURE_SUCCESS(res, res); + int32_t offset = mHTMLEditor->SplitNodeDeep(*splitNode->AsContent(), + *inOutParent->AsContent(), + inOutOffset); + NS_ENSURE_STATE(offset != -1); inOutParent = tagParent; + inOutOffset = offset; } return NS_OK; } /** * JoinNodesSmart: Join two nodes, doing whatever makes sense for their * children (which often means joining them, too). aNodeLeft & aNodeRight must * be same type of node.
--- a/editor/libeditor/nsHTMLEditor.cpp +++ b/editor/libeditor/nsHTMLEditor.cpp @@ -1578,53 +1578,53 @@ nsHTMLEditor::InsertNodeAtPoint(nsIDOMNo { nsCOMPtr<nsIContent> node = do_QueryInterface(aNode); NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(ioParent, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(*ioParent, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(ioOffset, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; - nsCOMPtr<nsINode> parent = do_QueryInterface(*ioParent); + nsCOMPtr<nsIContent> parent = do_QueryInterface(*ioParent); NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); - nsCOMPtr<nsINode> topChild = parent; - int32_t offsetOfInsert = *ioOffset; + nsCOMPtr<nsIContent> topChild = parent; + nsCOMPtr<nsIContent> origParent = parent; // Search up the parent chain to find a suitable container while (!CanContain(*parent, *node)) { // If the current parent is a root (body or table element) // then go no further - we can't insert if (parent->IsHTMLElement(nsGkAtoms::body) || nsHTMLEditUtils::IsTableElement(parent)) { return NS_ERROR_FAILURE; } // Get the next parent NS_ENSURE_TRUE(parent->GetParentNode(), NS_ERROR_FAILURE); if (!IsEditable(parent->GetParentNode())) { // There's no suitable place to put the node in this editing host. Maybe // someone is trying to put block content in a span. So just put it // where we were originally asked. - parent = topChild = do_QueryInterface(*ioParent); - NS_ENSURE_STATE(parent); + parent = topChild = origParent; break; } topChild = parent; - parent = parent->GetParentNode(); + parent = parent->GetParent(); } if (parent != topChild) { // we need to split some levels above the original selection parent - res = SplitNodeDeep(GetAsDOMNode(topChild), *ioParent, *ioOffset, - &offsetOfInsert, aNoEmptyNodes); - NS_ENSURE_SUCCESS(res, res); + int32_t offset = SplitNodeDeep(*topChild, *origParent, *ioOffset, + aNoEmptyNodes ? EmptyContainers::no + : EmptyContainers::yes); + NS_ENSURE_STATE(offset != -1); *ioParent = GetAsDOMNode(parent); - *ioOffset = offsetOfInsert; + *ioOffset = offset; } // Now we can insert the new node - res = InsertNode(*node, *parent, offsetOfInsert); + res = InsertNode(*node, *parent, *ioOffset); return res; } NS_IMETHODIMP nsHTMLEditor::SelectElement(nsIDOMElement* aElement) { nsresult res = NS_ERROR_NULL_POINTER; @@ -1922,40 +1922,41 @@ nsHTMLEditor::MakeOrChangeList(const nsA res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; if (!handled) { // Find out if the selection is collapsed: bool isCollapsed = selection->Collapsed(); - nsCOMPtr<nsINode> node; - int32_t offset; - res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); - if (!node) res = NS_ERROR_FAILURE; - NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection->GetRangeAt(0) && + selection->GetRangeAt(0)->GetStartParent() && + selection->GetRangeAt(0)->GetStartParent()->IsContent(), + NS_ERROR_FAILURE); + OwningNonNull<nsIContent> node = + *selection->GetRangeAt(0)->GetStartParent()->AsContent(); + int32_t offset = selection->GetRangeAt(0)->StartOffset(); if (isCollapsed) { // have to find a place to put the list - nsCOMPtr<nsINode> parent = node; - nsCOMPtr<nsINode> topChild = node; + nsCOMPtr<nsIContent> parent = node; + nsCOMPtr<nsIContent> topChild = node; nsCOMPtr<nsIAtom> listAtom = do_GetAtom(aListType); while (!CanContainTag(*parent, *listAtom)) { topChild = parent; - parent = parent->GetParentNode(); + parent = parent->GetParent(); } if (parent != node) { // we need to split up to the child of parent - res = SplitNodeDeep(GetAsDOMNode(topChild), GetAsDOMNode(node), offset, - &offset); - NS_ENSURE_SUCCESS(res, res); + offset = SplitNodeDeep(*topChild, *node, offset); + NS_ENSURE_STATE(offset != -1); } // make a list nsCOMPtr<Element> newList = CreateNode(listAtom, parent, offset); NS_ENSURE_STATE(newList); // make a list item nsCOMPtr<Element> newItem = CreateNode(nsGkAtoms::li, newList, 0); NS_ENSURE_STATE(newItem); @@ -2053,41 +2054,42 @@ nsHTMLEditor::InsertBasicBlock(const nsA res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; if (!handled) { // Find out if the selection is collapsed: bool isCollapsed = selection->Collapsed(); - nsCOMPtr<nsINode> node; - int32_t offset; - res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); - if (!node) res = NS_ERROR_FAILURE; - NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection->GetRangeAt(0) && + selection->GetRangeAt(0)->GetStartParent() && + selection->GetRangeAt(0)->GetStartParent()->IsContent(), + NS_ERROR_FAILURE); + OwningNonNull<nsIContent> node = + *selection->GetRangeAt(0)->GetStartParent()->AsContent(); + int32_t offset = selection->GetRangeAt(0)->StartOffset(); if (isCollapsed) { // have to find a place to put the block - nsCOMPtr<nsINode> parent = node; - nsCOMPtr<nsINode> topChild = node; + nsCOMPtr<nsIContent> parent = node; + nsCOMPtr<nsIContent> topChild = node; nsCOMPtr<nsIAtom> blockAtom = do_GetAtom(aBlockType); while (!CanContainTag(*parent, *blockAtom)) { - NS_ENSURE_TRUE(parent->GetParentNode(), NS_ERROR_FAILURE); + NS_ENSURE_TRUE(parent->GetParent(), NS_ERROR_FAILURE); topChild = parent; - parent = parent->GetParentNode(); + parent = parent->GetParent(); } if (parent != node) { // we need to split up to the child of parent - res = SplitNodeDeep(GetAsDOMNode(topChild), GetAsDOMNode(node), offset, - &offset); - NS_ENSURE_SUCCESS(res, res); + offset = SplitNodeDeep(*topChild, *node, offset); + NS_ENSURE_STATE(offset != -1); } // make a block nsCOMPtr<Element> newBlock = CreateNode(blockAtom, parent, offset); NS_ENSURE_STATE(newBlock); // reposition selection to inside the block res = selection->Collapse(newBlock,0); @@ -2123,57 +2125,58 @@ nsHTMLEditor::Indent(const nsAString& aI nsTextRulesInfo ruleInfo(opID); res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; if (!handled) { // Do default - insert a blockquote node if selection collapsed - nsCOMPtr<nsINode> node; - int32_t offset; bool isCollapsed = selection->Collapsed(); - res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); - if (!node) res = NS_ERROR_FAILURE; - NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(selection->GetRangeAt(0) && + selection->GetRangeAt(0)->GetStartParent() && + selection->GetRangeAt(0)->GetStartParent()->IsContent(), + NS_ERROR_FAILURE); + OwningNonNull<nsIContent> node = + *selection->GetRangeAt(0)->GetStartParent()->AsContent(); + int32_t offset = selection->GetRangeAt(0)->StartOffset(); if (aIndent.EqualsLiteral("indent")) { if (isCollapsed) { // have to find a place to put the blockquote - nsCOMPtr<nsINode> parent = node; - nsCOMPtr<nsINode> topChild = node; + nsCOMPtr<nsIContent> parent = node; + nsCOMPtr<nsIContent> topChild = node; while (!CanContainTag(*parent, *nsGkAtoms::blockquote)) { - NS_ENSURE_TRUE(parent->GetParentNode(), NS_ERROR_FAILURE); + NS_ENSURE_TRUE(parent->GetParent(), NS_ERROR_FAILURE); topChild = parent; - parent = parent->GetParentNode(); + parent = parent->GetParent(); } if (parent != node) { // we need to split up to the child of parent - res = SplitNodeDeep(GetAsDOMNode(topChild), GetAsDOMNode(node), - offset, &offset); - NS_ENSURE_SUCCESS(res, res); + offset = SplitNodeDeep(*topChild, *node, offset); + NS_ENSURE_STATE(offset != -1); } // make a blockquote nsCOMPtr<Element> newBQ = CreateNode(nsGkAtoms::blockquote, parent, offset); NS_ENSURE_STATE(newBQ); // put a space in it so layout will draw the list item res = selection->Collapse(newBQ,0); NS_ENSURE_SUCCESS(res, res); res = InsertText(NS_LITERAL_STRING(" ")); NS_ENSURE_SUCCESS(res, res); // reposition selection to before the space character - res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); - NS_ENSURE_SUCCESS(res, res); - res = selection->Collapse(node,0); + NS_ENSURE_STATE(selection->GetRangeAt(0)); + res = selection->Collapse(selection->GetRangeAt(0)->GetStartParent(), + 0); NS_ENSURE_SUCCESS(res, res); } } } res = mRules->DidDoAction(selection, &ruleInfo, res); return res; }
--- a/editor/libeditor/nsHTMLEditorStyle.cpp +++ b/editor/libeditor/nsHTMLEditorStyle.cpp @@ -587,22 +587,32 @@ nsresult nsHTMLEditor::SplitStyleAbovePo (aProperty && node->IsHTMLElement(aProperty)) || // node is href - test if really <a href=... (aProperty == nsGkAtoms::href && nsHTMLEditUtils::IsLink(node)) || // or node is any prop, and we asked to split them all (!aProperty && NodeIsProperty(GetAsDOMNode(node))) || // or the style is specified in the style attribute isSet) { // found a style node we need to split - nsresult rv = SplitNodeDeep(GetAsDOMNode(node), *aNode, *aOffset, - &offset, false, outLeftNode, outRightNode); - NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIContent> outLeftContent, outRightContent; + nsCOMPtr<nsIContent> nodeParam = do_QueryInterface(*aNode); + NS_ENSURE_STATE(nodeParam || !*aNode); + offset = SplitNodeDeep(*node, *nodeParam, *aOffset, EmptyContainers::yes, + getter_AddRefs(outLeftContent), + getter_AddRefs(outRightContent)); + NS_ENSURE_TRUE(offset != -1, NS_ERROR_FAILURE); // reset startNode/startOffset *aNode = GetAsDOMNode(node->GetParent()); *aOffset = offset; + if (outLeftNode) { + *outLeftNode = GetAsDOMNode(outLeftContent); + } + if (outRightNode) { + *outRightNode = GetAsDOMNode(outRightContent); + } } node = node->GetParent(); } return NS_OK; } nsresult nsHTMLEditor::ClearStyle(nsCOMPtr<nsIDOMNode>* aNode, int32_t* aOffset,
--- a/gfx/2d/DrawTargetSkia.cpp +++ b/gfx/2d/DrawTargetSkia.cpp @@ -832,30 +832,28 @@ DrawTargetSkia::Init(const IntSize &aSiz { SkAlphaType alphaType = (aFormat == SurfaceFormat::B8G8R8X8) ? kOpaque_SkAlphaType : kPremul_SkAlphaType; SkImageInfo skiInfo = SkImageInfo::Make( aSize.width, aSize.height, GfxFormatToSkiaColorType(aFormat), alphaType); + // we need to have surfaces that have a stride aligned to 4 for interop with cairo + int stride = (BytesPerPixel(aFormat)*aSize.width + (4-1)) & -4; - SkAutoTUnref<SkBaseDevice> device(SkBitmapDevice::Create(skiInfo)); - if (!device) { - return false; - } - - SkBitmap bitmap = device->accessBitmap(true); + SkBitmap bitmap; + bitmap.setInfo(skiInfo, stride); if (!bitmap.allocPixels()) { return false; } bitmap.eraseARGB(0, 0, 0, 0); - mCanvas.adopt(new SkCanvas(device.get())); + mCanvas.adopt(new SkCanvas(bitmap)); mSize = aSize; mFormat = aFormat; return true; } #ifdef USE_SKIA_GPU bool
--- a/gfx/2d/SourceSurfaceD2DTarget.cpp +++ b/gfx/2d/SourceSurfaceD2DTarget.cpp @@ -59,16 +59,21 @@ SourceSurfaceD2DTarget::GetDataSurface() D3D10_TEXTURE2D_DESC desc; mTexture->GetDesc(&desc); desc.CPUAccessFlags = D3D10_CPU_ACCESS_READ; desc.Usage = D3D10_USAGE_STAGING; desc.BindFlags = 0; desc.MiscFlags = 0; + if (!Factory::GetDirect3D10Device()) { + gfxCriticalError() << "Invalid D3D10 device in D2D target surface"; + return nullptr; + } + HRESULT hr = Factory::GetDirect3D10Device()->CreateTexture2D(&desc, nullptr, byRef(dataSurf->mTexture)); if (FAILED(hr)) { gfxDebug() << "Failed to create staging texture for SourceSurface. Code: " << hexa(hr); return nullptr; } Factory::GetDirect3D10Device()->CopyResource(dataSurf->mTexture, mTexture);
--- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -1347,16 +1347,17 @@ GLContext::InitWithPrefix(const char *pr ClearSymbols(gpuShader4Symbols); } } if (IsSupported(GLFeature::map_buffer_range)) { SymLoadStruct mapBufferRangeSymbols[] = { { (PRFuncPtr*) &mSymbols.fMapBufferRange, { "MapBufferRange", nullptr } }, { (PRFuncPtr*) &mSymbols.fFlushMappedBufferRange, { "FlushMappedBufferRange", nullptr } }, + { (PRFuncPtr*) &mSymbols.fUnmapBuffer, { "UnmapBuffer", nullptr } }, END_SYMBOLS }; if (!LoadSymbols(mapBufferRangeSymbols, trygl, prefix)) { NS_ERROR("GL supports map_buffer_range without supplying its functions."); MarkUnsupported(GLFeature::map_buffer_range); ClearSymbols(mapBufferRangeSymbols);
--- a/gfx/skia/skia/src/core/SkDraw.cpp +++ b/gfx/skia/skia/src/core/SkDraw.cpp @@ -729,16 +729,26 @@ void SkDraw::drawPoints(SkCanvas::PointM path.rewind(); } break; } } } } +static inline SkPoint compute_stroke_size(const SkPaint& paint, const SkMatrix& matrix) { + SkASSERT(matrix.rectStaysRect()); + SkASSERT(SkPaint::kFill_Style != paint.getStyle()); + + SkVector size; + SkPoint pt = { paint.getStrokeWidth(), paint.getStrokeWidth() }; + matrix.mapVectors(&size, &pt, 1); + return SkPoint::Make(SkScalarAbs(size.fX), SkScalarAbs(size.fY)); +} + static bool easy_rect_join(const SkPaint& paint, const SkMatrix& matrix, SkPoint* strokeSize) { if (SkPaint::kMiter_Join != paint.getStrokeJoin() || paint.getStrokeMiter() < SK_ScalarSqrt2) { return false; } SkASSERT(matrix.rectStaysRect()); @@ -807,22 +817,32 @@ void SkDraw::drawRect(const SkRect& rect const SkMatrix& matrix = *fMatrix; SkRect devRect; // transform rect into devRect matrix.mapPoints(rect_points(devRect), rect_points(rect), 2); devRect.sort(); // look for the quick exit, before we build a blitter - SkIRect ir; - devRect.roundOut(&ir); + SkRect bbox = devRect; if (paint.getStyle() != SkPaint::kFill_Style) { // extra space for hairlines - ir.inset(-1, -1); + if (paint.getStrokeWidth() == 0) { + bbox.outset(1, 1); + } else { + // For kStroke_RectType, strokeSize is already computed. + const SkPoint& ssize = (kStroke_RectType == rtype) + ? strokeSize + : compute_stroke_size(paint, *fMatrix); + bbox.outset(SkScalarHalf(ssize.x()), SkScalarHalf(ssize.y())); + } } + + SkIRect ir; + bbox.roundOut(&ir); if (fRC->quickReject(ir)) { return; } SkDeviceLooper looper(*fBitmap, *fRC, ir, paint.isAntiAlias()); while (looper.next()) { SkRect localDevRect; looper.mapRect(&localDevRect, devRect);
--- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -2152,40 +2152,50 @@ gfxPlatform::UseProgressivePaint() } /*static*/ bool gfxPlatform::PerfWarnings() { return gfxPrefs::PerfWarnings(); } -void -gfxPlatform::GetAcceleratedCompositorBackends(nsTArray<LayersBackend>& aBackends) +static inline bool +AllowOpenGL(bool* aWhitelisted) { - // Being whitelisted is not enough to accelerate, but not being whitelisted is - // enough not to: - bool whitelisted = false; - nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); if (gfxInfo) { // bug 655578: on X11 at least, we must always call GetData (even if we don't need that information) // as that's what causes GfxInfo initialization which kills the zombie 'glxtest' process. // initially we relied on the fact that GetFeatureStatus calls GetData for us, but bug 681026 showed // that assumption to be unsafe. gfxInfo->GetData(); int32_t status; if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &status))) { if (status == nsIGfxInfo::FEATURE_STATUS_OK) { - aBackends.AppendElement(LayersBackend::LAYERS_OPENGL); - whitelisted = true; + *aWhitelisted = true; + return true; } } } + return gfxPrefs::LayersAccelerationForceEnabled(); +} + +void +gfxPlatform::GetAcceleratedCompositorBackends(nsTArray<LayersBackend>& aBackends) +{ + // Being whitelisted is not enough to accelerate, but not being whitelisted is + // enough not to: + bool whitelisted = false; + + if (AllowOpenGL(&whitelisted)) { + aBackends.AppendElement(LayersBackend::LAYERS_OPENGL); + } + if (!whitelisted) { static int tell_me_once = 0; if (!tell_me_once) { NS_WARNING("OpenGL-accelerated layers are not supported on this system"); tell_me_once = 1; } #ifdef MOZ_WIDGET_ANDROID NS_RUNTIMEABORT("OpenGL-accelerated layers are a hard requirement on this platform. "
--- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -1962,16 +1962,20 @@ gfxWindowsPlatform::CheckD3D11Support(bo *aCanUseHardware = !GetParentDevicePrefs().useD3D11WARP(); return FeatureStatus::Available; } if (gfxPrefs::LayersD3D11ForceWARP()) { *aCanUseHardware = false; return FeatureStatus::Available; } + if (gfxPrefs::LayersAccelerationForceEnabled()) { + *aCanUseHardware = true; + return FeatureStatus::Available; + } if (nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo()) { int32_t status; if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status))) { if (status != nsIGfxInfo::FEATURE_STATUS_OK) { if (CanUseWARP()) { *aCanUseHardware = false; return FeatureStatus::Available;
--- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -1787,16 +1787,17 @@ AsmJSModule::setProfilingEnabled(bool en uint8_t* caller = callerRetAddr - 4; Instruction* callerInsn = reinterpret_cast<Instruction*>(caller); BOffImm calleeOffset; callerInsn->as<InstBLImm>()->extractImm(&calleeOffset); void* callee = calleeOffset.getDest(callerInsn); #elif defined(JS_CODEGEN_ARM64) MOZ_CRASH(); void* callee = nullptr; + (void)callerRetAddr; #elif defined(JS_CODEGEN_MIPS32) Instruction* instr = (Instruction*)(callerRetAddr - 4 * sizeof(uint32_t)); void* callee = (void*)Assembler::ExtractLuiOriValue(instr, instr->next()); #elif defined(JS_CODEGEN_NONE) MOZ_CRASH(); void* callee = nullptr; #else # error "Missing architecture" @@ -1812,16 +1813,17 @@ AsmJSModule::setProfilingEnabled(bool en MOZ_ASSERT_IF(!profilingEnabled_, callee == entry); uint8_t* newCallee = enabled ? profilingEntry : entry; #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) X86Encoding::SetRel32(callerRetAddr, newCallee); #elif defined(JS_CODEGEN_ARM) new (caller) InstBLImm(BOffImm(newCallee - caller), Assembler::Always); #elif defined(JS_CODEGEN_ARM64) + (void)newCallee; MOZ_CRASH(); #elif defined(JS_CODEGEN_MIPS32) Assembler::WriteLuiOriInstructions(instr, instr->next(), ScratchRegister, (uint32_t)newCallee); instr[2] = InstReg(op_special, ScratchRegister, zero, ra, ff_jalr); #elif defined(JS_CODEGEN_NONE) MOZ_CRASH(); #else @@ -1877,16 +1879,18 @@ AsmJSModule::setProfilingEnabled(bool en if (enabled) { MOZ_ASSERT(reinterpret_cast<Instruction*>(jump)->is<InstNOP>()); new (jump) InstBImm(BOffImm(profilingEpilogue - jump), Assembler::Always); } else { MOZ_ASSERT(reinterpret_cast<Instruction*>(jump)->is<InstBImm>()); new (jump) InstNOP(); } #elif defined(JS_CODEGEN_ARM64) + (void)jump; + (void)profilingEpilogue; MOZ_CRASH(); #elif defined(JS_CODEGEN_MIPS32) Instruction* instr = (Instruction*)jump; if (enabled) { Assembler::WriteLuiOriInstructions(instr, instr->next(), ScratchRegister, (uint32_t)profilingEpilogue); instr[2] = InstReg(op_special, ScratchRegister, zero, zero, ff_jr); } else {
--- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -3106,23 +3106,30 @@ ASTSerializer::expression(ParseNode* pn, #if JS_HAS_GENERATOR_EXPRS case PNK_GENEXP: return generatorExpression(pn->generatorExpr(), dst); #endif case PNK_NEW: case PNK_TAGGED_TEMPLATE: case PNK_CALL: + case PNK_SUPERCALL: { ParseNode* next = pn->pn_head; MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); RootedValue callee(cx); - if (!expression(next, &callee)) - return false; + if (pn->isKind(PNK_SUPERCALL)) { + MOZ_ASSERT(next->isKind(PNK_POSHOLDER)); + if (!builder.super(&next->pn_pos, &callee)) + return false; + } else { + if (!expression(next, &callee)) + return false; + } NodeVector args(cx); if (!args.reserve(pn->pn_count - 1)) return false; for (next = next->pn_next; next; next = next->pn_next) { MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); @@ -3130,16 +3137,17 @@ ASTSerializer::expression(ParseNode* pn, if (!expression(next, &arg)) return false; args.infallibleAppend(arg); } if (pn->getKind() == PNK_TAGGED_TEMPLATE) return builder.taggedTemplate(callee, args, &pn->pn_pos, dst); + // SUPERCALL is Call(super, args) return pn->isKind(PNK_NEW) ? builder.newExpression(callee, args, &pn->pn_pos, dst) : builder.callExpression(callee, args, &pn->pn_pos, dst); } case PNK_DOT: {
--- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -863,17 +863,17 @@ SetSavedStacksRNGState(JSContext* cx, un CallArgs args = CallArgsFromVp(argc, vp); if (!args.requireAtLeast(cx, "setSavedStacksRNGState", 1)) return false; int32_t seed; if (!ToInt32(cx, args[0], &seed)) return false; - cx->compartment()->savedStacks().setRNGState((seed ^ RNG_MULTIPLIER) & RNG_MASK); + cx->compartment()->savedStacks().setRNGState(seed, seed * 33); return true; } static bool GetSavedFrameCount(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(cx->compartment()->savedStacks().count());
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1929,25 +1929,30 @@ BytecodeEmitter::checkSideEffects(ParseN // Trivial cases with no side effects. case PNK_NOP: case PNK_STRING: case PNK_TEMPLATE_STRING: case PNK_REGEXP: case PNK_TRUE: case PNK_FALSE: case PNK_NULL: - case PNK_THIS: case PNK_ELISION: case PNK_GENERATOR: case PNK_NUMBER: case PNK_OBJECT_PROPERTY_NAME: MOZ_ASSERT(pn->isArity(PN_NULLARY)); *answer = false; return true; + // |this| can throw in derived class constructors. + case PNK_THIS: + MOZ_ASSERT(pn->isArity(PN_NULLARY)); + *answer = sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); + return true; + // Trivial binary nodes with more token pos holders. case PNK_NEWTARGET: MOZ_ASSERT(pn->isArity(PN_BINARY)); MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER)); MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER)); *answer = false; return true; @@ -2180,16 +2185,17 @@ BytecodeEmitter::checkSideEffects(ParseN if ((pn = pn->pn_kid3)) goto restart; return true; // Function calls can invoke non-local code. case PNK_NEW: case PNK_CALL: case PNK_TAGGED_TEMPLATE: + case PNK_SUPERCALL: MOZ_ASSERT(pn->isArity(PN_LIST)); *answer = true; return true; // Classes typically introduce names. Even if no name is introduced, // the heritage and/or class body (through computed property names) // usually have effects. case PNK_CLASS: @@ -6722,55 +6728,72 @@ BytecodeEmitter::emitCallOrNew(ParseNode return false; emittingRunOnceLambda = false; } else { if (!emitTree(pn2)) return false; } callop = false; break; + case PNK_POSHOLDER: + MOZ_ASSERT(pn->isKind(PNK_SUPERCALL)); + MOZ_ASSERT(parser->handler.isSuperBase(pn2, cx)); + if (!emit1(JSOP_SUPERFUN)) + return false; + break; default: if (!emitTree(pn2)) return false; callop = false; /* trigger JSOP_UNDEFINED after */ break; } if (!callop) { JSOp thisop = pn->isKind(PNK_GENEXP) ? JSOP_THIS : JSOP_UNDEFINED; if (!emit1(thisop)) return false; } - bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW; + bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW || + pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL;; /* * Emit code for each argument in order, then emit the JSOP_*CALL or * JSOP_NEW bytecode with a two-byte immediate telling how many args * were pushed on the operand stack. */ bool oldEmittingForInit = emittingForInit; emittingForInit = false; if (!spread) { for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) { if (!emitTree(pn3)) return false; } if (isNewOp) { - // Repush the callee as new.target - if (!emitDupAt(argc + 1)) - return false; + if (pn->isKind(PNK_SUPERCALL)) { + if (!emit1(JSOP_NEWTARGET)) + return false; + } else { + // Repush the callee as new.target + if (!emitDupAt(argc + 1)) + return false; + } } } else { if (!emitArray(pn2->pn_next, argc, JSOP_SPREADCALLARRAY)) return false; if (isNewOp) { - if (!emitDupAt(2)) - return false; + if (pn->isKind(PNK_SUPERCALL)) { + if (!emit1(JSOP_NEWTARGET)) + return false; + } else { + if (!emitDupAt(2)) + return false; + } } } emittingForInit = oldEmittingForInit; if (!spread) { if (!emitCall(pn->getOp(), argc, pn)) return false; } else { @@ -6786,16 +6809,19 @@ BytecodeEmitter::emitCallOrNew(ParseNode uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); if (!emitUint32Operand(JSOP_LINENO, lineNum)) return false; } if (pn->pn_xflags & PNX_SETCALL) { if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS)) return false; } + + if (pn->isKind(PNK_SUPERCALL) && !emit1(JSOP_SETTHIS)) + return false; return true; } bool BytecodeEmitter::emitLogical(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); @@ -7842,16 +7868,17 @@ BytecodeEmitter::emitTree(ParseNode* pn) return false; } break; case PNK_NEW: case PNK_TAGGED_TEMPLATE: case PNK_CALL: case PNK_GENEXP: + case PNK_SUPERCALL: ok = emitCallOrNew(pn); break; case PNK_LEXICALSCOPE: ok = emitLexicalScope(pn); break; case PNK_LETBLOCK:
--- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -409,16 +409,17 @@ ContainsHoistedDeclaration(ExclusiveCont case PNK_FOROF: case PNK_FORHEAD: case PNK_FRESHENBLOCK: case PNK_CLASSMETHOD: case PNK_CLASSMETHODLIST: case PNK_CLASSNAMES: case PNK_NEWTARGET: case PNK_POSHOLDER: + case PNK_SUPERCALL: MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on " "some parent node without recurring to test this node"); case PNK_LIMIT: // invalid sentinel value MOZ_CRASH("unexpected PNK_LIMIT in node"); } MOZ_CRASH("invalid node kind"); @@ -1574,17 +1575,18 @@ FoldAdd(ExclusiveContext* cx, ParseNode* return true; } static bool FoldCall(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, bool inGenexpLambda) { - MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_TAGGED_TEMPLATE)); + MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_SUPERCALL) || + node->isKind(PNK_TAGGED_TEMPLATE)); MOZ_ASSERT(node->isArity(PN_LIST)); // Don't fold a parenthesized callable component in an invocation, as this // might cause a different |this| value to be used, changing semantics: // // var prop = "global"; // var obj = { prop: "obj", f: function() { return this.prop; } }; // assertEq((true ? obj.f : null)(), "global"); @@ -1871,16 +1873,17 @@ Fold(ExclusiveContext* cx, ParseNode** p case PNK_ELEM: return FoldElement(cx, pnp, parser, inGenexpLambda); case PNK_ADD: return FoldAdd(cx, pnp, parser, inGenexpLambda); case PNK_CALL: + case PNK_SUPERCALL: case PNK_TAGGED_TEMPLATE: return FoldCall(cx, pn, parser, inGenexpLambda); case PNK_SWITCH: case PNK_CASE: case PNK_COLON: case PNK_ASSIGN: case PNK_ADDASSIGN:
--- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -371,16 +371,17 @@ class NameResolver case PNK_GENERATOR: case PNK_NUMBER: case PNK_BREAK: case PNK_CONTINUE: case PNK_DEBUGGER: case PNK_EXPORT_BATCH_SPEC: case PNK_FRESHENBLOCK: case PNK_OBJECT_PROPERTY_NAME: + case PNK_POSHOLDER: MOZ_ASSERT(cur->isArity(PN_NULLARY)); break; case PNK_TYPEOFNAME: MOZ_ASSERT(cur->isArity(PN_UNARY)); MOZ_ASSERT(cur->pn_kid->isKind(PNK_NAME)); MOZ_ASSERT(!cur->pn_kid->maybeExpr()); break; @@ -668,16 +669,17 @@ class NameResolver case PNK_SUB: case PNK_STAR: case PNK_DIV: case PNK_MOD: case PNK_POW: case PNK_COMMA: case PNK_NEW: case PNK_CALL: + case PNK_SUPERCALL: case PNK_GENEXP: case PNK_ARRAY: case PNK_STATEMENTLIST: case PNK_ARGSBODY: // Initializers for individual variables, and computed property names // within destructuring patterns, may contain unnamed functions. case PNK_VAR: case PNK_CONST: @@ -791,17 +793,16 @@ class NameResolver break; // Kinds that should be handled by parent node resolution. case PNK_IMPORT_SPEC: // by PNK_IMPORT_SPEC_LIST case PNK_EXPORT_SPEC: // by PNK_EXPORT_SPEC_LIST case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE case PNK_CLASSNAMES: // by PNK_CLASS - case PNK_POSHOLDER: // by PNK_NEWTARGET, PNK_DOT MOZ_CRASH("should have been handled by a parent node"); case PNK_LIMIT: // invalid sentinel value MOZ_CRASH("invalid node kind"); } nparents--; return true;
--- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -484,16 +484,17 @@ PushNodeChildren(ParseNode* pn, NodeStac case PNK_SUB: case PNK_STAR: case PNK_DIV: case PNK_MOD: case PNK_POW: case PNK_COMMA: case PNK_NEW: case PNK_CALL: + case PNK_SUPERCALL: case PNK_GENEXP: case PNK_ARRAY: case PNK_OBJECT: case PNK_TEMPLATE_STRING_LIST: case PNK_TAGGED_TEMPLATE: case PNK_CALLSITEOBJ: case PNK_VAR: case PNK_CONST:
--- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -170,16 +170,17 @@ class PackedScopeCoordinate F(SPREAD) \ F(MUTATEPROTO) \ F(CLASS) \ F(CLASSMETHOD) \ F(CLASSMETHODLIST) \ F(CLASSNAMES) \ F(NEWTARGET) \ F(POSHOLDER) \ + F(SUPERCALL) \ \ /* Unary operators. */ \ F(TYPEOFNAME) \ F(TYPEOFEXPR) \ F(VOID) \ F(NOT) \ F(BITNOT) \ \
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8643,19 +8643,41 @@ Parser<ParseHandler>::memberExpr(YieldHa nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end); if (!nextMember) return null(); } else if ((allowCallSyntax && tt == TOK_LP) || tt == TOK_TEMPLATE_HEAD || tt == TOK_NO_SUBS_TEMPLATE) { if (handler.isSuperBase(lhs, context)) { - // For now... - report(ParseError, false, null(), JSMSG_BAD_SUPER); - return null(); + if (!pc->sc->isFunctionBox() || !pc->sc->asFunctionBox()->isDerivedClassConstructor()) { + report(ParseError, false, null(), JSMSG_BAD_SUPERCALL); + return null(); + } + + if (tt != TOK_LP) { + report(ParseError, false, null(), JSMSG_BAD_SUPER); + return null(); + } + + nextMember = handler.newList(PNK_SUPERCALL, lhs, JSOP_SUPERCALL); + if (!nextMember) + return null(); + + // Despite the fact that it's impossible to have |super()| is a + // generator, we still inherity the yieldHandling of the + // memberExpression, per spec. Curious. + bool isSpread = false; + if (!argumentList(yieldHandling, nextMember, &isSpread)) + return null(); + + if (isSpread) + handler.setOp(nextMember, JSOP_SPREADSUPERCALL); + + return nextMember; } nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate(); if (!nextMember) return null(); JSOp op = JSOP_CALL; if (PropertyName* name = handler.maybeNameAnyParentheses(lhs)) {
--- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -369,16 +369,17 @@ class FunctionBox : public ObjectBox, pu bool needsCallObject() { // Note: this should be kept in sync with JSFunction::needsCallObject(). return bindings.hasAnyAliasedBindings() || hasExtensibleScope() || needsDeclEnvObject() || needsHomeObject() || + isDerivedClassConstructor() || isGenerator(); } }; class ModuleBox : public ObjectBox, public SharedContext { public: Bindings bindings;
--- a/js/src/jit-test/tests/auto-regress/bug759306.js +++ b/js/src/jit-test/tests/auto-regress/bug759306.js @@ -1,9 +1,9 @@ -// |jit-test| error:InternalError +// |jit-test| error:TypeError // Binary: cache/js-dbg-32-4ce3983a43f4-linux // Flags: // function assertEq(setter) { if (setter > 10) return {assertEq: 3.3};
--- a/js/src/jit-test/tests/basic/testProxyConstructors.js +++ b/js/src/jit-test/tests/basic/testProxyConstructors.js @@ -1,17 +1,19 @@ // |jit-test| error: ExitCleanly -assertEq((new (Proxy.createFunction({}, +var handler = { getPropertyDescriptor() { return undefined; } } + +assertEq((new (Proxy.createFunction(handler, function(){ this.x = 1 }, function(){ this.x = 2 }))).x, 2); // proxies can return the callee -var x = Proxy.createFunction({}, function (q) { return q; }); +var x = Proxy.createFunction(handler, function (q) { return q; }); assertEq(new x(x), x); try { - var x = (Proxy.createFunction({}, "".indexOf)); + var x = (Proxy.createFunction(handler, "".indexOf)); new x; throw "Should not be reached" } catch (e) { assertEq(String(e.message).indexOf('is not a constructor') === -1, false); } throw "ExitCleanly"
--- a/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js +++ b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js @@ -27,10 +27,10 @@ function measure(P, expected) { dbg.memory.trackingAllocationSites = true; // These are the sample counts that were correct when this test was last // updated; changes to SpiderMonkey may occasionally cause changes // here. Anything that is within a plausible range for the given sampling // probability is fine. measure(0.0, 0); measure(1.0, 100); -measure(0.1, 9); -measure(0.5, 51); +measure(0.1, 7); +measure(0.5, 44);
--- a/js/src/jit-test/tests/ion/bug825705.js +++ b/js/src/jit-test/tests/ion/bug825705.js @@ -1,23 +1,8 @@ // Test 1: When constructing x, we shouldn't take the prototype for this. // it will crash if that happens evalcx("\ var x = newGlobal().Object;\ function f() { return new x; }\ f();\ f();\ ", newGlobal()); - -// Test 2: Don't take the prototype of proxy's to create |this|, -// as this will throw... Not expected behaviour. -var O = new Proxy(function() {}, { - get: function() { - throw "get trap"; - } -}); - -function f() { - new O(); -} - -f(); -f();
--- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -975,17 +975,17 @@ InitFromBailout(JSContext* cx, HandleScr JitSpew(JitSpew_BaselineBailouts, " Resuming %s pc offset %d (op %s) (line %d) of %s:%" PRIuSIZE, resumeAfter ? "after" : "at", (int) pcOff, js_CodeName[op], PCToLineNumber(script, pc), script->filename(), script->lineno()); JitSpew(JitSpew_BaselineBailouts, " Bailout kind: %s", BailoutKindString(bailoutKind)); #endif bool pushedNewTarget = op == JSOP_NEW; - + // If this was the last inline frame, or we are bailing out to a catch or // finally block in this frame, then unpacking is almost done. if (!iter.moreFrames() || catchingException) { // Last frame, so PC for call to next frame is set to nullptr. *callPC = nullptr; // If the bailout was a resumeAfter, and the opcode is monitored, // then the bailed out state should be in a position to enter @@ -1872,16 +1872,18 @@ jit::FinishBailoutToBaseline(BaselineBai case Bailout_NonBooleanInput: case Bailout_NonObjectInput: case Bailout_NonStringInput: case Bailout_NonSymbolInput: case Bailout_NonSimdInt32x4Input: case Bailout_NonSimdFloat32x4Input: case Bailout_InitialState: case Bailout_Debugger: + case Bailout_UninitializedThis: + case Bailout_BadDerivedConstructorReturn: // Do nothing. break; // Invalid assumption based on baseline code. case Bailout_OverflowInvalidate: case Bailout_NonStringInputInvalidate: case Bailout_DoubleOutput: case Bailout_ObjectIdentityOrTypeGuard:
--- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1280,30 +1280,60 @@ BaselineCompiler::emit_JSOP_HOLE() bool BaselineCompiler::emit_JSOP_NULL() { frame.push(NullValue()); return true; } +typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame); +static const VMFunction ThrowUninitializedThisInfo = + FunctionInfo<ThrowUninitializedThisFn>(BaselineThrowUninitializedThis); + +bool +BaselineCompiler::emitCheckThis() +{ + frame.assertSyncedStack(); + + Label thisOK; + masm.branchTestMagic(Assembler::NotEqual, frame.addressOfThis(), &thisOK); + + prepareVMCall(); + + masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); + pushArg(R0.scratchReg()); + + if (!callVM(ThrowUninitializedThisInfo)) + return false; + + masm.bind(&thisOK); + return true; +} + bool BaselineCompiler::emit_JSOP_THIS() { if (function() && function()->isArrow()) { // Arrow functions store their (lexical) |this| value in an // extended slot. frame.syncStack(0); Register scratch = R0.scratchReg(); masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), scratch); masm.loadValue(Address(scratch, FunctionExtended::offsetOfArrowThisSlot()), R0); frame.push(R0); return true; } + if (script->isDerivedClassConstructor()) { + frame.syncStack(0); + if (!emitCheckThis()) + return false; + } + // Keep this value in R0 frame.pushThis(); // In strict mode code or self-hosted functions, |this| is left alone. if (script->strict() || (function() && function()->isSelfHostedBuiltin())) return true; Label skipIC; @@ -2868,17 +2898,17 @@ BaselineCompiler::emit_JSOP_UNINITIALIZE return true; } bool BaselineCompiler::emitCall() { MOZ_ASSERT(IsCallPC(pc)); - bool construct = JSOp(*pc) == JSOP_NEW; + bool construct = JSOp(*pc) == JSOP_NEW || JSOp(*pc) == JSOP_SUPERCALL; uint32_t argc = GET_ARGC(pc); frame.syncStack(0); masm.move32(Imm32(argc), R0.scratchReg()); // Call IC ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct, /* isSpread = */ false); @@ -2895,23 +2925,23 @@ bool BaselineCompiler::emitSpreadCall() { MOZ_ASSERT(IsCallPC(pc)); frame.syncStack(0); masm.move32(Imm32(1), R0.scratchReg()); // Call IC - ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ JSOp(*pc) == JSOP_SPREADNEW, + bool construct = JSOp(*pc) == JSOP_SPREADNEW || JSOp(*pc) == JSOP_SPREADSUPERCALL; + ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct, /* isSpread = */ true); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) return false; // Update FrameInfo. - bool construct = JSOp(*pc) == JSOP_SPREADNEW; frame.popn(3 + construct); frame.push(R0); return true; } bool BaselineCompiler::emit_JSOP_CALL() { @@ -2920,16 +2950,22 @@ BaselineCompiler::emit_JSOP_CALL() bool BaselineCompiler::emit_JSOP_NEW() { return emitCall(); } bool +BaselineCompiler::emit_JSOP_SUPERCALL() +{ + return emitCall(); +} + +bool BaselineCompiler::emit_JSOP_FUNCALL() { return emitCall(); } bool BaselineCompiler::emit_JSOP_FUNAPPLY() { @@ -2956,16 +2992,22 @@ BaselineCompiler::emit_JSOP_SPREADCALL() bool BaselineCompiler::emit_JSOP_SPREADNEW() { return emitSpreadCall(); } bool +BaselineCompiler::emit_JSOP_SPREADSUPERCALL() +{ + return emitSpreadCall(); +} + +bool BaselineCompiler::emit_JSOP_SPREADEVAL() { return emitSpreadCall(); } bool BaselineCompiler::emit_JSOP_STRICTSPREADEVAL() { @@ -3285,23 +3327,50 @@ BaselineCompiler::emit_JSOP_DEBUGGER() { masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); masm.jump(&return_); } masm.bind(&done); return true; } +typedef bool (*ThrowBadDerivedReturnFn)(JSContext*, HandleValue); +static const VMFunction ThrowBadDerivedReturnInfo = + FunctionInfo<ThrowBadDerivedReturnFn>(jit::ThrowBadDerivedReturn); + typedef bool (*DebugEpilogueFn)(JSContext*, BaselineFrame*, jsbytecode*); static const VMFunction DebugEpilogueInfo = FunctionInfo<DebugEpilogueFn>(jit::DebugEpilogueOnBaselineReturn); bool BaselineCompiler::emitReturn() { + if (script->isDerivedClassConstructor()) { + frame.syncStack(0); + + Label derivedDone, returnOK; + masm.branchTestObject(Assembler::Equal, JSReturnOperand, &derivedDone); + masm.branchTestUndefined(Assembler::Equal, JSReturnOperand, &returnOK); + + // This is going to smash JSReturnOperand, but we don't care, because it's + // also going to throw unconditionally. + prepareVMCall(); + pushArg(JSReturnOperand); + if (!callVM(ThrowBadDerivedReturnInfo)) + return false; + masm.assumeUnreachable("Should throw on bad derived constructor return"); + + masm.bind(&returnOK); + + if (!emitCheckThis()) + return false; + + masm.bind(&derivedDone); + } + if (compileDebugInstrumentation_) { // Move return value into the frame's rval slot. masm.storeValue(JSReturnOperand, frame.addressOfReturnValue()); masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags()); // Load BaselineFrame pointer in R0. frame.syncStack(0); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
--- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -196,17 +196,19 @@ namespace jit { _(JSOP_YIELD) \ _(JSOP_DEBUGAFTERYIELD) \ _(JSOP_FINALYIELDRVAL) \ _(JSOP_RESUME) \ _(JSOP_CALLEE) \ _(JSOP_SETRVAL) \ _(JSOP_RETRVAL) \ _(JSOP_RETURN) \ - _(JSOP_NEWTARGET) + _(JSOP_NEWTARGET) \ + _(JSOP_SUPERCALL) \ + _(JSOP_SPREADSUPERCALL) class BaselineCompiler : public BaselineCompilerSpecific { FixedList<Label> labels_; NonAssertingLabel return_; NonAssertingLabel postBarrierSlot_; // Native code offset right before the scope chain is initialized. @@ -300,16 +302,17 @@ class BaselineCompiler : public Baseline bool emitSpreadCall(); bool emitInitPropGetterSetter(); bool emitInitElemGetterSetter(); bool emitFormalArgAccess(uint32_t arg, bool get); bool emitUninitializedLexicalCheck(const ValueOperand& val); + bool emitCheckThis(); bool addPCMappingEntry(bool addIndexEntry); bool addYieldOffset(); void getScopeCoordinateObject(Register reg); Address getScopeCoordinateAddressFromObject(Register objReg, Register reg); Address getScopeCoordinateAddress(Register reg);
--- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -390,16 +390,18 @@ ICTypeMonitor_Fallback::addMonitorStubFo if (numOptimizedMonitorStubs_ >= MAX_OPTIMIZED_STUBS) { // TODO: if the TypeSet becomes unknown or has the AnyObject type, // replace stubs with a single stub to handle these. return true; } if (val.isPrimitive()) { + if (val.isMagic(JS_UNINITIALIZED_LEXICAL)) + return true; MOZ_ASSERT(!val.isMagic()); JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType(); // Check for existing TypeMonitor stub. ICTypeMonitor_PrimitiveSet* existingStub = nullptr; for (ICStubConstIterator iter(firstMonitorStub()); !iter.atEnd(); iter++) { if (iter->isTypeMonitor_PrimitiveSet()) { existingStub = iter->toTypeMonitor_PrimitiveSet(); @@ -498,30 +500,38 @@ ICTypeMonitor_Fallback::addMonitorStubFo } static bool DoTypeMonitorFallback(JSContext* cx, BaselineFrame* frame, ICTypeMonitor_Fallback* stub, HandleValue value, MutableHandleValue res) { // It's possible that we arrived here from bailing out of Ion, and that // Ion proved that the value is dead and optimized out. In such cases, do - // nothing. - if (value.isMagic(JS_OPTIMIZED_OUT)) { - res.set(value); - return true; + // nothing. However, it's also possible that we have an uninitialized this, + // in which case we should not look for other magic values. + if (stub->monitorsThis()) { + MOZ_ASSERT_IF(value.isMagic(), value.isMagic(JS_UNINITIALIZED_LEXICAL)); + } else { + if (value.isMagic(JS_OPTIMIZED_OUT)) { + res.set(value); + return true; + } } RootedScript script(cx, frame->script()); jsbytecode* pc = stub->icEntry()->pc(script); TypeFallbackICSpew(cx, stub, "TypeMonitor"); uint32_t argument; if (stub->monitorsThis()) { MOZ_ASSERT(pc == script->code()); - TypeScript::SetThis(cx, script, value); + if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) + TypeScript::SetThis(cx, script, TypeSet::UnknownType()); + else + TypeScript::SetThis(cx, script, value); } else if (stub->monitorsArgument(&argument)) { MOZ_ASSERT(pc == script->code()); TypeScript::SetArgument(cx, script, argument, value); } else { TypeScript::Monitor(cx, script, pc, value); } if (!stub->addMonitorStubForValue(cx, script, value)) @@ -5010,16 +5020,28 @@ TryAttachGlobalNameAccessorStub(JSContex // requires a Baseline stub) handles non-outerized this objects correctly. bool isScripted; if (IsCacheableGetPropCall(cx, global, current, shape, &isScripted, isTemporarilyUnoptimizable) && !isScripted) { ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); RootedFunction getter(cx, &shape->getterObject()->as<JSFunction>()); + // The CallNativeGlobal stub needs to generate 3 shape checks: + // + // 1. The global lexical scope shape check. + // 2. The global object shape check. + // 3. The holder shape check. + // + // 1 is done as the receiver check, as for GETNAME the global lexical scope is in the + // receiver position. 2 is done as a manual check that other GetProp stubs don't do. 3 is + // done as the holder check per normal. + // + // In the case the holder is the global object, check 2 is redundant but is not yet + // optimized away. JitSpew(JitSpew_BaselineIC, " Generating GetName(GlobalName/NativeGetter) stub"); if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNativeGlobal, current, globalLexical, getter)) { *attached = true; return true; } ICGetPropCallNativeCompiler compiler(cx, ICStub::GetProp_CallNativeGlobal, @@ -8617,16 +8639,18 @@ IsOptimizableCallStringSplit(Value calle return true; } static bool TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsbytecode* pc, JSOp op, uint32_t argc, Value* vp, bool constructing, bool isSpread, bool createSingleton, bool* handled) { + bool isSuper = op == JSOP_SUPERCALL || op == JSOP_SPREADSUPERCALL; + if (createSingleton || op == JSOP_EVAL || op == JSOP_STRICTEVAL) return true; if (stub->numOptimizedStubs() >= ICCall_Fallback::MAX_OPTIMIZED_STUBS) { // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. // But for now we just bail. return true; } @@ -8724,50 +8748,53 @@ TryAttachCallStub(JSContext* cx, ICCall_ } // Keep track of the function's |prototype| property in type // information, for use during Ion compilation. if (IsIonEnabled(cx)) EnsureTrackPropertyTypes(cx, fun, NameToId(cx->names().prototype)); // Remember the template object associated with any script being called - // as a constructor, for later use during Ion compilation. + // as a constructor, for later use during Ion compilation. This is unsound + // for super(), as a single callsite can have multiple possible prototype object + // created (via different newTargets) RootedObject templateObject(cx); - if (constructing) { + if (constructing && !isSuper) { // If we are calling a constructor for which the new script // properties analysis has not been performed yet, don't attach a // stub. After the analysis is performed, CreateThisForFunction may // start returning objects with a different type, and the Ion // compiler will get confused. // Only attach a stub if the function already has a prototype and // we can look it up without causing side effects. + RootedObject newTarget(cx, &vp[2 + argc].toObject()); RootedValue protov(cx); - if (!GetPropertyPure(cx, fun, NameToId(cx->names().prototype), protov.address())) { + if (!GetPropertyPure(cx, newTarget, NameToId(cx->names().prototype), protov.address())) { JitSpew(JitSpew_BaselineIC, " Can't purely lookup function prototype"); return true; } if (protov.isObject()) { TaggedProto proto(&protov.toObject()); - ObjectGroup* group = ObjectGroup::defaultNewGroup(cx, nullptr, proto, fun); + ObjectGroup* group = ObjectGroup::defaultNewGroup(cx, nullptr, proto, newTarget); if (!group) return false; if (group->newScript() && !group->newScript()->analyzed()) { JitSpew(JitSpew_BaselineIC, " Function newScript has not been analyzed"); // This is temporary until the analysis is perfomed, so // don't treat this as unoptimizable. *handled = true; return true; } } - JSObject* thisObject = CreateThisForFunction(cx, fun, TenuredObject); + JSObject* thisObject = CreateThisForFunction(cx, fun, newTarget, TenuredObject); if (!thisObject) return false; if (thisObject->is<PlainObject>() || thisObject->is<UnboxedPlainObject>()) templateObject = thisObject; } JitSpew(JitSpew_BaselineIC, @@ -8825,17 +8852,17 @@ TryAttachCallStub(JSContext* cx, ICCall_ return false; stub->addNewStub(newStub); *handled = true; return true; } RootedObject templateObject(cx); - if (MOZ_LIKELY(!isSpread)) { + if (MOZ_LIKELY(!isSpread && !isSuper)) { bool skipAttach = false; CallArgs args = CallArgsFromVp(argc, vp); if (!GetTemplateObjectForNative(cx, fun->native(), args, &templateObject, &skipAttach)) return false; if (skipAttach) { *handled = true; return true; } @@ -9511,17 +9538,18 @@ ICCall_Fallback::Compiler::postGenerateS return; CodeOffsetLabel offset(returnOffset_); offset.fixup(&masm); cx->compartment()->jitCompartment()->initBaselineCallReturnAddr(code->raw() + offset.offset(), isConstructing_); } -typedef bool (*CreateThisFn)(JSContext* cx, HandleObject callee, MutableHandleValue rval); +typedef bool (*CreateThisFn)(JSContext* cx, HandleObject callee, HandleObject newTarget, + MutableHandleValue rval); static const VMFunction CreateThisInfoBaseline = FunctionInfo<CreateThisFn>(CreateThis); bool ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) { MOZ_ASSERT(engine_ == Engine::Baseline); Label failure; @@ -9605,34 +9633,41 @@ ICCallScriptedCompiler::generateStubCode Label failureLeaveStubFrame; if (isConstructing_) { // Save argc before call. masm.push(argcReg); // Stack now looks like: // [..., Callee, ThisV, Arg0V, ..., ArgNV, NewTarget, StubFrameHeader, ArgC ] + masm.loadValue(Address(masm.getStackPointer(), STUB_FRAME_SIZE + sizeof(size_t)), R1); + masm.push(masm.extractObject(R1, ExtractTemp0)); + if (isSpread_) { masm.loadValue(Address(masm.getStackPointer(), - 3 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)), R1); + 3 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t) + + sizeof(JSObject*)), + R1); } else { BaseValueIndex calleeSlot2(masm.getStackPointer(), argcReg, - 2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)); + 2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t) + + sizeof(JSObject*)); masm.loadValue(calleeSlot2, R1); } masm.push(masm.extractObject(R1, ExtractTemp0)); if (!callVM(CreateThisInfoBaseline, masm)) return false; - // Return of CreateThis must be an object. + // Return of CreateThis must be an object or uninitialized. #ifdef DEBUG - Label createdThisIsObject; - masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisIsObject); - masm.assumeUnreachable("The return of CreateThis must be an object."); - masm.bind(&createdThisIsObject); + Label createdThisOK; + masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisOK); + masm.branchTestMagic(Assembler::Equal, JSReturnOperand, &createdThisOK); + masm.assumeUnreachable("The return of CreateThis must be an object or uninitialized."); + masm.bind(&createdThisOK); #endif // Reset the register set from here on in. MOZ_ASSERT(JSReturnOperand == R0); regs = availableGeneralRegs(0); regs.take(R0); regs.take(ArgumentsRectifierReg); argcReg = regs.takeAny();
--- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -106,17 +106,18 @@ EnterBaseline(JSContext* cx, EnterJitDat } MOZ_ASSERT(jit::IsBaselineEnabled(cx)); MOZ_ASSERT_IF(data.osrFrame, CheckFrame(data.osrFrame)); EnterJitCode enter = cx->runtime()->jitRuntime()->enterBaseline(); // Caller must construct |this| before invoking the Ion function. - MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject()); + MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject() || + data.maxArgv[0].isMagic(JS_UNINITIALIZED_LEXICAL)); data.result.setInt32(data.numActualArgs); { AssertCompartmentUnchanged pcc(cx); JitActivation activation(cx, data.calleeToken); if (data.osrFrame) data.osrFrame->setRunningInJit(); @@ -126,19 +127,22 @@ EnterBaseline(JSContext* cx, EnterJitDat data.scopeChain.get(), data.osrNumStackValues, data.result.address()); if (data.osrFrame) data.osrFrame->clearRunningInJit(); } MOZ_ASSERT(!cx->runtime()->jitRuntime()->hasIonReturnOverride()); - // Jit callers wrap primitive constructor return. - if (!data.result.isMagic() && data.constructing && data.result.isPrimitive()) + // Jit callers wrap primitive constructor return, except for derived + // class constructors, which are forced to do it themselves. + if (!data.result.isMagic() && data.constructing && data.result.isPrimitive()) { + MOZ_ASSERT(data.maxArgv[0].isObject()); data.result = data.maxArgv[0]; + } // Release temporary buffer used for OSR into Ion. cx->runtime()->getJitRuntime(cx)->freeOsrTempData(); MOZ_ASSERT_IF(data.result.isMagic(), data.result.isMagic(JS_ION_ERROR)); return data.result.isMagic() ? JitExec_Error : JitExec_Ok; } @@ -206,17 +210,17 @@ jit::EnterBaselineAtBranch(JSContext* cx else data.calleeToken = CalleeToToken(fp->script()); if (fp->isEvalFrame()) { if (!vals.reserve(2)) return JitExec_Aborted; vals.infallibleAppend(thisv); - + if (fp->isFunctionFrame()) vals.infallibleAppend(fp->newTarget()); else vals.infallibleAppend(NullValue()); data.maxArgc = 2; data.maxArgv = vals.begin(); } @@ -301,25 +305,16 @@ CanEnterBaselineJIT(JSContext* cx, Handl // script being a debuggee script, e.g., when performing // Debugger.Frame.prototype.eval. return BaselineCompile(cx, script, osrFrame && osrFrame->isDebuggee()); } MethodStatus jit::CanEnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, bool newType) { - // If constructing, allocate a new |this| object. - if (fp->isConstructing() && fp->functionThis().isPrimitive()) { - RootedObject callee(cx, &fp->callee()); - RootedObject obj(cx, CreateThisForFunction(cx, callee, newType ? SingletonObject : GenericObject)); - if (!obj) - return Method_Skipped; - fp->functionThis().setObject(*obj); - } - if (!CheckFrame(fp)) return Method_CantCompile; // This check is needed in the following corner case. Consider a function h, // // function h(x) { // h(false); // if (!x) @@ -351,18 +346,23 @@ jit::CanEnterBaselineMethod(JSContext* c if (state.isInvoke()) { InvokeState& invoke = *state.asInvoke(); if (invoke.args().length() > BASELINE_MAX_ARGS_LENGTH) { JitSpew(JitSpew_BaselineAbort, "Too many arguments (%u)", invoke.args().length()); return Method_CantCompile; } - if (!state.maybeCreateThisForConstructor(cx)) - return Method_Skipped; + if (!state.maybeCreateThisForConstructor(cx)) { + if (cx->isThrowingOutOfMemory()) { + cx->recoverFromOutOfMemory(); + return Method_Skipped; + } + return Method_Error; + } } else { MOZ_ASSERT(state.isExecute()); ExecuteType type = state.asExecute()->type(); if (type == EXECUTE_DEBUG) { JitSpew(JitSpew_BaselineAbort, "debugger frame"); return Method_CantCompile; } }
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -4923,53 +4923,67 @@ CodeGenerator::visitInitPropGetterSetter pushArg(value); pushArg(ImmGCPtr(lir->mir()->name())); pushArg(obj); pushArg(ImmPtr(lir->mir()->resumePoint()->pc())); callVM(InitPropGetterSetterInfo, lir); } -typedef bool (*CreateThisFn)(JSContext* cx, HandleObject callee, MutableHandleValue rval); +typedef bool (*CreateThisFn)(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHandleValue rval); static const VMFunction CreateThisInfoCodeGen = FunctionInfo<CreateThisFn>(CreateThis); void CodeGenerator::visitCreateThis(LCreateThis* lir) { const LAllocation* callee = lir->getCallee(); + const LAllocation* newTarget = lir->getNewTarget(); + + if (newTarget->isConstant()) + pushArg(ImmGCPtr(&newTarget->toConstant()->toObject())); + else + pushArg(ToRegister(newTarget)); if (callee->isConstant()) pushArg(ImmGCPtr(&callee->toConstant()->toObject())); else pushArg(ToRegister(callee)); callVM(CreateThisInfoCodeGen, lir); } static JSObject* -CreateThisForFunctionWithProtoWrapper(JSContext* cx, js::HandleObject callee, HandleObject proto) -{ - return CreateThisForFunctionWithProto(cx, callee, proto); -} - -typedef JSObject* (*CreateThisWithProtoFn)(JSContext* cx, HandleObject callee, HandleObject proto); +CreateThisForFunctionWithProtoWrapper(JSContext* cx, HandleObject callee, HandleObject newTarget, + HandleObject proto) +{ + return CreateThisForFunctionWithProto(cx, callee, newTarget, proto); +} + +typedef JSObject* (*CreateThisWithProtoFn)(JSContext* cx, HandleObject callee, + HandleObject newTarget, HandleObject proto); static const VMFunction CreateThisWithProtoInfo = FunctionInfo<CreateThisWithProtoFn>(CreateThisForFunctionWithProtoWrapper); void CodeGenerator::visitCreateThisWithProto(LCreateThisWithProto* lir) { const LAllocation* callee = lir->getCallee(); + const LAllocation* newTarget = lir->getNewTarget(); const LAllocation* proto = lir->getPrototype(); if (proto->isConstant()) pushArg(ImmGCPtr(&proto->toConstant()->toObject())); else pushArg(ToRegister(proto)); + if (newTarget->isConstant()) + pushArg(ImmGCPtr(&newTarget->toConstant()->toObject())); + else + pushArg(ToRegister(newTarget)); + if (callee->isConstant()) pushArg(ImmGCPtr(&callee->toConstant()->toObject())); else pushArg(ToRegister(callee)); callVM(CreateThisWithProtoInfo, lir); } @@ -10320,16 +10334,29 @@ CodeGenerator::visitNewTarget(LNewTarget masm.bind(&actualArgsSufficient); BaseValueIndex newTarget(masm.getStackPointer(), argvLen, frameSize() + JitFrameLayout::offsetOfActualArgs()); masm.loadValue(newTarget, output); masm.bind(&done); } +void +CodeGenerator::visitCheckReturn(LCheckReturn* ins) +{ + ValueOperand returnValue = ToValue(ins, LCheckReturn::ReturnValue); + ValueOperand thisValue = ToValue(ins, LCheckReturn::ThisValue); + Label bail, noChecks; + masm.branchTestObject(Assembler::Equal, returnValue, &noChecks); + masm.branchTestUndefined(Assembler::NotEqual, returnValue, &bail); + masm.branchTestMagicValue(Assembler::Equal, thisValue, JS_UNINITIALIZED_LEXICAL, &bail); + bailoutFrom(&bail, ins->snapshot()); + masm.bind(&noChecks); +} + // Out-of-line math_random_no_outparam call for LRandom. class OutOfLineRandom : public OutOfLineCodeBase<CodeGenerator> { LRandom* lir_; public: explicit OutOfLineRandom(LRandom* lir) : lir_(lir)
--- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -330,16 +330,17 @@ class CodeGenerator : public CodeGenerat void visitAsmJSReturn(LAsmJSReturn* ret); void visitAsmJSVoidReturn(LAsmJSVoidReturn* ret); void visitLexicalCheck(LLexicalCheck* ins); void visitThrowUninitializedLexical(LThrowUninitializedLexical* ins); void visitGlobalNameConflictsCheck(LGlobalNameConflictsCheck* ins); void visitDebugger(LDebugger* ins); void visitNewTarget(LNewTarget* ins); void visitArrowNewTarget(LArrowNewTarget* ins); + void visitCheckReturn(LCheckReturn* ins); void visitCheckOverRecursed(LCheckOverRecursed* lir); void visitCheckOverRecursedFailure(CheckOverRecursedFailure* ool); void visitInterruptCheckImplicit(LInterruptCheckImplicit* ins); void visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit* ins); void visitUnboxFloatingPoint(LUnboxFloatingPoint* lir);
--- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2542,18 +2542,21 @@ jit::CanEnter(JSContext* cx, RunState& s if (TooManyFormalArguments(invoke.args().callee().as<JSFunction>().nargs())) { TrackAndSpewIonAbort(cx, script, "too many args"); ForbidCompilation(cx, script); return Method_CantCompile; } if (!state.maybeCreateThisForConstructor(cx)) { - cx->recoverFromOutOfMemory(); - return Method_Skipped; + if (cx->isThrowingOutOfMemory()) { + cx->recoverFromOutOfMemory(); + return Method_Skipped; + } + return Method_Error; } } // If --ion-eager is used, compile with Baseline first, so that we // can directly enter IonMonkey. if (js_JitOptions.eagerCompilation && !rscript->hasBaselineScript()) { MethodStatus status = CanEnterBaselineMethod(cx, state); if (status != Method_Compiled) @@ -2654,32 +2657,37 @@ EnterIon(JSContext* cx, EnterJitData& da { JS_CHECK_RECURSION(cx, return JitExec_Aborted); MOZ_ASSERT(jit::IsIonEnabled(cx)); MOZ_ASSERT(!data.osrFrame); EnterJitCode enter = cx->runtime()->jitRuntime()->enterIon(); // Caller must construct |this| before invoking the Ion function. - MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject()); + MOZ_ASSERT_IF(data.constructing, + data.maxArgv[0].isObject() || data.maxArgv[0].isMagic(JS_UNINITIALIZED_LEXICAL)); data.result.setInt32(data.numActualArgs); { AssertCompartmentUnchanged pcc(cx); JitActivation activation(cx, data.calleeToken); CALL_GENERATED_CODE(enter, data.jitcode, data.maxArgc, data.maxArgv, /* osrFrame = */nullptr, data.calleeToken, /* scopeChain = */ nullptr, 0, data.result.address()); } MOZ_ASSERT(!cx->runtime()->jitRuntime()->hasIonReturnOverride()); - // Jit callers wrap primitive constructor return. - if (!data.result.isMagic() && data.constructing && data.result.isPrimitive()) + // Jit callers wrap primitive constructor return, except for derived class constructors. + if (!data.result.isMagic() && data.constructing && + data.result.isPrimitive()) + { + MOZ_ASSERT(data.maxArgv[0].isObject()); data.result = data.maxArgv[0]; + } // Release temporary buffer used for OSR into Ion. cx->runtime()->getJitRuntime(cx)->freeOsrTempData(); MOZ_ASSERT_IF(data.result.isMagic(), data.result.isMagic(JS_ION_ERROR)); return data.result.isMagic() ? JitExec_Error : JitExec_Ok; }
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1862,17 +1862,18 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_FUNCALL: return jsop_funcall(GET_ARGC(pc)); case JSOP_FUNAPPLY: return jsop_funapply(GET_ARGC(pc)); case JSOP_CALL: case JSOP_NEW: - return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW); + case JSOP_SUPERCALL: + return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL); case JSOP_EVAL: case JSOP_STRICTEVAL: return jsop_eval(GET_ARGC(pc)); case JSOP_INT8: return pushConstant(Int32Value(GET_INT8(pc))); @@ -4464,16 +4465,25 @@ IonBuilder::processReturn(JSOp op) def = current->getSlot(info().returnValueSlot()); break; default: def = nullptr; MOZ_CRASH("unknown return op"); } + if (script()->isDerivedClassConstructor() && + def->type() != MIRType_Object) + { + MOZ_ASSERT(info().funMaybeLazy() && info().funMaybeLazy()->isClassConstructor()); + MCheckReturn* checkRet = MCheckReturn::New(alloc(), def, current->getSlot(info().thisSlot())); + current->add(checkRet); + def = checkRet; + } + MReturn* ret = MReturn::New(alloc(), def); current->end(ret); if (!graph().addReturn(current)) return ControlStatus_Error; // Make sure no one tries to use this block now. setCurrent(nullptr); @@ -4939,17 +4949,17 @@ IonBuilder::inlineScriptedCall(CallInfo& uint32_t depth = current->stackDepth() + callInfo.numFormals(); if (depth > current->nslots()) { if (!current->increaseSlots(depth - current->nslots())) return false; } // Create new |this| on the caller-side for inlined constructors. if (callInfo.constructing()) { - MDefinition* thisDefn = createThis(target, callInfo.fun()); + MDefinition* thisDefn = createThis(target, callInfo.fun(), callInfo.getNewTarget()); if (!thisDefn) return false; callInfo.setThis(thisDefn); } // Capture formals in the outer resume point. callInfo.pushFormals(current); @@ -6033,45 +6043,45 @@ IonBuilder::createCallObject(MDefinition current->add(MStoreFixedSlot::New(alloc(), callObj, slot, param)); } } return callObj; } MDefinition* -IonBuilder::createThisScripted(MDefinition* callee) +IonBuilder::createThisScripted(MDefinition* callee, MDefinition* newTarget) { // Get callee.prototype. // // This instruction MUST be idempotent: since it does not correspond to an // explicit operation in the bytecode, we cannot use resumeAfter(). // Getters may not override |prototype| fetching, so this operation is indeed idempotent. // - First try an idempotent property cache. // - Upon failing idempotent property cache, we can't use a non-idempotent cache, // therefore we fallback to CallGetProperty // // Note: both CallGetProperty and GetPropertyCache can trigger a GC, // and thus invalidation. MInstruction* getProto; if (!invalidatedIdempotentCache()) { - MGetPropertyCache* getPropCache = MGetPropertyCache::New(alloc(), callee, names().prototype, + MGetPropertyCache* getPropCache = MGetPropertyCache::New(alloc(), newTarget, names().prototype, /* monitored = */ false); getPropCache->setIdempotent(); getProto = getPropCache; } else { - MCallGetProperty* callGetProp = MCallGetProperty::New(alloc(), callee, names().prototype, + MCallGetProperty* callGetProp = MCallGetProperty::New(alloc(), newTarget, names().prototype, /* callprop = */ false); callGetProp->setIdempotent(); getProto = callGetProp; } current->add(getProto); // Create this from prototype - MCreateThisWithProto* createThis = MCreateThisWithProto::New(alloc(), callee, getProto); + MCreateThisWithProto* createThis = MCreateThisWithProto::New(alloc(), callee, newTarget, getProto); current->add(createThis); return createThis; } JSObject* IonBuilder::getSingletonPrototype(JSFunction* target) { @@ -6126,16 +6136,18 @@ IonBuilder::createThisScriptedBaseline(M { // Try to inline |this| creation based on Baseline feedback. JSFunction* target = inspector->getSingleCallee(pc); if (!target || !target->hasScript()) return nullptr; JSObject* templateObject = inspector->getTemplateObject(pc); + if (!templateObject) + return nullptr; if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>()) return nullptr; Shape* shape = target->lookupPure(compartment->runtime()->names().prototype); if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) return nullptr; Value protov = target->getSlot(shape->slot()); @@ -6176,46 +6188,51 @@ IonBuilder::createThisScriptedBaseline(M templateObject->group()->initialHeap(constraints())); current->add(templateConst); current->add(createThis); return createThis; } MDefinition* -IonBuilder::createThis(JSFunction* target, MDefinition* callee) +IonBuilder::createThis(JSFunction* target, MDefinition* callee, MDefinition* newTarget) { // Create |this| for unknown target. if (!target) { if (MDefinition* createThis = createThisScriptedBaseline(callee)) return createThis; - MCreateThis* createThis = MCreateThis::New(alloc(), callee); + MCreateThis* createThis = MCreateThis::New(alloc(), callee, newTarget); current->add(createThis); return createThis; } // Native constructors build the new Object themselves. if (target->isNative()) { if (!target->isConstructor()) return nullptr; MConstant* magic = MConstant::New(alloc(), MagicValue(JS_IS_CONSTRUCTING)); current->add(magic); return magic; } + if (target->isDerivedClassConstructor()) { + MOZ_ASSERT(target->isClassConstructor()); + return constant(MagicValue(JS_UNINITIALIZED_LEXICAL)); + } + // Try baking in the prototype. if (MDefinition* createThis = createThisScriptedSingleton(target, callee)) return createThis; if (MDefinition* createThis = createThisScriptedBaseline(callee)) return createThis; - return createThisScripted(callee); + return createThisScripted(callee, newTarget); } bool IonBuilder::jsop_funcall(uint32_t argc) { // Stack for JSOP_FUNCALL: // 1: arg0 // ... @@ -6595,17 +6612,17 @@ IonBuilder::makeCallHelper(JSFunction* t for (int32_t i = callInfo.argc() - 1; i >= 0; i--) call->addArg(i + 1, callInfo.getArg(i)); // Now that we've told it about all the args, compute whether it's movable call->computeMovable(); // Inline the constructor on the caller-side. if (callInfo.constructing()) { - MDefinition* create = createThis(target, callInfo.fun()); + MDefinition* create = createThis(target, callInfo.fun(), callInfo.getNewTarget()); if (!create) { abort("Failure inlining constructor for call."); return nullptr; } callInfo.thisArg()->setImplicitlyUsedUnchecked(); callInfo.setThis(create); } @@ -12744,19 +12761,31 @@ IonBuilder::jsop_this() MLoadArrowThis* thisObj = MLoadArrowThis::New(alloc(), getCallee()); current->add(thisObj); current->push(thisObj); return true; } if (script()->strict() || info().funMaybeLazy()->isSelfHostedBuiltin()) { // No need to wrap primitive |this| in strict mode or self-hosted code. - current->pushSlot(info().thisSlot()); - return true; - } + MDefinition* thisVal = current->getSlot(info().thisSlot()); + if (script()->isDerivedClassConstructor()) { + MOZ_ASSERT(info().funMaybeLazy()->isClassConstructor()); + MOZ_ASSERT(script()->strict()); + + MLexicalCheck* checkThis = MLexicalCheck::New(alloc(), thisVal, Bailout_UninitializedThis); + current->add(checkThis); + thisVal = checkThis; + } + + current->push(thisVal); + return true; + } + + MOZ_ASSERT(!info().funMaybeLazy()->isClassConstructor()); if (thisTypes && (thisTypes->getKnownMIRType() == MIRType_Object || (thisTypes->empty() && baselineFrame_ && baselineFrame_->thisType.isSomeObject()))) { // This is safe, because if the entry type of |this| is an object, it // will necessarily be an object throughout the entire function. OSR // can introduce a phi, but this phi will be specialized. current->pushSlot(info().thisSlot());
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -375,20 +375,20 @@ class IonBuilder // Creates a MDefinition based on the given def improved with type as TypeSet. MDefinition* ensureDefiniteTypeSet(MDefinition* def, TemporaryTypeSet* types); void maybeMarkEmpty(MDefinition* ins); JSObject* getSingletonPrototype(JSFunction* target); - MDefinition* createThisScripted(MDefinition* callee); + MDefinition* createThisScripted(MDefinition* callee, MDefinition* newTarget); MDefinition* createThisScriptedSingleton(JSFunction* target, MDefinition* callee); MDefinition* createThisScriptedBaseline(MDefinition* callee); - MDefinition* createThis(JSFunction* target, MDefinition* callee); + MDefinition* createThis(JSFunction* target, MDefinition* callee, MDefinition* newTarget); MInstruction* createDeclEnvObject(MDefinition* callee, MDefinition* scopeObj); MInstruction* createCallObject(MDefinition* callee, MDefinition* scopeObj); MDefinition* walkScopeChain(unsigned hops); MInstruction* addConvertElementsToDoubles(MDefinition* elements); MDefinition* addMaybeCopyElementsForWrite(MDefinition* object, bool checkNative); MInstruction* addBoundsCheck(MDefinition* index, MDefinition* length);
--- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -106,19 +106,24 @@ enum BailoutKind Bailout_NonSimdFloat32x4Input, // For the initial snapshot when entering a function. Bailout_InitialState, // We hit a |debugger;| statement. Bailout_Debugger, + // |this| used uninitialized in a derived constructor + Bailout_UninitializedThis, + + // Derived constructors must return object or undefined + Bailout_BadDerivedConstructorReturn, + // END Normal bailouts - // Bailouts caused by invalid assumptions based on Baseline code. // Causes immediate invalidation. // Like Bailout_Overflow, but causes immediate invalidation. Bailout_OverflowInvalidate, // Like NonStringInput, but should cause immediate invalidation. // Used for jsop_iternext. @@ -146,17 +151,17 @@ enum BailoutKind // (We saw an object whose shape does not match that / any of those observed // by the baseline IC.) Bailout_ShapeGuard, // When we're trying to use an uninitialized lexical. Bailout_UninitializedLexical, // A bailout to baseline from Ion on exception to handle Debugger hooks. - Bailout_IonExceptionDebugMode, + Bailout_IonExceptionDebugMode }; inline const char* BailoutKindString(BailoutKind kind) { switch (kind) { // Normal bailouts. case Bailout_Inevitable: @@ -204,16 +209,20 @@ BailoutKindString(BailoutKind kind) case Bailout_NonSimdInt32x4Input: return "Bailout_NonSimdInt32x4Input"; case Bailout_NonSimdFloat32x4Input: return "Bailout_NonSimdFloat32x4Input"; case Bailout_InitialState: return "Bailout_InitialState"; case Bailout_Debugger: return "Bailout_Debugger"; + case Bailout_UninitializedThis: + return "Bailout_UninitializedThis"; + case Bailout_BadDerivedConstructorReturn: + return "Bailout_BadDerivedConstructorReturn"; // Bailouts caused by invalid assumptions. case Bailout_OverflowInvalidate: return "Bailout_OverflowInvalidate"; case Bailout_NonStringInputInvalidate: return "Bailout_NonStringInputInvalidate"; case Bailout_DoubleOutput: return "Bailout_DoubleOutput";
--- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -323,25 +323,27 @@ LIRGenerator::visitCreateThisWithTemplat assignSafepoint(lir, ins); } void LIRGenerator::visitCreateThisWithProto(MCreateThisWithProto* ins) { LCreateThisWithProto* lir = new(alloc()) LCreateThisWithProto(useRegisterOrConstantAtStart(ins->getCallee()), + useRegisterOrConstantAtStart(ins->getNewTarget()), useRegisterOrConstantAtStart(ins->getPrototype())); defineReturn(lir, ins); assignSafepoint(lir, ins); } void LIRGenerator::visitCreateThis(MCreateThis* ins) { - LCreateThis* lir = new(alloc()) LCreateThis(useRegisterOrConstantAtStart(ins->getCallee())); + LCreateThis* lir = new(alloc()) LCreateThis(useRegisterOrConstantAtStart(ins->getCallee()), + useRegisterOrConstantAtStart(ins->getNewTarget())); defineReturn(lir, ins); assignSafepoint(lir, ins); } void LIRGenerator::visitCreateArgumentsObject(MCreateArgumentsObject* ins) { // LAllocation callObj = useRegisterAtStart(ins->getCallObject()); @@ -4253,17 +4255,17 @@ LIRGenerator::visitSimdShift(MSimdShift* void LIRGenerator::visitLexicalCheck(MLexicalCheck* ins) { MDefinition* input = ins->input(); MOZ_ASSERT(input->type() == MIRType_Value); LLexicalCheck* lir = new(alloc()) LLexicalCheck(); useBox(lir, LLexicalCheck::Input, input); - assignSnapshot(lir, Bailout_UninitializedLexical); + assignSnapshot(lir, ins->bailoutKind()); add(lir, ins); redefine(ins, input); } void LIRGenerator::visitThrowUninitializedLexical(MThrowUninitializedLexical* ins) { LThrowUninitializedLexical* lir = new(alloc()) LThrowUninitializedLexical(); @@ -4288,16 +4290,32 @@ LIRGenerator::visitDebugger(MDebugger* i } void LIRGenerator::visitAtomicIsLockFree(MAtomicIsLockFree* ins) { define(new(alloc()) LAtomicIsLockFree(useRegister(ins->input())), ins); } +void +LIRGenerator::visitCheckReturn(MCheckReturn* ins) +{ + MDefinition* retVal = ins->returnValue(); + MDefinition* thisVal = ins->thisValue(); + MOZ_ASSERT(retVal->type() == MIRType_Value); + MOZ_ASSERT(thisVal->type() == MIRType_Value); + + LCheckReturn* lir = new(alloc()) LCheckReturn(); + useBoxAtStart(lir, LCheckReturn::ReturnValue, retVal); + useBoxAtStart(lir, LCheckReturn::ThisValue, thisVal); + assignSnapshot(lir, Bailout_BadDerivedConstructorReturn); + add(lir, ins); + redefine(ins, retVal); +} + static void SpewResumePoint(MBasicBlock* block, MInstruction* ins, MResumePoint* resumePoint) { Fprinter& out = JitSpewPrinter(); out.printf("Current resume point %p details:\n", (void*)resumePoint); out.printf(" frame count: %u\n", resumePoint->frameCount()); if (ins) {
--- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -301,14 +301,15 @@ class LIRGenerator : public LIRGenerator void visitUnknownValue(MUnknownValue* ins); void visitLexicalCheck(MLexicalCheck* ins); void visitThrowUninitializedLexical(MThrowUninitializedLexical* ins); void visitGlobalNameConflictsCheck(MGlobalNameConflictsCheck* ins); void visitDebugger(MDebugger* ins); void visitNewTarget(MNewTarget* ins); void visitArrowNewTarget(MArrowNewTarget* ins); void visitAtomicIsLockFree(MAtomicIsLockFree* ins); + void visitCheckReturn(MCheckReturn* ins); }; } // namespace jit } // namespace js #endif /* jit_Lowering_h */
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -4608,71 +4608,77 @@ class MCreateThisWithTemplate bool writeRecoverData(CompactBufferWriter& writer) const override; bool canRecoverOnBailout() const override; }; // Caller-side allocation of |this| for |new|: // Given a prototype operand, construct |this| for JSOP_NEW. class MCreateThisWithProto - : public MBinaryInstruction, - public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data -{ - MCreateThisWithProto(MDefinition* callee, MDefinition* prototype) - : MBinaryInstruction(callee, prototype) + : public MTernaryInstruction, + public Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, ObjectPolicy<2> >::Data +{ + MCreateThisWithProto(MDefinition* callee, MDefinition* newTarget, MDefinition* prototype) + : MTernaryInstruction(callee, newTarget, prototype) { setResultType(MIRType_Object); } public: INSTRUCTION_HEADER(CreateThisWithProto) static MCreateThisWithProto* New(TempAllocator& alloc, MDefinition* callee, - MDefinition* prototype) - { - return new(alloc) MCreateThisWithProto(callee, prototype); + MDefinition* newTarget, MDefinition* prototype) + { + return new(alloc) MCreateThisWithProto(callee, newTarget, prototype); } MDefinition* getCallee() const { return getOperand(0); } + MDefinition* getNewTarget() const { + return getOperand(1); + } MDefinition* getPrototype() const { - return getOperand(1); + return getOperand(2); } // Although creation of |this| modifies global state, it is safely repeatable. AliasSet getAliasSet() const override { return AliasSet::None(); } bool possiblyCalls() const override { return true; } }; // Caller-side allocation of |this| for |new|: // Constructs |this| when possible, else MagicValue(JS_IS_CONSTRUCTING). class MCreateThis - : public MUnaryInstruction, - public ObjectPolicy<0>::Data -{ - explicit MCreateThis(MDefinition* callee) - : MUnaryInstruction(callee) + : public MBinaryInstruction, + public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data +{ + explicit MCreateThis(MDefinition* callee, MDefinition* newTarget) + : MBinaryInstruction(callee, newTarget) { setResultType(MIRType_Value); } public: INSTRUCTION_HEADER(CreateThis) - static MCreateThis* New(TempAllocator& alloc, MDefinition* callee) - { - return new(alloc) MCreateThis(callee); + static MCreateThis* New(TempAllocator& alloc, MDefinition* callee, MDefinition* newTarget) + { + return new(alloc) MCreateThis(callee, newTarget); } MDefinition* getCallee() const { return getOperand(0); } + MDefinition* getNewTarget() const { + return getOperand(0); + } // Although creation of |this| modifies global state, it is safely repeatable. AliasSet getAliasSet() const override { return AliasSet::None(); } bool possiblyCalls() const override { return true; } @@ -7225,40 +7231,47 @@ class MAsmJSInterruptCheck }; // Checks if a value is JS_UNINITIALIZED_LEXICAL, bailout out if so, leaving // it to baseline to throw at the correct pc. class MLexicalCheck : public MUnaryInstruction, public BoxPolicy<0>::Data { - explicit MLexicalCheck(MDefinition* input) - : MUnaryInstruction(input) + BailoutKind kind_; + explicit MLexicalCheck(MDefinition* input, BailoutKind kind) + : MUnaryInstruction(input), + kind_(kind) { setResultType(MIRType_Value); setResultTypeSet(input->resultTypeSet()); setMovable(); setGuard(); } public: INSTRUCTION_HEADER(LexicalCheck) - static MLexicalCheck* New(TempAllocator& alloc, MDefinition* input) { - return new(alloc) MLexicalCheck(input); + static MLexicalCheck* New(TempAllocator& alloc, MDefinition* input, + BailoutKind kind = Bailout_UninitializedLexical) { + return new(alloc) MLexicalCheck(input, kind); } AliasSet getAliasSet() const override { return AliasSet::None(); } MDefinition* input() const { return getOperand(0); } + BailoutKind bailoutKind() const { + return kind_; + } + bool congruentTo(const MDefinition* ins) const override { return congruentIfOperandsEqual(ins); } }; // Unconditionally throw an uninitialized let error. class MThrowUninitializedLexical : public MNullaryInstruction { @@ -12931,16 +12944,43 @@ class MHasClass if (!ins->isHasClass()) return false; if (getClass() != ins->toHasClass()->getClass()) return false; return congruentIfOperandsEqual(ins); } }; +class MCheckReturn + : public MBinaryInstruction, + public BoxInputsPolicy::Data +{ + explicit MCheckReturn(MDefinition* retVal, MDefinition* thisVal) + : MBinaryInstruction(retVal, thisVal) + { + setGuard(); + setResultType(MIRType_Value); + setResultTypeSet(retVal->resultTypeSet()); + } + + public: + INSTRUCTION_HEADER(CheckReturn) + + static MCheckReturn* New(TempAllocator& alloc, MDefinition* retVal, MDefinition* thisVal) { + return new (alloc) MCheckReturn(retVal, thisVal); + } + + MDefinition* returnValue() const { + return getOperand(0); + } + MDefinition* thisValue() const { + return getOperand(1); + } +}; + // Increase the warm-up counter of the provided script upon execution and test if // the warm-up counter surpasses the threshold. Upon hit it will recompile the // outermost script (i.e. not the inlined script). class MRecompileCheck : public MNullaryInstruction { public: enum RecompileCheckType { RecompileCheck_OptimizationLevel,
--- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -272,17 +272,18 @@ namespace jit { _(AsmJSAtomicExchangeHeap) \ _(AsmJSAtomicBinopHeap) \ _(UnknownValue) \ _(LexicalCheck) \ _(ThrowUninitializedLexical) \ _(GlobalNameConflictsCheck) \ _(Debugger) \ _(NewTarget) \ - _(ArrowNewTarget) + _(ArrowNewTarget) \ + _(CheckReturn) // Forward declarations of MIR types. #define FORWARD_DECLARE(op) class M##op; MIR_OPCODE_LIST(FORWARD_DECLARE) #undef FORWARD_DECLARE class MDefinitionVisitor // interface i.e. pure abstract class {
--- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -1191,16 +1191,17 @@ FilterTypeSetPolicy::adjustInputs(TempAl _(IntPolicy<1>) \ _(Mix3Policy<ObjectPolicy<0>, StringPolicy<1>, BoxPolicy<2> >) \ _(Mix3Policy<ObjectPolicy<0>, BoxPolicy<1>, BoxPolicy<2> >) \ _(Mix3Policy<ObjectPolicy<0>, BoxPolicy<1>, ObjectPolicy<2> >) \ _(Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, BoxPolicy<2> >) \ _(Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2> >) \ _(Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, TruncateToInt32Policy<2> >) \ _(Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, IntPolicy<2> >) \ + _(Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, ObjectPolicy<2> >) \ _(Mix3Policy<StringPolicy<0>, IntPolicy<1>, IntPolicy<2>>) \ _(Mix3Policy<StringPolicy<0>, ObjectPolicy<1>, StringPolicy<2> >) \ _(Mix3Policy<StringPolicy<0>, StringPolicy<1>, StringPolicy<2> >) \ _(Mix4Policy<ObjectPolicy<0>, StringPolicy<1>, BoxPolicy<2>, BoxPolicy<3>>) \ _(Mix4Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2>, IntPolicy<3>>) \ _(Mix4Policy<ObjectPolicy<0>, IntPolicy<1>, TruncateToInt32Policy<2>, TruncateToInt32Policy<3> >) \ _(Mix4Policy<SimdScalarPolicy<0>, SimdScalarPolicy<1>, SimdScalarPolicy<2>, SimdScalarPolicy<3> >) \ _(MixPolicy<BoxPolicy<0>, ObjectPolicy<1> >) \
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -560,30 +560,34 @@ GetIntrinsicValue(JSContext* cx, HandleP // guaranteed to bail out after this function, but because of its AliasSet, // type info will not be reflowed. Manually monitor here. TypeScript::Monitor(cx, rval); return true; } bool -CreateThis(JSContext* cx, HandleObject callee, MutableHandleValue rval) +CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHandleValue rval) { rval.set(MagicValue(JS_IS_CONSTRUCTING)); if (callee->is<JSFunction>()) { JSFunction* fun = &callee->as<JSFunction>(); if (fun->isInterpreted() && fun->isConstructor()) { JSScript* script = fun->getOrCreateScript(cx); if (!script || !script->ensureHasTypes(cx)) return false; - JSObject* thisObj = CreateThisForFunction(cx, callee, GenericObject); - if (!thisObj) - return false; - rval.set(ObjectValue(*thisObj)); + if (script->isDerivedClassConstructor()) { + rval.set(MagicValue(JS_UNINITIALIZED_LEXICAL)); + } else { + JSObject* thisObj = CreateThisForFunction(cx, callee, newTarget, GenericObject); + if (!thisObj) + return false; + rval.set(ObjectValue(*thisObj)); + } } } return true; } void GetDynamicName(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp) @@ -1286,10 +1290,23 @@ bool ThrowUninitializedLexical(JSContext* cx) { ScriptFrameIter iter(cx); RootedScript script(cx, iter.script()); ReportUninitializedLexical(cx, script, iter.pc()); return false; } +bool +ThrowBadDerivedReturn(JSContext* cx, HandleValue v) +{ + ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, v, nullptr); + return false; +} + +bool +BaselineThrowUninitializedThis(JSContext* cx, BaselineFrame* frame) +{ + return ThrowUninitializedThis(cx, frame); +} + } // namespace jit } // namespace js
--- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -629,17 +629,17 @@ JSObject* NewCallObject(JSContext* cx, H JSObject* NewSingletonCallObject(JSContext* cx, HandleShape shape, uint32_t lexicalBegin); JSObject* NewStringObject(JSContext* cx, HandleString str); bool OperatorIn(JSContext* cx, HandleValue key, HandleObject obj, bool* out); bool OperatorInI(JSContext* cx, uint32_t index, HandleObject obj, bool* out); bool GetIntrinsicValue(JSContext* cx, HandlePropertyName name, MutableHandleValue rval); -bool CreateThis(JSContext* cx, HandleObject callee, MutableHandleValue rval); +bool CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHandleValue rval); void GetDynamicName(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp); void PostWriteBarrier(JSRuntime* rt, JSObject* obj); void PostGlobalWriteBarrier(JSRuntime* rt, JSObject* obj); uint32_t GetIndexFromString(JSString* str); @@ -729,13 +729,15 @@ IonMarkFunction(MIRType type) return JS_FUNC_TO_DATA_PTR(void*, MarkObjectGroupFromIon); default: MOZ_CRASH(); } } bool ObjectIsCallable(JSObject* obj); bool ThrowUninitializedLexical(JSContext* cx); +bool BaselineThrowUninitializedThis(JSContext* cx, BaselineFrame* frame); +bool ThrowBadDerivedReturn(JSContext* cx, HandleValue v); } // namespace jit } // namespace js #endif /* jit_VMFunctions_h */
--- a/js/src/jit/arm64/BaselineIC-arm64.cpp +++ b/js/src/jit/arm64/BaselineIC-arm64.cpp @@ -8,16 +8,17 @@ #include "jit/SharedICHelpers.h" #ifdef JS_SIMULATOR_ARM64 #include "jit/arm64/Assembler-arm64.h" #include "jit/arm64/BaselineCompiler-arm64.h" #include "jit/arm64/vixl/Debugger-vixl.h" #endif +#include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; namespace js { namespace jit { // ICCompare_Int32
--- a/js/src/jit/arm64/MoveEmitter-arm64.cpp +++ b/js/src/jit/arm64/MoveEmitter-arm64.cpp @@ -1,19 +1,30 @@ /* -*- 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 "jit/arm64/MoveEmitter-arm64.h" +#include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; +MemOperand +MoveEmitterARM64::toMemOperand(const MoveOperand& operand) const +{ + MOZ_ASSERT(operand.isMemory()); + ARMRegister base(operand.base(), 64); + if (operand.base() == masm.getStackPointer()) + return MemOperand(base, operand.disp() + (masm.framePushed() - pushedAtStart_)); + return MemOperand(base, operand.disp()); +} + void MoveEmitterARM64::emit(const MoveResolver& moves) { if (moves.numCycles()) { masm.reserveStack(sizeof(void*)); pushedAtCycle_ = masm.framePushed(); }
--- a/js/src/jit/arm64/MoveEmitter-arm64.h +++ b/js/src/jit/arm64/MoveEmitter-arm64.h @@ -30,23 +30,17 @@ class MoveEmitterARM64 int32_t pushedAtCycle_; int32_t pushedAtSpill_; void assertDone() { MOZ_ASSERT(!inCycle_); } MemOperand cycleSlot(); - MemOperand toMemOperand(const MoveOperand& operand) const { - MOZ_ASSERT(operand.isMemory()); - ARMRegister base(operand.base(), 64); - if (operand.base() == masm.getStackPointer()) - return MemOperand(base, operand.disp() + (masm.framePushed() - pushedAtStart_)); - return MemOperand(base, operand.disp()); - } + MemOperand toMemOperand(const MoveOperand& operand) const; ARMRegister toARMReg32(const MoveOperand& operand) const { MOZ_ASSERT(operand.isGeneralReg()); return ARMRegister(operand.reg(), 32); } ARMRegister toARMReg64(const MoveOperand& operand) const { if (operand.isGeneralReg()) return ARMRegister(operand.reg(), 64); else
--- a/js/src/jit/arm64/vixl/Instructions-vixl.cpp +++ b/js/src/jit/arm64/vixl/Instructions-vixl.cpp @@ -251,17 +251,17 @@ const Instruction* Instruction::ImmPCOff VIXL_ASSERT(BranchType() != UnknownBranchType); // Relative branch offsets are instruction-size-aligned. offset = ImmBranch() << kInstructionSizeLog2; } return base + offset; } -inline int Instruction::ImmBranch() const { +int Instruction::ImmBranch() const { switch (BranchType()) { case CondBranchType: return ImmCondBranch(); case UncondBranchType: return ImmUncondBranch(); case CompareBranchType: return ImmCmpBranch(); case TestBranchType: return ImmTestBranch(); default: VIXL_UNREACHABLE(); } return 0;
--- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -1295,53 +1295,62 @@ class LToIdV : public LInstructionHelper const LDefinition* tempFloat() { return getTemp(0); } }; // Allocate an object for |new| on the caller-side, // when there is no templateObject or prototype known -class LCreateThis : public LCallInstructionHelper<BOX_PIECES, 1, 0> +class LCreateThis : public LCallInstructionHelper<BOX_PIECES, 2, 0> { public: LIR_HEADER(CreateThis) - explicit LCreateThis(const LAllocation& callee) + LCreateThis(const LAllocation& callee, const LAllocation& newTarget) { setOperand(0, callee); + setOperand(1, newTarget); } const LAllocation* getCallee() { return getOperand(0); } + const LAllocation* getNewTarget() { + return getOperand(1); + } MCreateThis* mir() const { return mir_->toCreateThis(); } }; // Allocate an object for |new| on the caller-side, // when the prototype is known. -class LCreateThisWithProto : public LCallInstructionHelper<1, 2, 0> +class LCreateThisWithProto : public LCallInstructionHelper<1, 3, 0> { public: LIR_HEADER(CreateThisWithProto) - LCreateThisWithProto(const LAllocation& callee, const LAllocation& prototype) + LCreateThisWithProto(const LAllocation& callee, const LAllocation& newTarget, + const LAllocation& prototype) { setOperand(0, callee); - setOperand(1, prototype); + setOperand(1, newTarget); + setOperand(2, prototype); } const LAllocation* getCallee() { return getOperand(0); } + const LAllocation* getNewTarget() { + return getOperand(1); + } const LAllocation* getPrototype() { - return getOperand(1); + return getOperand(2); } MCreateThis* mir() const { return mir_->toCreateThis(); } }; // Allocate an object for |new| on the caller-side. @@ -7242,12 +7251,21 @@ class LRandom : public LInstructionHelpe } #endif MRandom* mir() const { return mir_->toRandom(); } }; +class LCheckReturn : public LCallInstructionHelper<BOX_PIECES, 2 * BOX_PIECES, 0> +{ + public: + static const size_t ReturnValue = 0; + static const size_t ThisValue = BOX_PIECES; + + LIR_HEADER(CheckReturn) +}; + } // namespace jit } // namespace js #endif /* jit_shared_LIR_shared_h */
--- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -363,11 +363,12 @@ _(AssertRangeV) \ _(AssertResultV) \ _(AssertResultT) \ _(LexicalCheck) \ _(ThrowUninitializedLexical) \ _(GlobalNameConflictsCheck) \ _(Debugger) \ _(NewTarget) \ - _(ArrowNewTarget) + _(ArrowNewTarget) \ + _(CheckReturn) #endif /* jit_shared_LOpcodes_shared_h */
--- a/js/src/js.msg +++ b/js/src/js.msg @@ -101,16 +101,18 @@ MSG_DEF(JSMSG_NEXT_RETURNED_PRIMITIVE, 0 MSG_DEF(JSMSG_CANT_SET_PROTO, 0, JSEXN_TYPEERR, "can't set prototype of this object") MSG_DEF(JSMSG_CANT_SET_PROTO_OF, 1, JSEXN_TYPEERR, "can't set prototype of {0}") MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE, 0, JSEXN_TYPEERR, "can't set prototype: it would cause a prototype chain cycle") MSG_DEF(JSMSG_INVALID_ARG_TYPE, 3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}") MSG_DEF(JSMSG_TERMINATED, 1, JSEXN_ERR, "Script terminated by timeout at:\n{0}") MSG_DEF(JSMSG_PROTO_NOT_OBJORNULL, 1, JSEXN_TYPEERR, "{0}.prototype is not an object or null") MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with |new|") MSG_DEF(JSMSG_DISABLED_DERIVED_CLASS, 1, JSEXN_INTERNALERR, "{0} temporarily disallowed in derived class constructors") +MSG_DEF(JSMSG_UNINITIALIZED_THIS, 1, JSEXN_REFERENCEERR, "|this| used uninitialized in {0} class constructor") +MSG_DEF(JSMSG_BAD_DERIVED_RETURN, 1, JSEXN_TYPEERR, "derived class constructor returned invalid value {0}") // JSON MSG_DEF(JSMSG_JSON_BAD_PARSE, 3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data") MSG_DEF(JSMSG_JSON_CYCLIC_VALUE, 1, JSEXN_TYPEERR, "cyclic {0} value") // Runtime errors MSG_DEF(JSMSG_BAD_INSTANCEOF_RHS, 1, JSEXN_TYPEERR, "invalid 'instanceof' operand {0}") MSG_DEF(JSMSG_BAD_LEFTSIDE_OF_ASS, 0, JSEXN_REFERENCEERR, "invalid assignment left-hand side") @@ -207,16 +209,17 @@ MSG_DEF(JSMSG_BAD_METHOD_DEF, 0 MSG_DEF(JSMSG_BAD_OCTAL, 1, JSEXN_SYNTAXERR, "{0} is not a legal ECMA-262 octal constant") MSG_DEF(JSMSG_BAD_OPERAND, 1, JSEXN_SYNTAXERR, "invalid {0} operand") MSG_DEF(JSMSG_BAD_PROP_ID, 0, JSEXN_SYNTAXERR, "invalid property id") MSG_DEF(JSMSG_BAD_RETURN_OR_YIELD, 1, JSEXN_SYNTAXERR, "{0} not in function") MSG_DEF(JSMSG_BAD_STRICT_ASSIGN, 1, JSEXN_SYNTAXERR, "can't assign to {0} in strict mode") MSG_DEF(JSMSG_BAD_SWITCH, 0, JSEXN_SYNTAXERR, "invalid switch statement") MSG_DEF(JSMSG_BAD_SUPER, 0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'") MSG_DEF(JSMSG_BAD_SUPERPROP, 1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods") +MSG_DEF(JSMSG_BAD_SUPERCALL, 0, JSEXN_SYNTAXERR, "super() is only valid in derived class constructors") MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension") MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list") MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression") MSG_DEF(JSMSG_CATCH_AFTER_GENERAL, 0, JSEXN_SYNTAXERR, "catch after unconditional catch") MSG_DEF(JSMSG_CATCH_IDENTIFIER, 0, JSEXN_SYNTAXERR, "missing identifier in catch") MSG_DEF(JSMSG_CATCH_OR_FINALLY, 0, JSEXN_SYNTAXERR, "missing catch or finally after try") MSG_DEF(JSMSG_CATCH_WITHOUT_TRY, 0, JSEXN_SYNTAXERR, "catch without try") MSG_DEF(JSMSG_COLON_AFTER_CASE, 0, JSEXN_SYNTAXERR, "missing : after case label") @@ -502,15 +505,16 @@ MSG_DEF(JSMSG_CANT_DEFINE_WINDOW_ELEMENT MSG_DEF(JSMSG_CANT_DELETE_WINDOW_ELEMENT, 0, JSEXN_TYPEERR, "can't delete elements from a Window object") MSG_DEF(JSMSG_CANT_DELETE_WINDOW_NAMED_PROPERTY, 1, JSEXN_TYPEERR, "can't delete property {0} from window's named properties object") MSG_DEF(JSMSG_CANT_PREVENT_EXTENSIONS, 0, JSEXN_TYPEERR, "can't prevent extensions on this proxy object") MSG_DEF(JSMSG_NO_NAMED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have a named property setter for '{1}'") MSG_DEF(JSMSG_NO_INDEXED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have an indexed property setter for '{1}'") // Super MSG_DEF(JSMSG_CANT_DELETE_SUPER, 0, JSEXN_REFERENCEERR, "invalid delete involving 'super'") +MSG_DEF(JSMSG_REINIT_THIS, 0, JSEXN_REFERENCEERR, "super() called twice in derived class constructor") // Modules MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT, 0, JSEXN_SYNTAXERR, "default export cannot be provided by export *") MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "indirect export not found") MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export") MSG_DEF(JSMSG_MISSING_IMPORT, 0, JSEXN_SYNTAXERR, "import not found") MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 0, JSEXN_SYNTAXERR, "ambiguous import")
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6385,16 +6385,22 @@ JS_DecodeInterpretedFunction(JSContext* { XDRDecoder decoder(cx, data, length); RootedFunction funobj(cx); if (!decoder.codeFunction(&funobj)) return nullptr; return funobj; } +JS_PUBLIC_API(bool) +JS_SetImmutablePrototype(JSContext *cx, JS::HandleObject obj, bool *succeeded) +{ + return SetImmutablePrototype(cx, obj, succeeded); +} + JS_PUBLIC_API(void) JS::SetAsmJSCacheOps(JSRuntime* rt, const JS::AsmJSCacheOps* ops) { rt->asmJSCacheOps = *ops; } char* JSAutoByteString::encodeLatin1(ExclusiveContext* cx, JSString* str)
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2457,16 +2457,26 @@ JS_FreezeObject(JSContext* cx, JS::Handl * attempt, return false (with a pending exception set, depending upon the * nature of the error). If no error occurs, return true with |result| set * to indicate whether the attempt successfully set the [[Extensible]] property * to false. */ extern JS_PUBLIC_API(bool) JS_PreventExtensions(JSContext* cx, JS::HandleObject obj, JS::ObjectOpResult& result); +/* + * Attempt to make the [[Prototype]] of |obj| immutable, such that any attempt + * to modify it will fail. If an error occurs during the attempt, return false + * (with a pending exception set, depending upon the nature of the error). If + * no error occurs, return true with |*succeeded| set to indicate whether the + * attempt successfully made the [[Prototype]] immutable. + */ +extern JS_PUBLIC_API(bool) +JS_SetImmutablePrototype(JSContext* cx, JS::HandleObject obj, bool* succeeded); + extern JS_PUBLIC_API(JSObject*) JS_New(JSContext* cx, JS::HandleObject ctor, const JS::HandleValueArray& args); /*** Property descriptors ************************************************************************/ struct JSPropertyDescriptor : public JS::Traceable { JSObject* obj;
--- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -369,16 +369,22 @@ struct JSCompartment // Non-zero if any typed objects in this compartment might be neutered. int32_t neuteredTypedObjects; private: friend class js::AutoSetNewObjectMetadata; js::NewObjectMetadataState objectMetadataState; public: + // Recompute the probability with which this compartment should record + // profiling data (stack traces, allocations log, etc.) about each + // allocation. We consult the probabilities requested by the Debugger + // instances observing us, if any. + void chooseAllocationSamplingProbability() { savedStacks_.chooseSamplingProbability(this); } + bool hasObjectPendingMetadata() const { return objectMetadataState.is<js::PendingMetadata>(); } void setObjectPendingMetadata(JSContext* cx, JSObject* obj) { MOZ_ASSERT(objectMetadataState.is<js::DelayMetadata>()); objectMetadataState = js::NewObjectMetadataState(js::PendingMetadata(obj)); } void setObjectPendingMetadata(js::ExclusiveContext* ecx, JSObject* obj) {
--- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -151,16 +151,17 @@ class JSFunction : public js::NativeObje if (isNative()) return false; // Note: this should be kept in sync with FunctionBox::needsCallObject(). return nonLazyScript()->hasAnyAliasedBindings() || nonLazyScript()->funHasExtensibleScope() || nonLazyScript()->funNeedsDeclEnvObject() || nonLazyScript()->needsHomeObject() || + nonLazyScript()->isDerivedClassConstructor() || isGenerator(); } size_t nargs() const { return nargs_; } uint16_t flags() const { @@ -511,16 +512,26 @@ class JSFunction : public js::NativeObje return u.n.jitinfo; } void setJitInfo(const JSJitInfo* data) { MOZ_ASSERT(isNative()); u.n.jitinfo = data; } + bool isDerivedClassConstructor() { + bool derived; + if (isInterpretedLazy()) + derived = lazyScript()->isDerivedClassConstructor(); + else + derived = nonLazyScript()->isDerivedClassConstructor(); + MOZ_ASSERT_IF(derived, isClassConstructor()); + return derived; + } + static unsigned offsetOfNativeOrScript() { static_assert(offsetof(U, n.native) == offsetof(U, i.s.script_), "native and script pointers must be in the same spot " "for offsetOfNativeOrScript() have any sense"); static_assert(offsetof(U, n.native) == offsetof(U, nativeOrScript), "U::nativeOrScript must be at the same offset as " "native");
--- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -729,25 +729,21 @@ js::math_pow_handle(JSContext* cx, Handl bool js::math_pow(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return math_pow_handle(cx, args.get(0), args.get(1), args.rval()); } -static uint64_t -random_generateSeed() +void +js::random_generateSeed(uint64_t* seedBuffer, size_t length) { - union { - uint8_t u8[8]; - uint32_t u32[2]; - uint64_t u64; - } seed; - seed.u64 = 0; + if (length == 0) + return; #if defined(XP_WIN) /* * Temporary diagnostic for bug 1167248: Test whether the injected hooks * react differently to LoadLibraryW / LoadLibraryExW. */ HMODULE oldWay = LoadLibraryW(L"ADVAPI32.DLL"); HMODULE newWay = LoadLibraryExW(L"ADVAPI32.DLL", @@ -755,55 +751,82 @@ random_generateSeed() LOAD_LIBRARY_SEARCH_SYSTEM32); /* Fallback for older versions of Windows */ if (!newWay && GetLastError() == ERROR_INVALID_PARAMETER) newWay = LoadLibraryExW(L"ADVAPI32.DLL", nullptr, 0); if (oldWay && !newWay) MOZ_CRASH(); + union { + uint32_t u32[2]; + uint64_t u64; + } seed; + seed.u64 = 0; + errno_t error = rand_s(&seed.u32[0]); if (oldWay) FreeLibrary(oldWay); if (newWay) FreeLibrary(newWay); MOZ_ASSERT(error == 0, "rand_s() error?!"); error = rand_s(&seed.u32[1]); MOZ_ASSERT(error == 0, "rand_s() error?!"); + + seedBuffer[0] = seed.u64 ^= PRMJ_Now(); + for (size_t i = 1; i < length; i++) { + error = rand_s(&seed.u32[0]); + MOZ_ASSERT(error == 0, "rand_s() error?!"); + + error = rand_s(&seed.u32[1]); + MOZ_ASSERT(error == 0, "rand_s() error?!"); + + seedBuffer[i] = seed.u64 ^ PRMJ_Now(); + } + #elif defined(HAVE_ARC4RANDOM) - seed.u32[0] = arc4random(); - seed.u32[1] = arc4random(); + union { + uint32_t u32[2]; + uint64_t u64; + } seed; + seed.u64 = 0; + + for (size_t i = 0; i < length; i++) { + seed.u32[0] = arc4random(); + seed.u32[1] = arc4random(); + seedBuffer[i] = seed.u64 ^ PRMJ_Now(); + } + #elif defined(XP_UNIX) int fd = open("/dev/urandom", O_RDONLY); MOZ_ASSERT(fd >= 0, "Can't open /dev/urandom?!"); if (fd >= 0) { - ssize_t nread = read(fd, seed.u8, mozilla::ArrayLength(seed.u8)); - MOZ_ASSERT(nread == 8, "Can't read /dev/urandom?!"); + ssize_t size = length * sizeof(seedBuffer[0]); + ssize_t nread = read(fd, (char *) seedBuffer, size); + MOZ_ASSERT(nread == size, "Can't read /dev/urandom?!"); mozilla::unused << nread; close(fd); } #else # error "Platform needs to implement random_generateSeed()" #endif - - seed.u64 ^= PRMJ_Now(); - return seed.u64; } /* * Math.random() support, lifted from java.util.Random.java. */ void js::random_initState(uint64_t* rngState) { /* Our PRNG only uses 48 bits, so squeeze our entropy into those bits. */ - uint64_t seed = random_generateSeed(); + uint64_t seed; + random_generateSeed(&seed, 1); seed ^= (seed >> 16); *rngState = (seed ^ RNG_MULTIPLIER) & RNG_MASK; } uint64_t js::random_next(uint64_t* rngState, int bits) { MOZ_ASSERT((*rngState & 0xffff000000000000ULL) == 0, "Bad rngState");
--- a/js/src/jsmath.h +++ b/js/src/jsmath.h @@ -104,16 +104,23 @@ class MathCache /* * JS math functions. */ extern JSObject* InitMathClass(JSContext* cx, HandleObject obj); +/* + * Fill |seed[0]| through |seed[length-1]| with random bits, suitable for + * seeding a random number generator. + */ +extern void +random_generateSeed(uint64_t* seed, size_t length); + extern void random_initState(uint64_t* rngState); extern uint64_t random_next(uint64_t* rngState, int bits); static const double RNG_DSCALE = double(1LL << 53); static const int RNG_STATE_WIDTH = 48;
--- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -962,36 +962,36 @@ CreateThisForFunctionWithGroup(JSContext if (newKind == SingletonObject) { Rooted<TaggedProto> protoRoot(cx, group->proto()); return NewObjectWithGivenTaggedProto(cx, &PlainObject::class_, protoRoot, allocKind, newKind); } return NewObjectWithGroup<PlainObject>(cx, group, allocKind, newKind); } JSObject* -js::CreateThisForFunctionWithProto(JSContext* cx, HandleObject callee, HandleObject proto, - NewObjectKind newKind /* = GenericObject */) +js::CreateThisForFunctionWithProto(JSContext* cx, HandleObject callee, HandleObject newTarget, + HandleObject proto, NewObjectKind newKind /* = GenericObject */) { RootedObject res(cx); if (proto) { RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, nullptr, TaggedProto(proto), - &callee->as<JSFunction>())); + newTarget)); if (!group) return nullptr; if (group->newScript() && !group->newScript()->analyzed()) { bool regenerate; if (!group->newScript()->maybeAnalyze(cx, group, ®enerate)) return nullptr; if (regenerate) { // The script was analyzed successfully and may have changed // the new type table, so refetch the group. group = ObjectGroup::defaultNewGroup(cx, nullptr, TaggedProto(proto), - &callee->as<JSFunction>()); + newTarget); MOZ_ASSERT(group && group->newScript()); } } res = CreateThisForFunctionWithGroup(cx, group, newKind); } else { res = NewBuiltinClassInstance<PlainObject>(cx, newKind); } @@ -1002,25 +1002,26 @@ js::CreateThisForFunctionWithProto(JSCon return nullptr; TypeScript::SetThis(cx, script, TypeSet::ObjectType(res)); } return res; } JSObject* -js::CreateThisForFunction(JSContext* cx, HandleObject callee, NewObjectKind newKind) +js::CreateThisForFunction(JSContext* cx, HandleObject callee, HandleObject newTarget, + NewObjectKind newKind) { RootedValue protov(cx); - if (!GetProperty(cx, callee, callee, cx->names().prototype, &protov)) + if (!GetProperty(cx, newTarget, newTarget, cx->names().prototype, &protov)) return nullptr; RootedObject proto(cx); if (protov.isObject()) proto = &protov.toObject(); - JSObject* obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind); + JSObject* obj = CreateThisForFunctionWithProto(cx, callee, newTarget, proto, newKind); if (obj && newKind == SingletonObject) { RootedPlainObject nobj(cx, &obj->as<PlainObject>()); /* Reshape the singleton before passing it as the 'this' value. */ NativeObject::clear(cx, nobj); JSScript* calleeScript = callee->as<JSFunction>().nonLazyScript(); @@ -2389,17 +2390,17 @@ JSObject::reportNotExtensible(JSContext* } // Our immutable-prototype behavior is non-standard, and it's unclear whether // it's shippable. (Or at least it's unclear whether it's shippable with any // provided-by-default uses exposed to script.) If this bool is true, // immutable-prototype behavior is enforced; if it's false, behavior is not // enforced, and immutable-prototype bits stored on objects are completely // ignored. -static const bool ImmutablePrototypesEnabled = true; +static const bool ImmutablePrototypesEnabled = false; JS_FRIEND_API(bool) JS_ImmutablePrototypesEnabled() { return ImmutablePrototypesEnabled; } /*** ES6 standard internal methods ***************************************************************/ @@ -3335,16 +3336,17 @@ JSObject::dump() if (obj->isQualifiedVarObj()) fprintf(stderr, " varobj"); if (obj->isUnqualifiedVarObj()) fprintf(stderr, " unqualified_varobj"); if (obj->watched()) fprintf(stderr, " watched"); if (obj->isIteratedSingleton()) fprintf(stderr, " iterated_singleton"); if (obj->isNewGroupUnknown()) fprintf(stderr, " new_type_unknown"); if (obj->hasUncacheableProto()) fprintf(stderr, " has_uncacheable_proto"); if (obj->hadElementsAccess()) fprintf(stderr, " had_elements_access"); if (obj->wasNewScriptCleared()) fprintf(stderr, " new_script_cleared"); + if (!obj->hasLazyPrototype() && obj->nonLazyPrototypeIsImmutable()) fprintf(stderr, " immutable_prototype"); if (obj->isNative()) { NativeObject* nobj = &obj->as<NativeObject>(); if (nobj->inDictionaryMode()) fprintf(stderr, " inDictionaryMode"); if (nobj->hasShapeTable()) fprintf(stderr, " hasShapeTable"); }
--- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1157,22 +1157,23 @@ GetInitialHeap(NewObjectKind newKind, co if (clasp->finalize && !(clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE)) return gc::TenuredHeap; return gc::DefaultHeap; } // Specialized call for constructing |this| with a known function callee, // and a known prototype. extern JSObject* -CreateThisForFunctionWithProto(JSContext* cx, js::HandleObject callee, HandleObject proto, - NewObjectKind newKind = GenericObject); +CreateThisForFunctionWithProto(JSContext* cx, js::HandleObject callee, HandleObject newTarget, + HandleObject proto, NewObjectKind newKind = GenericObject); // Specialized call for constructing |this| with a known function callee. extern JSObject* -CreateThisForFunction(JSContext* cx, js::HandleObject callee, NewObjectKind newKind); +CreateThisForFunction(JSContext* cx, js::HandleObject callee, js::HandleObject newTarget, + NewObjectKind newKind); // Generic call for constructing |this|. extern JSObject* CreateThis(JSContext* cx, const js::Class* clasp, js::HandleObject callee); extern JSObject* CloneObject(JSContext* cx, HandleObject obj, Handle<js::TaggedProto> proto);
--- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -120,16 +120,17 @@ js::StackUses(JSScript* script, jsbyteco if (cs.nuses >= 0) return cs.nuses; MOZ_ASSERT(js_CodeSpec[op].nuses == -1); switch (op) { case JSOP_POPN: return GET_UINT16(pc); case JSOP_NEW: + case JSOP_SUPERCALL: return 2 + GET_ARGC(pc) + 1; default: /* stack: fun, this, [argc arguments] */ MOZ_ASSERT(op == JSOP_CALL || op == JSOP_EVAL || op == JSOP_STRICTEVAL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY); return 2 + GET_ARGC(pc); } }
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -104,100 +104,107 @@ using mozilla::UniquePtr; enum JSShellExitCode { EXITCODE_RUNTIME_ERROR = 3, EXITCODE_FILE_NOT_FOUND = 4, EXITCODE_OUT_OF_MEMORY = 5, EXITCODE_TIMEOUT = 6 }; -static size_t gStackChunkSize = 8192; +static const size_t gStackChunkSize = 8192; /* * Note: This limit should match the stack limit set by the browser in * js/xpconnect/src/XPCJSRuntime.cpp */ #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN)) -static size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024; +static const size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024; #else -static size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; +static const size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; #endif /* * Limit the timeout to 30 minutes to prevent an overflow on platfoms * that represent the time internally in microseconds using 32-bit int. */ -static double MAX_TIMEOUT_INTERVAL = 1800.0; -static double gTimeoutInterval = -1.0; -static Atomic<bool> gServiceInterrupt; -static JS::PersistentRootedValue gInterruptFunc; - -static bool gLastWarningEnabled = false; -static JS::PersistentRootedValue gLastWarning; - +static const double MAX_TIMEOUT_INTERVAL = 1800.0; + +// Per-runtime shell state. +struct ShellRuntime +{ + ShellRuntime(); + + bool isWorker; + double timeoutInterval; + Atomic<bool> serviceInterrupt; + JS::PersistentRootedValue interruptFunc; + bool lastWarningEnabled; + JS::PersistentRootedValue lastWarning; + + /* + * Watchdog thread state. + */ + PRLock* watchdogLock; + PRCondVar* watchdogWakeup; + PRThread* watchdogThread; + bool watchdogHasTimeout; + int64_t watchdogTimeout; + + PRCondVar* sleepWakeup; + + int exitCode; + bool quitting; + bool gotError; +}; + +// Shell state set once at startup. static bool enableCodeCoverage = false; static bool enableDisassemblyDumps = false; static bool offthreadCompilation = false; static bool enableBaseline = false; static bool enableIon = false; static bool enableAsmJS = false; static bool enableNativeRegExp = false; static bool enableUnboxedArrays = false; #ifdef JS_GC_ZEAL static char gZealStr[128]; #endif - static bool printTiming = false; static const char* jsCacheDir = nullptr; static const char* jsCacheAsmJSPath = nullptr; -static bool jsCachingEnabled = false; +static FILE* gErrFile = nullptr; +static FILE* gOutFile = nullptr; +static bool reportWarnings = true; +static bool compileOnly = false; +static bool fuzzingSafe = false; +static bool disableOOMFunctions = false; +#ifdef DEBUG +static bool dumpEntrainedVariables = false; +static bool OOM_printAllocationCount = false; +#endif + +// Shell state this is only accessed on the main thread. +bool jsCachingEnabled = false; mozilla::Atomic<bool> jsCacheOpened(false); static bool SetTimeoutValue(JSContext* cx, double t); static bool InitWatchdog(JSRuntime* rt); static void -KillWatchdog(); +KillWatchdog(JSRuntime *rt); static bool ScheduleWatchdog(JSRuntime* rt, double t); static void CancelExecution(JSRuntime* rt); -/* - * Watchdog thread state. - */ -static PRLock* gWatchdogLock = nullptr; -static PRCondVar* gWatchdogWakeup = nullptr; -static PRThread* gWatchdogThread = nullptr; -static bool gWatchdogHasTimeout = false; -static int64_t gWatchdogTimeout = 0; - -static PRCondVar* gSleepWakeup = nullptr; - -static int gExitCode = 0; -static bool gQuitting = false; -static bool gGotError = false; -static FILE* gErrFile = nullptr; -static FILE* gOutFile = nullptr; - -static bool reportWarnings = true; -static bool compileOnly = false; -static bool fuzzingSafe = false; -static bool disableOOMFunctions = false; - -#ifdef DEBUG -static bool dumpEntrainedVariables = false; -static bool OOM_printAllocationCount = false; -#endif - static JSContext* NewContext(JSRuntime* rt); static void DestroyContext(JSContext* cx, bool withGC); static JSObject* NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options, @@ -262,16 +269,46 @@ ShellPrincipals ShellPrincipals::fullyTr #ifdef EDITLINE extern "C" { extern JS_EXPORT_API(char*) readline(const char* prompt); extern JS_EXPORT_API(void) add_history(char* line); } // extern "C" #endif +ShellRuntime::ShellRuntime() + : isWorker(false), + timeoutInterval(-1.0), + serviceInterrupt(false), + lastWarningEnabled(false), + watchdogLock(nullptr), + watchdogWakeup(nullptr), + watchdogThread(nullptr), + watchdogHasTimeout(false), + watchdogTimeout(0), + sleepWakeup(nullptr), + exitCode(0), + quitting(false), + gotError(false) +{} + +static ShellRuntime* +GetShellRuntime(JSRuntime *rt) +{ + ShellRuntime* sr = static_cast<ShellRuntime*>(JS_GetRuntimePrivate(rt)); + MOZ_ASSERT(sr); + return sr; +} + +static ShellRuntime* +GetShellRuntime(JSContext* cx) +{ + return GetShellRuntime(cx->runtime()); +} + static char* GetLine(FILE* file, const char * prompt) { #ifdef EDITLINE /* * Use readline only if file is stdin, because there's no way to specify * another handle. Are other filehandles interactive? */ @@ -365,27 +402,28 @@ GetContextData(JSContext* cx) MOZ_ASSERT(data); return data; } static bool ShellInterruptCallback(JSContext* cx) { - if (!gServiceInterrupt) + ShellRuntime* sr = GetShellRuntime(cx); + if (!sr->serviceInterrupt) return true; - // Reset gServiceInterrupt. CancelExecution or InterruptIf will set it to + // Reset serviceInterrupt. CancelExecution or InterruptIf will set it to // true to distinguish watchdog or user triggered interrupts. // Do this first to prevent other interrupts that may occur while the // user-supplied callback is executing from re-entering the handler. - gServiceInterrupt = false; + sr->serviceInterrupt = false; bool result; - RootedValue interruptFunc(cx, gInterruptFunc); + RootedValue interruptFunc(cx, sr->interruptFunc); if (!interruptFunc.isNull()) { JS::AutoSaveExceptionState savedExc(cx); JSAutoCompartment ac(cx, &interruptFunc.toObject()); RootedValue rval(cx); if (!JS_CallFunctionValue(cx, nullptr, interruptFunc, JS::HandleValueArray::empty(), &rval)) { return false; @@ -393,18 +431,18 @@ ShellInterruptCallback(JSContext* cx) if (rval.isBoolean()) result = rval.toBoolean(); else result = false; } else { result = false; } - if (!result && gExitCode == 0) - gExitCode = EXITCODE_TIMEOUT; + if (!result && sr->exitCode == 0) + sr->exitCode = EXITCODE_TIMEOUT; return result; } /* * Some UTF-8 files, notably those written using Notepad, have a Unicode * Byte-Order-Mark (BOM) as their first character. This is useless (byte-order * is meaningless for UTF-8) but causes a syntax error unless we skip it. @@ -427,16 +465,18 @@ SkipUTF8BOM(FILE* file) ungetc(ch2, file); if (ch1 != EOF) ungetc(ch1, file); } static void RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) { + ShellRuntime* sr = GetShellRuntime(cx); + SkipUTF8BOM(file); // To support the UNIX #! shell hack, gobble the first line if it starts // with '#'. int ch = fgetc(file); if (ch == '#') { while ((ch = fgetc(file)) != EOF) { if (ch == '\n' || ch == '\r') @@ -451,29 +491,29 @@ RunFile(JSContext* cx, const char* filen { CompileOptions options(cx); options.setIntroductionType("js shell file") .setUTF8(true) .setFileAndLine(filename, 1) .setIsRunOnce(true) .setNoScriptRval(true); - gGotError = false; + sr->gotError = false; (void) JS::Compile(cx, options, file, &script); - MOZ_ASSERT_IF(!script, gGotError); + MOZ_ASSERT_IF(!script, sr->gotError); } #ifdef DEBUG if (dumpEntrainedVariables) AnalyzeEntrainedVariables(cx, script); #endif if (script && !compileOnly) { if (!JS_ExecuteScript(cx, script)) { - if (!gQuitting && gExitCode != EXITCODE_TIMEOUT) - gExitCode = EXITCODE_RUNTIME_ERROR; + if (!sr->quitting && sr->exitCode != EXITCODE_TIMEOUT) + sr->exitCode = EXITCODE_RUNTIME_ERROR; } int64_t t2 = PRMJ_Now() - t1; if (printTiming) printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC); } } static bool @@ -509,64 +549,65 @@ EvalAndPrint(JSContext* cx, const char* JS_free(cx, utf8chars); } return true; } static void ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly) { + ShellRuntime* sr = GetShellRuntime(cx); int lineno = 1; bool hitEOF = false; do { /* * Accumulate lines until we get a 'compilable unit' - one that either * generates an error (before running out of source) or that compiles * cleanly. This should be whenever we get a complete statement that * coincides with the end of a line. */ int startline = lineno; typedef Vector<char, 32> CharBuffer; CharBuffer buffer(cx); do { ScheduleWatchdog(cx->runtime(), -1); - gServiceInterrupt = false; + sr->serviceInterrupt = false; errno = 0; char* line = GetLine(in, startline == lineno ? "js> " : ""); if (!line) { if (errno) { JS_ReportError(cx, strerror(errno)); return; } hitEOF = true; break; } if (!buffer.append(line, strlen(line)) || !buffer.append('\n')) return; lineno++; - if (!ScheduleWatchdog(cx->runtime(), gTimeoutInterval)) { + if (!ScheduleWatchdog(cx->runtime(), sr->timeoutInterval)) { hitEOF = true; break; } } while (!JS_BufferIsCompilableUnit(cx, cx->global(), buffer.begin(), buffer.length())); if (hitEOF && buffer.empty()) break; if (!EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly, out)) { // Catch the error, report it, and keep going. JS_ReportPendingException(cx); } - } while (!hitEOF && !gQuitting); + } while (!hitEOF && !sr->quitting); fprintf(out, "\n"); } static void Process(JSContext* cx, const char* filename, bool forceTTY) { FILE* file; @@ -1538,16 +1579,18 @@ PrintErr(JSContext* cx, unsigned argc, V } static bool Help(JSContext* cx, unsigned argc, Value* vp); static bool Quit(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); + #ifdef JS_MORE_DETERMINISTIC // Print a message to stderr in more-deterministic builds to help jsfunfuzz // find uncatchable-exception bugs. fprintf(stderr, "quit called\n"); #endif CallArgs args = CallArgsFromVp(argc, vp); int32_t code; @@ -1559,18 +1602,18 @@ Quit(JSContext* cx, unsigned argc, Value // POSIX platforms, the exit code is 8-bit and negative values can also // result in an exit code >= 128. We restrict the value to range [0, 127] to // avoid false positives. if (code < 0 || code >= 128) { JS_ReportError(cx, "quit exit code should be in range 0-127"); return false; } - gExitCode = code; - gQuitting = true; + sr->exitCode = code; + sr->quitting = true; return false; } static bool StartTimingMutator(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() > 0) { @@ -2540,19 +2583,35 @@ WorkerMain(void* arg) { WorkerInput* input = (WorkerInput*) arg; JSRuntime* rt = JS_NewRuntime(8L * 1024L * 1024L, 2L * 1024L * 1024L, input->runtime); if (!rt) { js_delete(input); return; } + + mozilla::UniquePtr<ShellRuntime> sr = MakeUnique<ShellRuntime>(); + if (!sr) { + JS_DestroyRuntime(rt); + js_delete(input); + return; + } + + sr->isWorker = true; + JS_SetRuntimePrivate(rt, sr.get()); JS_SetErrorReporter(rt, my_ErrorReporter); SetWorkerRuntimeOptions(rt); + if (!InitWatchdog(rt)) { + JS_DestroyRuntime(rt); + js_delete(input); + return; + } + JSContext* cx = NewContext(rt); if (!cx) { JS_DestroyRuntime(rt); js_delete(input); return; } JS::SetLargeAllocationFailureCallback(rt, my_LargeAllocFailCallback, (void*)cx); @@ -2648,16 +2707,17 @@ static bool IsBefore(int64_t t1, int64_t t2) { return int32_t(t1 - t2) < 0; } static bool Sleep_fn(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); int64_t t_ticks; if (args.length() == 0) { t_ticks = 0; } else { double t_secs; @@ -2668,185 +2728,192 @@ Sleep_fn(JSContext* cx, unsigned argc, V if (!(t_secs <= MAX_TIMEOUT_INTERVAL)) { JS_ReportError(cx, "Excessive sleep interval"); return false; } t_ticks = (t_secs <= 0.0) ? 0 : int64_t(PRMJ_USEC_PER_SEC * t_secs); } - PR_Lock(gWatchdogLock); + PR_Lock(sr->watchdogLock); int64_t to_wakeup = PRMJ_Now() + t_ticks; for (;;) { - PR_WaitCondVar(gSleepWakeup, PR_MillisecondsToInterval(t_ticks / 1000)); - if (gServiceInterrupt) + PR_WaitCondVar(sr->sleepWakeup, PR_MillisecondsToInterval(t_ticks / 1000)); + if (sr->serviceInterrupt) break; int64_t now = PRMJ_Now(); if (!IsBefore(now, to_wakeup)) break; t_ticks = to_wakeup - now; } - PR_Unlock(gWatchdogLock); + PR_Unlock(sr->watchdogLock); args.rval().setUndefined(); - return !gServiceInterrupt; + return !sr->serviceInterrupt; } static bool InitWatchdog(JSRuntime* rt) { - MOZ_ASSERT(!gWatchdogThread); - gWatchdogLock = PR_NewLock(); - if (gWatchdogLock) { - gWatchdogWakeup = PR_NewCondVar(gWatchdogLock); - if (gWatchdogWakeup) { - gSleepWakeup = PR_NewCondVar(gWatchdogLock); - if (gSleepWakeup) + ShellRuntime* sr = GetShellRuntime(rt); + MOZ_ASSERT(!sr->watchdogThread); + sr->watchdogLock = PR_NewLock(); + if (sr->watchdogLock) { + sr->watchdogWakeup = PR_NewCondVar(sr->watchdogLock); + if (sr->watchdogWakeup) { + sr->sleepWakeup = PR_NewCondVar(sr->watchdogLock); + if (sr->sleepWakeup) return true; - PR_DestroyCondVar(gWatchdogWakeup); + PR_DestroyCondVar(sr->watchdogWakeup); } - PR_DestroyLock(gWatchdogLock); + PR_DestroyLock(sr->watchdogLock); } return false; } static void -KillWatchdog() -{ +KillWatchdog(JSRuntime* rt) +{ + ShellRuntime* sr = GetShellRuntime(rt); PRThread* thread; - PR_Lock(gWatchdogLock); - thread = gWatchdogThread; + PR_Lock(sr->watchdogLock); + thread = sr->watchdogThread; if (thread) { /* * The watchdog thread is running, tell it to terminate waking it up * if necessary. */ - gWatchdogThread = nullptr; - PR_NotifyCondVar(gWatchdogWakeup); - } - PR_Unlock(gWatchdogLock); + sr->watchdogThread = nullptr; + PR_NotifyCondVar(sr->watchdogWakeup); + } + PR_Unlock(sr->watchdogLock); if (thread) PR_JoinThread(thread); - PR_DestroyCondVar(gSleepWakeup); - PR_DestroyCondVar(gWatchdogWakeup); - PR_DestroyLock(gWatchdogLock); + PR_DestroyCondVar(sr->sleepWakeup); + PR_DestroyCondVar(sr->watchdogWakeup); + PR_DestroyLock(sr->watchdogLock); } static void WatchdogMain(void* arg) { PR_SetCurrentThreadName("JS Watchdog"); JSRuntime* rt = (JSRuntime*) arg; - - PR_Lock(gWatchdogLock); - while (gWatchdogThread) { + ShellRuntime* sr = GetShellRuntime(rt); + + PR_Lock(sr->watchdogLock); + while (sr->watchdogThread) { int64_t now = PRMJ_Now(); - if (gWatchdogHasTimeout && !IsBefore(now, gWatchdogTimeout)) { + if (sr->watchdogHasTimeout && !IsBefore(now, sr->watchdogTimeout)) { /* * The timeout has just expired. Request an interrupt callback * outside the lock. */ - gWatchdogHasTimeout = false; - PR_Unlock(gWatchdogLock); + sr->watchdogHasTimeout = false; + PR_Unlock(sr->watchdogLock); CancelExecution(rt); - PR_Lock(gWatchdogLock); + PR_Lock(sr->watchdogLock); /* Wake up any threads doing sleep. */ - PR_NotifyAllCondVar(gSleepWakeup); + PR_NotifyAllCondVar(sr->sleepWakeup); } else { - if (gWatchdogHasTimeout) { + if (sr->watchdogHasTimeout) { /* * Time hasn't expired yet. Simulate an interrupt callback * which doesn't abort execution. */ JS_RequestInterruptCallback(rt); } uint64_t sleepDuration = PR_INTERVAL_NO_TIMEOUT; - if (gWatchdogHasTimeout) + if (sr->watchdogHasTimeout) sleepDuration = PR_TicksPerSecond() / 10; mozilla::DebugOnly<PRStatus> status = - PR_WaitCondVar(gWatchdogWakeup, sleepDuration); + PR_WaitCondVar(sr->watchdogWakeup, sleepDuration); MOZ_ASSERT(status == PR_SUCCESS); } } - PR_Unlock(gWatchdogLock); + PR_Unlock(sr->watchdogLock); } static bool ScheduleWatchdog(JSRuntime* rt, double t) { + ShellRuntime* sr = GetShellRuntime(rt); + if (t <= 0) { - PR_Lock(gWatchdogLock); - gWatchdogHasTimeout = false; - PR_Unlock(gWatchdogLock); + PR_Lock(sr->watchdogLock); + sr->watchdogHasTimeout = false; + PR_Unlock(sr->watchdogLock); return true; } int64_t interval = int64_t(ceil(t * PRMJ_USEC_PER_SEC)); int64_t timeout = PRMJ_Now() + interval; - PR_Lock(gWatchdogLock); - if (!gWatchdogThread) { - MOZ_ASSERT(!gWatchdogHasTimeout); - gWatchdogThread = PR_CreateThread(PR_USER_THREAD, + PR_Lock(sr->watchdogLock); + if (!sr->watchdogThread) { + MOZ_ASSERT(!sr->watchdogHasTimeout); + sr->watchdogThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, rt, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); - if (!gWatchdogThread) { - PR_Unlock(gWatchdogLock); + if (!sr->watchdogThread) { + PR_Unlock(sr->watchdogLock); return false; } - } else if (!gWatchdogHasTimeout || IsBefore(timeout, gWatchdogTimeout)) { - PR_NotifyCondVar(gWatchdogWakeup); - } - gWatchdogHasTimeout = true; - gWatchdogTimeout = timeout; - PR_Unlock(gWatchdogLock); + } else if (!sr->watchdogHasTimeout || IsBefore(timeout, sr->watchdogTimeout)) { + PR_NotifyCondVar(sr->watchdogWakeup); + } + sr->watchdogHasTimeout = true; + sr->watchdogTimeout = timeout; + PR_Unlock(sr->watchdogLock); return true; } static void CancelExecution(JSRuntime* rt) { - gServiceInterrupt = true; + ShellRuntime* sr = GetShellRuntime(rt); + sr->serviceInterrupt = true; JS_RequestInterruptCallback(rt); - if (!gInterruptFunc.isNull()) { + if (!sr->interruptFunc.isNull()) { static const char msg[] = "Script runs for too long, terminating.\n"; fputs(msg, stderr); } } static bool SetTimeoutValue(JSContext* cx, double t) { /* NB: The next condition also filter out NaNs. */ if (!(t <= MAX_TIMEOUT_INTERVAL)) { JS_ReportError(cx, "Excessive timeout value"); return false; } - gTimeoutInterval = t; + GetShellRuntime(cx)->timeoutInterval = t; if (!ScheduleWatchdog(cx->runtime(), t)) { JS_ReportError(cx, "Failed to create the watchdog"); return false; } return true; } static bool Timeout(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); if (args.length() == 0) { - args.rval().setNumber(gTimeoutInterval); + args.rval().setNumber(sr->timeoutInterval); return true; } if (args.length() > 2) { JS_ReportError(cx, "Wrong number of arguments"); return false; } @@ -2855,17 +2922,17 @@ Timeout(JSContext* cx, unsigned argc, Va return false; if (args.length() > 1) { RootedValue value(cx, args[1]); if (!value.isObject() || !value.toObject().is<JSFunction>()) { JS_ReportError(cx, "Second argument must be a timeout function"); return false; } - gInterruptFunc = value; + sr->interruptFunc = value; } args.rval().setUndefined(); return SetTimeoutValue(cx, t); } static bool InterruptIf(JSContext* cx, unsigned argc, Value* vp) @@ -2873,34 +2940,34 @@ InterruptIf(JSContext* cx, unsigned argc CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { JS_ReportError(cx, "Wrong number of arguments"); return false; } if (ToBoolean(args[0])) { - gServiceInterrupt = true; + GetShellRuntime(cx)->serviceInterrupt = true; JS_RequestInterruptCallback(cx->runtime()); } args.rval().setUndefined(); return true; } static bool InvokeInterruptCallbackWrapper(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { JS_ReportError(cx, "Wrong number of arguments"); return false; } - gServiceInterrupt = true; + GetShellRuntime(cx)->serviceInterrupt = true; JS_RequestInterruptCallback(cx->runtime()); bool interruptRv = CheckForInterrupt(cx); // The interrupt handler could have set a pending exception. Since we call // back into JS, don't have it see the pending exception. If we have an // uncatchable exception that's not propagating a debug mode forced // return, return. if (!interruptRv && !cx->isExceptionPending() && !cx->isPropagatingForcedReturn()) @@ -2926,74 +2993,78 @@ SetInterruptCallback(JSContext* cx, unsi return false; } RootedValue value(cx, args[0]); if (!value.isObject() || !value.toObject().is<JSFunction>()) { JS_ReportError(cx, "Argument must be a function"); return false; } - gInterruptFunc = value; + GetShellRuntime(cx)->interruptFunc = value; args.rval().setUndefined(); return true; } static bool EnableLastWarning(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); - gLastWarningEnabled = true; - gLastWarning.setNull(); + sr->lastWarningEnabled = true; + sr->lastWarning.setNull(); args.rval().setUndefined(); return true; } static bool DisableLastWarning(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); - gLastWarningEnabled = false; - gLastWarning.setNull(); + sr->lastWarningEnabled = false; + sr->lastWarning.setNull(); args.rval().setUndefined(); return true; } static bool GetLastWarning(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); - if (!gLastWarningEnabled) { + if (!sr->lastWarningEnabled) { JS_ReportError(cx, "Call enableLastWarning first."); return false; } - if (!JS_WrapValue(cx, &gLastWarning)) - return false; - - args.rval().set(gLastWarning); + if (!JS_WrapValue(cx, &sr->lastWarning)) + return false; + + args.rval().set(sr->lastWarning); return true; } static bool ClearLastWarning(JSContext* cx, unsigned argc, Value* vp) { + ShellRuntime* sr = GetShellRuntime(cx); CallArgs args = CallArgsFromVp(argc, vp); - if (!gLastWarningEnabled) { + if (!sr->lastWarningEnabled) { JS_ReportError(cx, "Call enableLastWarning first."); return false; } - gLastWarning.setNull(); + sr->lastWarning.setNull(); args.rval().setUndefined(); return true; } #ifdef DEBUG static bool StackDump(JSContext* cx, unsigned argc, Value* vp) @@ -3888,16 +3959,21 @@ IsCachingEnabled(JSContext* cx, unsigned args.rval().setBoolean(jsCachingEnabled && jsCacheAsmJSPath != nullptr); return true; } static bool SetCachingEnabled(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + if (GetShellRuntime(cx)->isWorker) { + JS_ReportError(cx, "Caching is not supported in workers"); + return false; + } + jsCachingEnabled = ToBoolean(args.get(0)); args.rval().setUndefined(); return true; } static void PrintProfilerEvents_Callback(const char* msg) { @@ -5137,17 +5213,17 @@ CreateLastWarningObject(JSContext* cx, J RootedValue linenoVal(cx, Int32Value(report->lineno)); if (!DefineProperty(cx, warningObj, cx->names().lineNumber, linenoVal)) return false; RootedValue columnVal(cx, Int32Value(report->column)); if (!DefineProperty(cx, warningObj, cx->names().columnNumber, columnVal)) return false; - gLastWarning.setObject(*warningObj); + GetShellRuntime(cx)->lastWarning.setObject(*warningObj); return true; } static bool PrintStackTrace(JSContext* cx, HandleValue exn) { if (!exn.isObject()) return false; @@ -5179,55 +5255,56 @@ PrintStackTrace(JSContext* cx, HandleVal fputs(stack.get(), gErrFile); return true; } void js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) { - if (report && JSREPORT_IS_WARNING(report->flags) && gLastWarningEnabled) { + ShellRuntime* sr = GetShellRuntime(cx); + + if (report && JSREPORT_IS_WARNING(report->flags) && sr->lastWarningEnabled) { JS::AutoSaveExceptionState savedExc(cx); if (!CreateLastWarningObject(cx, report)) { fputs("Unhandled error happened while creating last warning object.\n", gOutFile); fflush(gOutFile); } savedExc.restore(); } // Get exception object before printing and clearing exception. RootedValue exn(cx); if (JS_IsExceptionPending(cx)) (void) JS_GetPendingException(cx, &exn); - gGotError = PrintError(cx, gErrFile, message, report, reportWarnings); + sr->gotError = PrintError(cx, gErrFile, message, report, reportWarnings); if (!exn.isUndefined()) { JS::AutoSaveExceptionState savedExc(cx); if (!PrintStackTrace(cx, exn)) fputs("(Unable to print stack trace)\n", gOutFile); savedExc.restore(); } if (report->exnType != JSEXN_NONE && !JSREPORT_IS_WARNING(report->flags)) { - if (report->errorNumber == JSMSG_OUT_OF_MEMORY) { - gExitCode = EXITCODE_OUT_OF_MEMORY; - } else { - gExitCode = EXITCODE_RUNTIME_ERROR; - } + if (report->errorNumber == JSMSG_OUT_OF_MEMORY) + sr->exitCode = EXITCODE_OUT_OF_MEMORY; + else + sr->exitCode = EXITCODE_RUNTIME_ERROR; } } static void my_OOMCallback(JSContext* cx, void* data) { // If a script is running, the engine is about to throw the string "out of // memory", which may or may not be caught. Otherwise the engine will just // unwind and return null/false, with no exception set. if (!JS_IsRunning(cx)) - gGotError = true; + GetShellRuntime(cx)->gotError = true; } static bool global_enumerate(JSContext* cx, HandleObject obj) { #ifdef LAZY_STANDARD_CLASSES return JS_EnumerateStandardClasses(cx, obj); #else @@ -5762,16 +5839,23 @@ NewGlobalObject(JSContext* cx, JS::Compa { JSAutoCompartment ac(cx, glob); #ifndef LAZY_STANDARD_CLASSES if (!JS_InitStandardClasses(cx, glob)) return nullptr; #endif + bool succeeded; + if (!JS_SetImmutablePrototype(cx, glob, &succeeded)) + return nullptr; + MOZ_ASSERT(succeeded, + "a fresh, unexposed global object is always capable of " + "having its [[Prototype]] be immutable"); + #ifdef JS_HAS_CTYPES if (!JS_InitCTypesClass(cx, glob)) return nullptr; #endif if (!JS_InitReflectParse(cx, glob)) return nullptr; if (!JS_DefineDebuggerObject(cx, glob)) return nullptr; @@ -5875,67 +5959,69 @@ OptionFailure(const char* option, const { fprintf(stderr, "Unrecognized option for %s: %s\n", option, str); return false; } static int ProcessArgs(JSContext* cx, OptionParser* op) { + ShellRuntime* sr = GetShellRuntime(cx); + if (op->getBoolOption('s')) JS::RuntimeOptionsRef(cx).toggleExtraWarnings(); /* |scriptArgs| gets bound on the global before any code is run. */ if (!BindScriptArgs(cx, op)) return EXIT_FAILURE; MultiStringRange filePaths = op->getMultiStringOption('f'); MultiStringRange codeChunks = op->getMultiStringOption('e'); if (filePaths.empty() && codeChunks.empty() && !op->getStringArg("script")) { Process(cx, nullptr, true); /* Interactive. */ - return gExitCode; + return sr->exitCode; } while (!filePaths.empty() || !codeChunks.empty()) { size_t fpArgno = filePaths.empty() ? -1 : filePaths.argno(); size_t ccArgno = codeChunks.empty() ? -1 : codeChunks.argno(); if (fpArgno < ccArgno) { char* path = filePaths.front(); Process(cx, path, false); - if (gExitCode) - return gExitCode; + if (sr->exitCode) + return sr->exitCode; filePaths.popFront(); } else { const char* code = codeChunks.front(); RootedValue rval(cx); JS::CompileOptions opts(cx); opts.setFileAndLine("-e", 1); if (!JS::Evaluate(cx, opts, code, strlen(code), &rval)) - return gExitCode ? gExitCode : EXITCODE_RUNTIME_ERROR; + return sr->exitCode ? sr->exitCode : EXITCODE_RUNTIME_ERROR; codeChunks.popFront(); - if (gQuitting) + if (sr->quitting) break; } } - if (gQuitting) - return gExitCode ? gExitCode : EXIT_SUCCESS; + if (sr->quitting) + return sr->exitCode ? sr->exitCode : EXIT_SUCCESS; /* The |script| argument is processed after all options. */ if (const char* path = op->getStringArg("script")) { Process(cx, path, false); - if (gExitCode) - return gExitCode; + if (sr->exitCode) + return sr->exitCode; } if (op->getBoolOption('i')) Process(cx, nullptr, true); - return gExitCode ? gExitCode : EXIT_SUCCESS; + return sr->exitCode ? sr->exitCode : EXIT_SUCCESS; } static bool SetRuntimeOptions(JSRuntime* rt, const OptionParser& op) { enableBaseline = !op.getBoolOption("no-baseline"); enableIon = !op.getBoolOption("no-ion"); enableAsmJS = !op.getBoolOption("no-asmjs"); @@ -6525,23 +6611,28 @@ main(int argc, char** argv, char** envp) size_t nurseryBytes = JS::DefaultNurseryBytes; nurseryBytes = op.getIntOption("nursery-size") * 1024L * 1024L; /* Use the same parameters as the browser in xpcjsruntime.cpp. */ rt = JS_NewRuntime(JS::DefaultHeapMaxBytes, nurseryBytes); if (!rt) return 1; + mozilla::UniquePtr<ShellRuntime> sr = MakeUnique<ShellRuntime>(); + if (!sr) + return 1; + + JS_SetRuntimePrivate(rt, sr.get()); JS_SetErrorReporter(rt, my_ErrorReporter); JS::SetOutOfMemoryCallback(rt, my_OOMCallback, nullptr); if (!SetRuntimeOptions(rt, op)) return 1; - gInterruptFunc.init(rt, NullValue()); - gLastWarning.init(rt, NullValue()); + sr->interruptFunc.init(rt, NullValue()); + sr->lastWarning.init(rt, NullValue()); JS_SetGCParameter(rt, JSGC_MAX_BYTES, 0xffffffff); size_t availMem = op.getIntOption("available-memory"); if (availMem > 0) JS_SetGCParametersBasedOnAvailableMemory(rt, availMem); JS_SetTrustedPrincipals(rt, &ShellPrincipals::fullyTrusted); @@ -6589,17 +6680,17 @@ main(int argc, char** argv, char** envp) if (OOM_printAllocationCount) printf("OOM max count: %u\n", OOM_counter); #endif JS::SetLargeAllocationFailureCallback(rt, nullptr, nullptr); DestroyContext(cx, true); - KillWatchdog(); + KillWatchdog(rt); MOZ_ASSERT_IF(!CanUseExtraThreads(), workerThreads.empty()); for (size_t i = 0; i < workerThreads.length(); i++) PR_JoinThread(workerThreads[i]); DestructSharedArrayBufferMailbox(); JS_DestroyRuntime(rt);
--- a/js/src/tests/ecma_6/Class/classHeritage.js +++ b/js/src/tests/ecma_6/Class/classHeritage.js @@ -31,21 +31,21 @@ var staticMethodCalled; var overrideCalled; class base { constructor() { }; method() { baseMethodCalled = true; } static staticMethod() { staticMethodCalled = true; } override() { overrideCalled = "base" } } class derived extends base { - constructor() { }; + constructor() { super(); }; override() { overrideCalled = "derived"; } } var derivedExpr = class extends base { - constructor() { }; + constructor() { super(); }; override() { overrideCalled = "derived"; } }; // Make sure we get the right object layouts. for (let extension of [derived, derivedExpr]) { baseMethodCalled = false; staticMethodCalled = false; overrideCalled = "";
--- a/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js +++ b/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js @@ -13,25 +13,26 @@ class derived extends base { constructor() { eval(''); } } // Make sure eval and arrows are still valid in non-derived constructors. new base(); + // Eval throws in derived class constructors, in both class expressions and // statements. assertThrowsInstanceOf((() => new derived()), InternalError); assertThrowsInstanceOf((() => new class extends base { constructor() { eval('') } }()), InternalError); var g = newGlobal(); var dbg = Debugger(g); dbg.onDebuggerStatement = function(frame) { assertThrowsInstanceOf(()=>frame.eval(''), InternalError); }; -g.eval("new class foo extends null { constructor() { debugger; } }()"); +g.eval("new class foo extends null { constructor() { debugger; return {}; } }()"); `; if (classesEnabled()) eval(test); if (typeof reportCompare === 'function') reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorInlining.js @@ -0,0 +1,19 @@ +// Since we (for now!) can't emit jitcode for derived class statements. Make +// sure we can corectly invoke derived class constructors. + +class foo extends null { + constructor() { + // Anything that tests |this| should throw, so just let it run off the + // end. + } +} + +function intermediate() { + new foo(); +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(intermediate, "|this|"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorReturnPrimitive.js @@ -0,0 +1,15 @@ +class foo extends null { + constructor() { + // Returning a primitive is a TypeError in derived constructors. This + // ensures that super() can take the return value directly, without + // checking it. Use |null| here, as a tricky check to make sure we + // didn't lump it in with the object check, somehow. + return null; + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "return"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZExplicitThis.js @@ -0,0 +1,18 @@ +function pleaseRunMyCode() { } + +class foo extends null { + constructor() { + // Just bareword |this| is DCEd by the BytecodeEmitter. Your guess as + // to why we think this is a good idea is as good as mine. In order to + // combat this inanity, make it a function arg, so we have to compute + // it. + pleaseRunMyCode(this); + assertEq(false, true); + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "|this|"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZOffEdge.js @@ -0,0 +1,11 @@ +class foo extends null { + constructor() { + // Let it fall off the edge and throw. + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "|this|"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnObject.js @@ -0,0 +1,13 @@ +class foo extends null { + constructor() { + // If you return an object, we don't care that |this| went + // uninitialized + return {}; + } +} + +for (let i = 0; i < 1100; i++) + new foo(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnUndefined.js @@ -0,0 +1,13 @@ +class foo extends null { + constructor() { + // Explicit returns of undefined should act the same as falling off the + // end of the function. That is to say, they should throw. + return undefined; + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "|this|"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallBadDynamicSuperClass.js @@ -0,0 +1,17 @@ +var test = ` + +class base { constructor() { } } + +class inst extends base { constructor() { super(); } } + +Object.setPrototypeOf(inst, Math.sin); + +assertThrowsInstanceOf(() => new inst(), TypeError); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallBadNewTargetPrototype.js @@ -0,0 +1,30 @@ +var test = ` + +class base { constructor() { } } + +// lies and the lying liars who tell them +function lies() { } +lies.prototype = 4; + +assertThrowsInstanceOf(()=>Reflect.consruct(base, [], lies), TypeError); + +// lie a slightly different way +function get(target, property, receiver) { + if (property === "prototype") + return 42; + return Reflect.get(target, property, receiver); +} + +class inst extends base { + constructor() { super(); } +} + +assertThrowsInstanceOf(()=>new new Proxy(inst, {get})(), TypeError); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallBaseInvoked.js @@ -0,0 +1,53 @@ +var test = ` + +function testBase(base) { + class instance extends base { + constructor(inst, one) { + super(inst, one); + } + } + + let inst = new instance(instance, 1); + assertEq(Object.getPrototypeOf(inst), instance.prototype); + assertEq(inst.calledBase, true); +} + +class base { + // Base class must be [[Construct]]ed, as you cannot [[Call]] a class + // constructor + constructor(nt, one) { + assertEq(new.target, nt); + + // Check argument ordering + assertEq(one, 1); + this.calledBase = true; + } +} + +testBase(base); +testBase(class extends base { + constructor(nt, one) { + // Every step of the way, new.target and args should be right + assertEq(new.target, nt); + assertEq(one, 1); + super(nt, one); + } + }); +function baseFunc(nt, one) { + assertEq(new.target, nt); + assertEq(one, 1); + this.calledBase = true; +} + +testBase(baseFunc); +testBase(new Proxy(baseFunc, {})); + +// Object will have to wait for fixed builtins. + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallIllegal.js @@ -0,0 +1,7 @@ +// super() invalid outside derived class constructors, including in dynamic +// functions and eval +assertThrowsInstanceOf(() => new Function("super();"), SyntaxError); +assertThrowsInstanceOf(() => eval("super()"), SyntaxError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallInvalidBase.js @@ -0,0 +1,15 @@ +var test = ` + +class instance extends null { + constructor() { super(); } +} + +assertThrowsInstanceOf(() => new instance(), TypeError); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallOrder.js @@ -0,0 +1,33 @@ +var test = ` + +function base() { } + +class beforeSwizzle extends base { + constructor() { + super(Object.setPrototypeOf(beforeSwizzle, null)); + } +} + +new beforeSwizzle(); + +// Again, testing both dynamic prototype dispatch, and that we get the function +// before evaluating args +class beforeThrow extends base { + constructor() { + function thrower() { throw new Error(); } + super(thrower()); + } +} + +Object.setPrototypeOf(beforeThrow, Math.sin); + +// Will throw that Math.sin is not a constructor before evaluating the args +assertThrowsInstanceOf(() => new beforeThrow(), TypeError); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallProperBase.js @@ -0,0 +1,33 @@ +var test = ` + +class base1 { + constructor() { + this.base = 1; + } +} + +class base2 { + constructor() { + this.base = 2; + } +} + +class inst extends base1 { + constructor() { + super(); + } +} + +assertEq(new inst().base, 1); + +Object.setPrototypeOf(inst, base2); + +assertEq(new inst().base, 2); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallSpreadCall.js @@ -0,0 +1,34 @@ +var test = ` + +class base { + constructor(a, b, c) { + assertEq(a, 1); + assertEq(b, 2); + assertEq(c, 3); + this.calledBase = true; + } +} + +class test extends base { + constructor(arr) { + super(...arr); + } +} + +assertEq(new test([1,2,3]).calledBase, true); + +class testRest extends base { + constructor(...args) { + super(...args); + } +} + +assertEq(new testRest(1,2,3).calledBase, true); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallThisInit.js @@ -0,0 +1,52 @@ +var test = ` + +function base() { this.prop = 42; } + +class testInitialize extends base { + constructor() { + // A poor man's assertThrowsInstanceOf, as arrow functions are currently + // disabled in this context + try { + this; + throw new Error(); + } catch (e if e instanceof ReferenceError) { } + super(); + assertEq(this.prop, 42); + } +} +assertEq(new testInitialize().prop, 42); + +// super() twice is a no-go. +class willThrow extends base { + constructor() { + super(); + super(); + } +} +assertThrowsInstanceOf(()=>new willThrow(), ReferenceError); + +// This is determined at runtime, not the syntax level. +class willStillThrow extends base { + constructor() { + for (let i = 0; i < 3; i++) { + super(); + } + } +} +assertThrowsInstanceOf(()=>new willStillThrow(), ReferenceError); + +class canCatchThrow extends base { + constructor() { + super(); + try { super(); } catch(e) { } + } +} +assertEq(new canCatchThrow().prop, 42); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropBasicGetter.js +++ b/js/src/tests/ecma_6/Class/superPropBasicGetter.js @@ -8,17 +8,17 @@ class base { } setValue(v) { this._prop = v; } } class derived extends base { - constructor() {} + constructor() { super(); } get a() { return super.getValue(); } set a(v) { super.setValue(v); } get b() { return eval('super.getValue()'); } set b(v) { eval('super.setValue(v);'); } test() {
--- a/js/src/tests/ecma_6/Class/superPropBasicNew.js +++ b/js/src/tests/ecma_6/Class/superPropBasicNew.js @@ -1,20 +1,21 @@ var test = ` class Base { constructor() {} } class Mid extends Base { - constructor() {} + constructor() { super(); } f() { return new super.constructor(); } } class Derived extends Mid { - constructor() {} + constructor() { super(); } } + let d = new Derived(); var df = d.f(); assertEq(df.constructor, Base); `; if (classesEnabled()) eval(test);
--- a/js/src/tests/ecma_6/Class/superPropChains.js +++ b/js/src/tests/ecma_6/Class/superPropChains.js @@ -4,55 +4,55 @@ var test = ` class base { constructor() { } testChain() { this.baseCalled = true; } } class middle extends base { - constructor() { } + constructor() { super(); } testChain() { this.middleCalled = true; super.testChain(); } } class derived extends middle { - constructor() { } + constructor() { super(); } testChain() { super.testChain(); assertEq(this.middleCalled, true); assertEq(this.baseCalled, true); } } new derived().testChain(); // Super even chains in a wellbehaved fashion with normal functions. function bootlegMiddle() { } bootlegMiddle.prototype = middle.prototype; new class extends bootlegMiddle { - constructor() { } + constructor() { super(); } testChain() { super.testChain(); assertEq(this.middleCalled, true); assertEq(this.baseCalled, true); } }().testChain(); // Now let's try out some "long" chains base.prototype.x = "yeehaw"; -let chain = class extends base { constructor() { } } +let chain = class extends base { constructor() { super(); } } const CHAIN_LENGTH = 100; for (let i = 0; i < CHAIN_LENGTH; i++) - chain = class extends chain { constructor() { } } + chain = class extends chain { constructor() { super(); } } // Now we poke the chain let inst = new chain(); inst.testChain(); assertEq(inst.baseCalled, true); assertEq(inst.x, "yeehaw");
--- a/js/src/tests/ecma_6/Class/superPropDelete.js +++ b/js/src/tests/ecma_6/Class/superPropDelete.js @@ -3,17 +3,17 @@ var test = ` // |delete super.prop| and |delete super[expr]| throw universally. // Even so, we should make sure we get proper side effects class base { constructor() { } } class derived extends base { - constructor() { } + constructor() { super(); } testDeleteProp() { delete super.prop; } testDeleteElem() { let sideEffect = 0; assertThrowsInstanceOf(() => delete super[sideEffect = 1], ReferenceError); assertEq(sideEffect, 1); } testDeleteElemPropValFirst() { // The deletion error is a reference error, but by munging the prototype
--- a/js/src/tests/ecma_6/Class/superPropDerivedCalls.js +++ b/js/src/tests/ecma_6/Class/superPropDerivedCalls.js @@ -21,17 +21,17 @@ class base { set prop(val) { assertEq(this, derivedInstance); this.setterCalled = true; this._prop = val; } } class derived extends base { - constructor() { } + constructor() { super(); } // |super| actually checks the chain, not |this| method() { throw "FAIL"; } get prop() { throw "FAIL"; } set prop(v) { throw "FAIL"; } test() { this.reset();
--- a/js/src/tests/ecma_6/Class/superPropDestructuring.js +++ b/js/src/tests/ecma_6/Class/superPropDestructuring.js @@ -17,17 +17,17 @@ Object.defineProperty(base.prototype, "i set(x) { assertEq(x, "Fred"); seenValues.push(x) } }); const testArr = [525600, "Fred"]; class derived extends base { - constructor() { } + constructor() { super(); } prepForTest() { seenValues = []; } testAsserts() { assertDeepEq(seenValues, testArr); } testProps() { this.prepForTest(); [super.minutes, super.intendent] = testArr; this.testAsserts(); } testElems() {
--- a/js/src/tests/ecma_6/Class/superPropHomeObject.js +++ b/js/src/tests/ecma_6/Class/superPropHomeObject.js @@ -9,17 +9,17 @@ var test = ` // means it's gonna vary wildly as stuff gets moved around. class base { constructor() { } test(expectedThis) { assertEq(this, expectedThis); } } class derived extends base { - constructor() { } + constructor() { super(); } test(expected) { super.test(expected); } testArrow() { return (() => super.test(this)); } ["testCPN"](expected) { super.test(expected); } } let derivedInstance = new derived(); derivedInstance.test(derivedInstance); derivedInstance.testCPN(derivedInstance); @@ -50,17 +50,17 @@ class base1 { class base2 { constructor() { } test() { return "alpaca"; } } let animals = []; for (let exprBase of [base1, base2]) new class extends exprBase { - constructor() { } + constructor() { super(); } test() { animals.push(super["test"]()); } }().test(); assertDeepEq(animals, ["llama", "alpaca"]); `; if (classesEnabled()) eval(test);
--- a/js/src/tests/ecma_6/Class/superPropOrdering.js +++ b/js/src/tests/ecma_6/Class/superPropOrdering.js @@ -1,26 +1,26 @@ var test = ` class base { constructor() { } method() { this.methodCalled++; } } class derived extends base { - constructor() { this.methodCalled = 0; } + constructor() { super(); this.methodCalled = 0; } // Test orderings of various evaluations relative to the superbase - + // Unlike in regular element evaluation, the propVal is evaluated before // checking the starting object ([[HomeObject]].[[Prototype]]) testElem() { super[ruin()]; } - + // The starting object for looking up super.method is determined before - // ruin() is called. + // ruin() is called. testProp() { super.method(ruin()); } // The entire super.method property lookup has concluded before the args // are evaluated testPropCallDeleted() { super.method(()=>delete base.prototype.method); } // The starting object for looking up super["prop"] is determined before // ruin() is called. @@ -66,17 +66,16 @@ function ruin() { return 5; } function reset() { Object.setPrototypeOf(derived.prototype, base.prototype); } let instance = new derived(); - assertThrowsInstanceOf(() => instance.testElem(), TypeError); reset(); instance.testProp(); assertEq(instance.methodCalled, 1); reset(); instance.testPropCallDeleted(); @@ -86,15 +85,16 @@ instance.testElemAssign(); assertEq(instance.prop, 5); reset(); instance.testAssignElemPropValChange(); instance.testAssignProp(); instance.testCompoundAssignProp(); + `; if (classesEnabled()) eval(test); if (typeof reportCompare === 'function') reportCompare(0,0,"OK");
--- a/js/src/tests/ecma_6/Class/superPropProtoChanges.js +++ b/js/src/tests/ecma_6/Class/superPropProtoChanges.js @@ -5,17 +5,17 @@ class base { test() { return false; } } let standin = { test() { return true; } }; class derived extends base { - constructor() { } + constructor() { super(); } test() { assertEq(super.test(), false); Object.setPrototypeOf(derived.prototype, standin); assertEq(super["test"](), true); } } new derived().test();
--- a/js/src/tests/ecma_6/Class/superPropProxies.js +++ b/js/src/tests/ecma_6/Class/superPropProxies.js @@ -6,36 +6,35 @@ class base { let midStaticHandler = { }; // We shouldn't use the |this.called| strategy here, since we have proxies // snooping property accesses. let getterCalled, setterCalled; class mid extends new Proxy(base, midStaticHandler) { - constructor() { } + constructor() { super(); } testSuperInProxy() { super.prop = "looking"; assertEq(setterCalled, true); assertEq(super.prop, "found"); assertEq(getterCalled, true); } } class child extends mid { - constructor() { } + constructor() { super(); } static testStaticLookups() { // This funtion is called more than once. this.called = false; super.prop; assertEq(this.called, true); } } - let midInstance = new mid(); // Make sure proxies are searched properly on the prototype chain let baseHandler = { get(target, p, receiver) { assertEq(receiver, midInstance); getterCalled = true; return "found";
--- a/js/src/tests/ecma_6/Class/superPropSkips.js +++ b/js/src/tests/ecma_6/Class/superPropSkips.js @@ -4,17 +4,17 @@ var test = ` // That is, super lookups start with the superclass, not the current class. // The whole point: an empty superclass class base { constructor() { } } class derived extends base { - constructor() { this.prop = "flamingo"; } + constructor() { super(); this.prop = "flamingo"; } toString() { throw "No!"; } testSkipGet() { assertEq(super.prop, undefined); } testSkipDerivedOverrides() { @@ -32,16 +32,17 @@ class derived extends base { // skipped in the super lookup. assertEq(this.nonWritableProp, "pony"); super.nonWritableProp = "bear"; assertEq(this.nonWritableProp, "bear"); } } Object.defineProperty(derived.prototype, "nonWritableProp", { writable: false, value: "pony" }); + let instance = new derived(); instance.testSkipGet(); instance.testSkipDerivedOverrides(); instance.testSkipSet(); `; if (classesEnabled())
--- a/js/src/tests/ecma_6/Proxy/revoke-as-side-effect.js +++ b/js/src/tests/ecma_6/Proxy/revoke-as-side-effect.js @@ -66,13 +66,18 @@ for (var k in createProxy({a: 5})) { // [[OwnPropertyKeys]] assertEq(Object.getOwnPropertyNames(createProxy({})).length, 0); assertEq(Object.getOwnPropertyNames(createProxy({a: 5})).length, 1); // [[Call]] assertEq(createProxy(function() { return "ok" })(), "ok"); // [[Construct]] -// This should throw per bug 1141865. -assertEq(new (createProxy(function(){ return obj; })), obj); +// This throws because after the "construct" trap on the proxy is consulted, +// OrdinaryCreateFromConstructor (called because the |q| function's +// [[ConstructorKind]] is "base" per FunctionAllocate) accesses +// |new.target.prototype| to create the |this| for the construct operation, that +// would be returned if |return obj;| didn't override it. +assertThrowsInstanceOf(() => new (createProxy(function q(){ return obj; })), + TypeError); if (typeof reportCompare === "function") reportCompare(true, true);
--- a/js/src/tests/ecma_6/Reflect/construct.js +++ b/js/src/tests/ecma_6/Reflect/construct.js @@ -33,30 +33,25 @@ assertDeepEq(Reflect.construct(bound, [] if (classesEnabled()) { eval(`{ class Base { constructor(...args) { this.args = args; this.newTarget = new.target; } } - //class Derived extends Base { - // constructor(...args) { super(...args); } - //} + class Derived extends Base { + constructor(...args) { super(...args); } + } assertDeepEq(Reflect.construct(Base, []), new Base); - //assertDeepEq(Reflect.construct(Derived, [7]), new Derived(7)); - //g = Derived.bind(null, "q"); - //assertDeepEq(Reflect.construct(g, [8, 9]), new g(8, 9)); + assertDeepEq(Reflect.construct(Derived, [7]), new Derived(7)); + g = Derived.bind(null, "q"); + assertDeepEq(Reflect.construct(g, [8, 9]), new g(8, 9)); }`); - - if (classesEnabled("class X extends Y { constructor() { super(); } }")) { - throw new Error("Congratulations on implementing super()! " + - "Please uncomment the Derived tests in this file!"); - } } // Cross-compartment wrappers: var g = newGlobal(); var local = {here: this}; g.eval("function F(arg) { this.arg = arg }"); assertDeepEq(Reflect.construct(g.F, [local]), new g.F(local));
--- a/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js +++ b/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js @@ -202,16 +202,19 @@ function seqExpr(exprs) { return Pattern({ type: "SequenceExpression", expressions: exprs }); } function newExpr(callee, args) { return Pattern({ type: "NewExpression", callee: callee, arguments: args }); } function callExpr(callee, args) { return Pattern({ type: "CallExpression", callee: callee, arguments: args }); } +function superCallExpr(args) { + return callExpr({ type: "Super" }, args); +} function arrExpr(elts) { return Pattern({ type: "ArrayExpression", elements: elts }); } function objExpr(elts) { return Pattern({ type: "ObjectExpression", properties: elts }); } function computedName(elts) { return Pattern({ type: "ComputedName", name: elts });
--- a/js/src/tests/js1_8_5/reflect-parse/classes.js +++ b/js/src/tests/js1_8_5/reflect-parse/classes.js @@ -20,39 +20,40 @@ function testClasses() { return methodMaker(methodName, args.map(ident), blockStmt(body)); } function simpleMethod(id, kind, generator, args=[], isStatic=false) { return classMethod(ident(id), methodFun(id, kind, generator, args), kind, isStatic); } - function ctorWithName(id) { + function ctorWithName(id, body = []) { return classMethod(ident("constructor"), - methodFun(id, "method", false, []), + methodFun(id, "method", false, [], body), "method", false); } function emptyCPNMethod(id, isStatic) { return classMethod(computedName(lit(id)), funExpr(null, [], blockStmt([])), "method", isStatic); } function assertClassExpr(str, methods, heritage=null, name=null) { let template = classExpr(name, heritage, methods); assertExpr("(" + str + ")", template); } + // FunctionExpression of constructor has class name as its id. // FIXME: Implement ES6 function "name" property semantics (bug 883377). let ctorPlaceholder = {}; - function assertClass(str, methods, heritage=null) { + function assertClass(str, methods, heritage=null, constructorBody=[]) { let namelessStr = str.replace("NAME", ""); let namedStr = str.replace("NAME", "Foo"); - let namedCtor = ctorWithName("Foo"); - let namelessCtor = ctorWithName(null); + let namedCtor = ctorWithName("Foo", constructorBody); + let namelessCtor = ctorWithName(null, constructorBody); let namelessMethods = methods.map(x => x == ctorPlaceholder ? namelessCtor : x); let namedMethods = methods.map(x => x == ctorPlaceholder ? namedCtor : x); assertClassExpr(namelessStr, namelessMethods, heritage); assertClassExpr(namedStr, namedMethods, heritage, ident("Foo")); let template = classStmt(ident("Foo"), heritage, namedMethods); assertStmt(namedStr, template); } @@ -432,16 +433,52 @@ function testClasses() { // Bare super is forbidden assertError("super", SyntaxError); // Even where super is otherwise allowed assertError("{ foo() { super } }", SyntaxError); assertClassError("class NAME { constructor() { super; } }", SyntaxError); + /* SuperCall */ + + // SuperCall is invalid outside derived class constructors. + assertError("super()", SyntaxError); + assertError("(function() { super(); })", SyntaxError); + + // SuperCall is invalid in generator comprehensions, even inside derived + // class constructors + assertError("(super() for (x in y))", SyntaxError); + assertClassError("class NAME { constructor() { (super() for (x in y))", SyntaxError); + + + // Even in class constructors + assertClassError("class NAME { constructor() { super(); } }", SyntaxError); + + function superConstructor(args) { + return classMethod(ident("constructor"), + methodFun("NAME", "method", false, + [], [exprStmt(superCallExpr(args))]), + "method", false); + } + + function superCallBody(args) { + return [exprStmt(superCallExpr(args))]; + } + + // SuperCall works with various argument configurations. + assertClass("class NAME extends null { constructor() { super() } }", + [ctorPlaceholder], lit(null), superCallBody([])); + assertClass("class NAME extends null { constructor() { super(1) } }", + [ctorPlaceholder], lit(null), superCallBody([lit(1)])); + assertClass("class NAME extends null { constructor() { super(1, a) } }", + [ctorPlaceholder], lit(null), superCallBody([lit(1), ident("a")])); + assertClass("class NAME extends null { constructor() { super(...[]) } }", + [ctorPlaceholder], lit(null), superCallBody([spread(arrExpr([]))])); + /* EOF */ // Clipped classes should throw a syntax error assertClassError("class NAME {", SyntaxError); assertClassError("class NAME {;", SyntaxError); assertClassError("class NAME { constructor", SyntaxError); assertClassError("class NAME { constructor(", SyntaxError); assertClassError("class NAME { constructor()", SyntaxError); assertClassError("class NAME { constructor()", SyntaxError);
--- a/js/src/tests/jstests.py +++ b/js/src/tests/jstests.py @@ -345,24 +345,25 @@ def main(): if test_count == 0: print('no tests selected') return 1 test_dir = dirname(abspath(__file__)) if options.debug: - if len(list(test_gen)) > 1: + tests = list(test_gen) + if len(tests) > 1: print('Multiple tests match command line arguments,' ' debugger can only run one') - for tc in test_gen: + for tc in tests: print(' {}'.format(tc.path)) return 2 - cmd = test_gen[0].get_command(RefTestCase.js_cmd_prefix) + cmd = tests[0].get_command(prefix) if options.show_cmd: print(list2cmdline(cmd)) with changedir(test_dir): call(cmd) return 0 with changedir(test_dir): # Force Pacific time zone to avoid failures in Date tests.
--- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1730,17 +1730,17 @@ Debugger::logTenurePromotion(JSRuntime* tenurePromotionsLogOverflowed = true; } } bool Debugger::appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame, double when) { - MOZ_ASSERT(trackingAllocationSites); + MOZ_ASSERT(trackingAllocationSites && enabled); AutoCompartment ac(cx, object); RootedObject wrappedFrame(cx, frame); if (!cx->compartment()->wrap(cx, &wrappedFrame)) return false; RootedAtom ctorName(cx); { @@ -2314,39 +2314,43 @@ Debugger::isObservedByDebuggerTrackingAl } } } return false; } /* static */ bool -Debugger::addAllocationsTracking(JSContext* cx, GlobalObject& debuggee) +Debugger::addAllocationsTracking(JSContext* cx, Handle<GlobalObject*> debuggee) { // Precondition: the given global object is being observed by at least one // Debugger that is tracking allocations. - MOZ_ASSERT(isObservedByDebuggerTrackingAllocations(debuggee)); - - if (Debugger::cannotTrackAllocations(debuggee)) { + MOZ_ASSERT(isObservedByDebuggerTrackingAllocations(*debuggee)); + + if (Debugger::cannotTrackAllocations(*debuggee)) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET); return false; } - debuggee.compartment()->setObjectMetadataCallback(SavedStacksMetadataCallback); + debuggee->compartment()->setObjectMetadataCallback(SavedStacksMetadataCallback); + debuggee->compartment()->chooseAllocationSamplingProbability(); return true; } /* static */ void Debugger::removeAllocationsTracking(GlobalObject& global) { // If there are still Debuggers that are observing allocations, we cannot - // remove the metadata callback yet. - if (isObservedByDebuggerTrackingAllocations(global)) + // remove the metadata callback yet. Recompute the sampling probability + // based on the remaining debuggers' needs. + if (isObservedByDebuggerTrackingAllocations(global)) { + global.compartment()->chooseAllocationSamplingProbability(); return; + } global.compartment()->forgetObjectMetadataCallback(); } bool Debugger::addAllocationsTrackingForAllDebuggees(JSContext* cx) { MOZ_ASSERT(trackingAllocationSites); @@ -2359,31 +2363,33 @@ Debugger::addAllocationsTrackingForAllDe for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) { if (Debugger::cannotTrackAllocations(*r.front().get())) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET); return false; } } + Rooted<GlobalObject*> g(cx); for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) { // This should always succeed, since we already checked for the // error case above. - MOZ_ALWAYS_TRUE(Debugger::addAllocationsTracking(cx, *r.front().get())); + g = r.front().get(); + MOZ_ALWAYS_TRUE(Debugger::addAllocationsTracking(cx, g)); } return true; } void Debugger::removeAllocationsTrackingForAllDebuggees() { - for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) { + for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) Debugger::removeAllocationsTracking(*r.front().get()); - } + allocationsLog.clear(); } /*** Debugger JSObjects **************************************************************************/ void @@ -3417,18 +3423,19 @@ Debugger::addDebuggeeGlobal(JSContext* c return false; } auto debuggeeZonesGuard = MakeScopeExit([&] { if (addingZoneRelation) debuggeeZones.remove(zone); }); // (5) - if (trackingAllocationSites && enabled && !Debugger::addAllocationsTracking(cx, *global)) - return false; + if (trackingAllocationSites && enabled && !Debugger::addAllocationsTracking(cx, global)) + return false; + auto allocationsTrackingGuard = MakeScopeExit([&] { if (trackingAllocationSites && enabled) Debugger::removeAllocationsTracking(*global); }); // (6) debuggeeCompartment->setIsDebuggee(); debuggeeCompartment->updateDebuggerObservesAsmJS();
--- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -391,17 +391,17 @@ class Debugger : private mozilla::Linked */ static bool isObservedByDebuggerTrackingAllocations(const GlobalObject& global); /* * Add allocations tracking for objects allocated within the given * debuggee's compartment. The given debuggee global must be observed by at * least one Debugger that is enabled and tracking allocations. */ - static bool addAllocationsTracking(JSContext* cx, GlobalObject& debuggee); + static bool addAllocationsTracking(JSContext* cx, Handle<GlobalObject*> debuggee); /* * Remove allocations tracking for objects allocated within the given * global's compartment. This is a no-op if there are still Debuggers * observing this global and who are tracking allocations. */ static void removeAllocationsTracking(GlobalObject& global);
--- a/js/src/vm/DebuggerMemory.cpp +++ b/js/src/vm/DebuggerMemory.cpp @@ -309,17 +309,28 @@ DebuggerMemory::setAllocationSamplingPro if (probability < 0.0 || probability > 1.0) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, "(set allocationSamplingProbability)'s parameter", "not a number between 0 and 1"); return false; } - memory->getDebugger()->allocationSamplingProbability = probability; + Debugger* dbg = memory->getDebugger(); + if (dbg->allocationSamplingProbability != probability) { + dbg->allocationSamplingProbability = probability; + + // If this is a change any debuggees would observe, have all debuggee + // compartments recompute their sampling probabilities. + if (dbg->enabled && dbg->trackingAllocationSites) { + for (auto r = dbg->debuggees.all(); !r.empty(); r.popFront()) + r.front()->compartment()->chooseAllocationSamplingProbability(); + } + } + args.rval().setUndefined(); return true; } /* static */ bool DebuggerMemory::getAllocationsLogOverflowed(JSContext* cx, unsigned argc, Value* vp) { THIS_DEBUGGER_MEMORY(cx, argc, vp, "(get allocationsLogOverflowed)", args, memory);
--- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -85,19 +85,16 @@ js::ProtoKeyToClass(JSProtoKey key) TypedObjectModuleObject& js::GlobalObject::getTypedObjectModule() const { Value v = getConstructor(JSProto_TypedObject); // only gets called from contexts where TypedObject must be initialized MOZ_ASSERT(v.isObject()); return v.toObject().as<TypedObjectModuleObject>(); } - - - /* static */ bool GlobalObject::ensureConstructor(JSContext* cx, Handle<GlobalObject*> global, JSProtoKey key) { if (global->isStandardClassResolved(key)) return true; return resolveConstructor(cx, global, key); }
--- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -342,21 +342,27 @@ js::ValueToCallable(JSContext* cx, Handl bool RunState::maybeCreateThisForConstructor(JSContext* cx) { if (isInvoke()) { InvokeState& invoke = *asInvoke(); if (invoke.constructing() && invoke.args().thisv().isPrimitive()) { RootedObject callee(cx, &invoke.args().callee()); - NewObjectKind newKind = invoke.createSingleton() ? SingletonObject : GenericObject; - JSObject* obj = CreateThisForFunction(cx, callee, newKind); - if (!obj) - return false; - invoke.args().setThis(ObjectValue(*obj)); + if (script()->isDerivedClassConstructor()) { + MOZ_ASSERT(callee->as<JSFunction>().isClassConstructor()); + invoke.args().setThis(MagicValue(JS_UNINITIALIZED_LEXICAL)); + } else { + RootedObject newTarget(cx, &invoke.args().newTarget().toObject()); + NewObjectKind newKind = invoke.createSingleton() ? SingletonObject : GenericObject; + JSObject* obj = CreateThisForFunction(cx, callee, newTarget, newKind); + if (!obj) + return false; + invoke.args().setThis(ObjectValue(*obj)); + } } } return true; } static MOZ_NEVER_INLINE bool Interpret(JSContext* cx, RunState& state); @@ -864,19 +870,19 @@ static bool StackCheckIsConstructorCalleeNewTarget(JSContext* cx, HandleValue callee, HandleValue newTarget) { // Calls from the stack could have any old non-constructor callee. if (!IsConstructor(callee)) { ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_SEARCH_STACK, callee, nullptr); return false; } - // The new.target for a stack construction attempt is just the callee: no - // need to check that it's a constructor. - MOZ_ASSERT(&callee.toObject() == &newTarget.toObject()); + // The new.target has already been vetted by previous calls, or is the callee. + // We can just assert that it's a constructor. + MOZ_ASSERT(IsConstructor(newTarget)); return true; } static bool ConstructFromStack(JSContext* cx, const CallArgs& args) { if (!StackCheckIsConstructorCalleeNewTarget(cx, args.calleev(), args.newTarget())) @@ -1740,16 +1746,44 @@ SetObjectElementOperation(JSContext* cx, RootedValue tmp(cx, value); ObjectOpResult result; return SetProperty(cx, obj, id, tmp, receiver, result) && result.checkStrictErrorOrWarning(cx, obj, id, strict); } /* + * Get the innermost enclosing function that has a 'this' binding. + * + * Implements ES6 12.3.5.2 GetSuperConstructor() steps 1-3, including + * the loop in ES6 8.3.2 GetThisEnvironment(). Our implementation of + * ES6 12.3.5.3 MakeSuperPropertyReference() also uses this code. + */ +static JSFunction& +GetSuperEnvFunction(JSContext *cx, InterpreterRegs& regs) +{ + ScopeIter si(cx, regs.fp()->scopeChain(), regs.fp()->script()->innermostStaticScope(regs.pc)); + for (; !si.done(); ++si) { + if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) { + JSFunction& callee = si.scope().as<CallObject>().callee(); + + // Arrow functions don't have the information we're looking for, + // their enclosing scopes do. Nevertheless, they might have call + // objects. Skip them to find what we came for. + if (callee.isArrow()) + continue; + + return callee; + } + } + MOZ_CRASH("unexpected scope chain for GetSuperEnvFunction"); +} + + +/* * As an optimization, the interpreter creates a handful of reserved Rooted<T> * variables at the beginning, thus inserting them into the Rooted list once * upon entry. ReservedRooted "borrows" a reserved Rooted variable and uses it * within a local scope, resetting the value to nullptr (or the appropriate * equivalent for T) at scope end. This avoids inserting/removing the Rooted * from the rooter list, while preventing stale values from being kept alive * unnecessarily. */ @@ -2048,20 +2082,16 @@ CASE(EnableInterruptsPseudoOpcode) DISPATCH_TO(op); } /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_UNUSED2) CASE(JSOP_UNUSED14) CASE(JSOP_BACKPATCH) -CASE(JSOP_UNUSED163) -CASE(JSOP_UNUSED164) -CASE(JSOP_UNUSED165) -CASE(JSOP_UNUSED166) CASE(JSOP_UNUSED167) CASE(JSOP_UNUSED168) CASE(JSOP_UNUSED169) CASE(JSOP_UNUSED170) CASE(JSOP_UNUSED171) CASE(JSOP_UNUSED172) CASE(JSOP_UNUSED173) CASE(JSOP_UNUSED174) @@ -2192,18 +2222,22 @@ CASE(JSOP_RETURN) CASE(JSOP_RETRVAL) { /* * When the inlined frame exits with an exception or an error, ok will be * false after the inline_return label. */ CHECK_BRANCH(); + if (!REGS.fp()->checkReturn(cx)) + goto error; + successful_return_continuation: interpReturnOK = true; + return_continuation: if (activation.entryFrame() != REGS.fp()) { // Stop the engine. (No details about which engine exactly, could be // interpreter, Baseline or IonMonkey.) TraceLogStopEvent(logger, TraceLogger_Engine); TraceLogStopEvent(logger, TraceLogger_Scripts); interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), interpReturnOK); @@ -2770,16 +2804,18 @@ END_CASE(JSOP_TYPEOF) CASE(JSOP_VOID) REGS.sp[-1].setUndefined(); END_CASE(JSOP_VOID) CASE(JSOP_THIS) if (!ComputeThis(cx, REGS.fp())) goto error; + if (!REGS.fp()->checkThis(cx)) + goto error; PUSH_COPY(REGS.fp()->thisValue()); END_CASE(JSOP_THIS) CASE(JSOP_GETPROP) CASE(JSOP_LENGTH) CASE(JSOP_CALLPROP) { MutableHandleValue lval = REGS.stackHandleAt(-1); @@ -2989,26 +3025,27 @@ CASE(JSOP_STRICTEVAL) } REGS.sp = args.spAfterCall(); TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]); } END_CASE(JSOP_EVAL) CASE(JSOP_SPREADNEW) CASE(JSOP_SPREADCALL) +CASE(JSOP_SPREADSUPERCALL) if (REGS.fp()->hasPushedSPSFrame()) cx->runtime()->spsProfiler.updatePC(script, REGS.pc); /* FALL THROUGH */ CASE(JSOP_SPREADEVAL) CASE(JSOP_STRICTSPREADEVAL) { static_assert(JSOP_SPREADEVAL_LENGTH == JSOP_STRICTSPREADEVAL_LENGTH, "spreadeval and strictspreadeval must be the same size"); - bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW; + bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW || JSOp(*REGS.pc) == JSOP_SPREADSUPERCALL;; MOZ_ASSERT(REGS.stackDepth() >= 3u + construct); HandleValue callee = REGS.stackHandleAt(-3 - construct); HandleValue thisv = REGS.stackHandleAt(-2 - construct); HandleValue arr = REGS.stackHandleAt(-1 - construct); MutableHandleValue ret = REGS.stackHandleAt(-3 - construct); @@ -3030,22 +3067,23 @@ CASE(JSOP_FUNAPPLY) CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp); if (!GuardFunApplyArgumentsOptimization(cx, REGS.fp(), args)) goto error; /* FALL THROUGH */ } CASE(JSOP_NEW) CASE(JSOP_CALL) +CASE(JSOP_SUPERCALL) CASE(JSOP_FUNCALL) { if (REGS.fp()->hasPushedSPSFrame()) cx->runtime()->spsProfiler.updatePC(script, REGS.pc); - bool construct = (*REGS.pc == JSOP_NEW); + bool construct = (*REGS.pc == JSOP_NEW || *REGS.pc == JSOP_SUPERCALL); unsigned argStackSlots = GET_ARGC(REGS.pc) + construct; MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc)); CallArgs args = CallArgsFromSp(argStackSlots, REGS.sp, construct); JSFunction* maybeFun; bool isFunction = IsFunctionObject(args.calleev(), &maybeFun); @@ -4058,55 +4096,82 @@ CASE(JSOP_INITHOMEOBJECT) MOZ_ASSERT(obj->is<PlainObject>() || obj->is<UnboxedPlainObject>() || obj->is<JSFunction>()); func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj)); } END_CASE(JSOP_INITHOMEOBJECT) CASE(JSOP_SUPERBASE) { - ScopeIter si(cx, REGS.fp()->scopeChain(), REGS.fp()->script()->innermostStaticScope(REGS.pc)); - for (; !si.done(); ++si) { - if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) { - JSFunction& callee = si.scope().as<CallObject>().callee(); - - // Arrow functions don't have the information we're looking for, - // their enclosing scopes do. Nevertheless, they might have call - // objects. Skip them to find what we came for. - if (callee.isArrow()) - continue; - - MOZ_ASSERT(callee.allowSuperProperty()); - MOZ_ASSERT(callee.nonLazyScript()->needsHomeObject()); - const Value& homeObjVal = callee.getExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT); - - ReservedRooted<JSObject*> homeObj(&rootObject0, &homeObjVal.toObject()); - ReservedRooted<JSObject*> superBase(&rootObject1); - if (!GetPrototype(cx, homeObj, &superBase)) - goto error; - - if (!superBase) { - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, - "null", "object"); - goto error; - } - PUSH_OBJECT(*superBase); - break; - } + JSFunction& superEnvFunc = GetSuperEnvFunction(cx, REGS); + MOZ_ASSERT(superEnvFunc.allowSuperProperty()); + MOZ_ASSERT(superEnvFunc.nonLazyScript()->needsHomeObject()); + const Value& homeObjVal = superEnvFunc.getExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT); + + ReservedRooted<JSObject*> homeObj(&rootObject0, &homeObjVal.toObject()); + ReservedRooted<JSObject*> superBase(&rootObject1); + if (!GetPrototype(cx, homeObj, &superBase)) + goto error; + + if (!superBase) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, + "null", "object"); + goto error; } - if (si.done()) - MOZ_CRASH("Unexpected scope chain in superbase"); + PUSH_OBJECT(*superBase); } END_CASE(JSOP_SUPERBASE) CASE(JSOP_NEWTARGET) PUSH_COPY(REGS.fp()->newTarget()); MOZ_ASSERT(REGS.sp[-1].isObject() || REGS.sp[-1].isUndefined()); END_CASE(JSOP_NEWTARGET) +CASE(JSOP_SUPERFUN) +{ + ReservedRooted<JSObject*> superEnvFunc(&rootObject0, &GetSuperEnvFunction(cx, REGS)); + MOZ_ASSERT(superEnvFunc->as<JSFunction>().isClassConstructor()); + MOZ_ASSERT(superEnvFunc->as<JSFunction>().nonLazyScript()->isDerivedClassConstructor()); + + ReservedRooted<JSObject*> superFun(&rootObject1); + + if (!GetPrototype(cx, superEnvFunc, &superFun)) + goto error; + + ReservedRooted<Value> superFunVal(&rootValue0, UndefinedValue()); + if (!superFun) + superFunVal = NullValue(); + else if (!superFun->isConstructor()) + superFunVal = ObjectValue(*superFun); + + if (superFunVal.isObjectOrNull()) { + ReportIsNotFunction(cx, superFunVal, JSDVG_IGNORE_STACK, CONSTRUCT); + goto error; + } + + PUSH_OBJECT(*superFun); +} +END_CASE(JSOP_SUPERFUN) + +CASE(JSOP_SETTHIS) +{ + MOZ_ASSERT(REGS.fp()->isNonEvalFunctionFrame()); + MOZ_ASSERT(REGS.fp()->script()->isDerivedClassConstructor()); + MOZ_ASSERT(REGS.fp()->callee().isClassConstructor()); + + if (!REGS.fp()->thisValue().isMagic(JS_UNINITIALIZED_LEXICAL)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_REINIT_THIS); + goto error; + } + + ReservedRooted<JSObject*> thisv(&rootObject0, ®S.sp[-1].toObject()); + REGS.fp()->setDerivedConstructorThis(thisv); +} +END_CASE(JSOP_SETTHIS) + DEFAULT() { char numBuf[12]; JS_snprintf(numBuf, sizeof numBuf, "%d", *REGS.pc); JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_BYTECODE, numBuf); goto error; } @@ -4697,17 +4762,17 @@ js::InitGetterSetterOperation(JSContext* bool js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue thisv, HandleValue callee, HandleValue arr, HandleValue newTarget, MutableHandleValue res) { RootedArrayObject aobj(cx, &arr.toObject().as<ArrayObject>()); uint32_t length = aobj->length(); JSOp op = JSOp(*pc); - bool constructing = op == JSOP_SPREADNEW; + bool constructing = op == JSOP_SPREADNEW || op == JSOP_SPREADSUPERCALL; if (length > ARGS_LENGTH_MAX) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, constructing ? JSMSG_TOO_MANY_CON_SPREADARGS : JSMSG_TOO_MANY_FUN_SPREADARGS); return false; } @@ -4715,17 +4780,17 @@ js::SpreadCallOperation(JSContext* cx, H // The object must be an array with dense elements and no holes. Baseline's // optimized spread call stubs rely on this. MOZ_ASSERT(aobj->getDenseInitializedLength() == length); MOZ_ASSERT(!aobj->isIndexed()); for (uint32_t i = 0; i < length; i++) MOZ_ASSERT(!aobj->getDenseElement(i).isMagic()); #endif - if (op == JSOP_SPREADNEW) { + if (constructing) { if (!StackCheckIsConstructorCalleeNewTarget(cx, callee, newTarget)) return false; ConstructArgs cargs(cx); if (!cargs.init(length)) return false; if (!GetElements(cx, aobj, length, cargs.array())) @@ -4975,8 +5040,28 @@ js::ReportRuntimeRedeclaration(JSContext if (declKind == frontend::Definition::VAR) kindStr = "non-configurable global property"; else kindStr = frontend::Definition::kindString(declKind); JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_REDECLARED_VAR, kindStr, printable.ptr()); } } + +bool +js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame) +{ + RootedFunction fun(cx, frame.callee()); + + MOZ_ASSERT(fun->isClassConstructor()); + MOZ_ASSERT(fun->nonLazyScript()->isDerivedClassConstructor()); + + const char* name = "anonymous"; + JSAutoByteString str; + if (fun->atom()) { + if (!AtomToPrintableString(cx, fun->atom(), &str)) + return false; + name = str.ptr(); + } + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_THIS, name); + return false; +}
--- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -480,12 +480,14 @@ void ReportUninitializedLexical(JSContext* cx, HandleScript script, jsbytecode* pc); // The parser only reports redeclarations that occurs within a single // script. Due to the extensibility of the global lexical scope, we also check // for redeclarations during runtime in JSOP_DEF{VAR,LET,CONST}. void ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, frontend::Definition::Kind declKind); +bool +ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame); } /* namespace js */ #endif /* vm_Interpreter_h */
--- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -460,54 +460,58 @@ ObjectGroupCompartment::newTablePostBarr } } /* static */ ObjectGroup* ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp, TaggedProto proto, JSObject* associated) { MOZ_ASSERT_IF(associated, proto.isObject()); - MOZ_ASSERT_IF(associated, associated->is<JSFunction>() || associated->is<TypeDescr>()); MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); // A null lookup clasp is used for 'new' groups with an associated // function. The group starts out as a plain object but might mutate into an // unboxed plain object. - MOZ_ASSERT(!clasp == (associated && associated->is<JSFunction>())); + MOZ_ASSERT_IF(!clasp, !!associated); AutoEnterAnalysis enter(cx); ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.defaultNewTable; if (!table) { table = cx->new_<ObjectGroupCompartment::NewTable>(); if (!table || !table->init()) { js_delete(table); table = nullptr; ReportOutOfMemory(cx); return nullptr; } } - if (associated && associated->is<JSFunction>()) { + if (associated && !associated->is<TypeDescr>()) { MOZ_ASSERT(!clasp); + if (associated->is<JSFunction>()) { - // Canonicalize new functions to use the original one associated with its script. - JSFunction* fun = &associated->as<JSFunction>(); - if (fun->hasScript()) - associated = fun->nonLazyScript()->functionNonDelazifying(); - else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin()) - associated = fun->lazyScript()->functionNonDelazifying(); - else + // Canonicalize new functions to use the original one associated with its script. + JSFunction* fun = &associated->as<JSFunction>(); + if (fun->hasScript()) + associated = fun->nonLazyScript()->functionNonDelazifying(); + else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin()) + associated = fun->lazyScript()->functionNonDelazifying(); + else + associated = nullptr; + + // If we have previously cleared the 'new' script information for this + // function, don't try to construct another one. + if (associated && associated->wasNewScriptCleared()) + associated = nullptr; + + } else { associated = nullptr; - - // If we have previously cleared the 'new' script information for this - // function, don't try to construct another one. - if (associated && associated->wasNewScriptCleared()) - associated = nullptr; + } if (!associated) clasp = &PlainObject::class_; } if (proto.isObject() && !proto.toObject()->isDelegate()) { RootedObject protoObj(cx, proto.toObject()); if (!protoObj->setDelegate(cx))
--- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1664,20 +1664,55 @@ 1234567890123456789012345678901234567890 * if a var binding with the same name exists on the global. * Category: Variables and Scopes * Type: Variables * Operands: uint32_t nameIndex * Stack: => */ \ macro(JSOP_DEFLET, 162,"deflet", NULL, 5, 0, 0, JOF_ATOM) \ \ - macro(JSOP_UNUSED163, 163,"unused163", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED164, 164,"unused164", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED165, 165,"unused165", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED166, 166,"unused166", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Bind the |this| value of a function to the supplied value. + * + * Category: Variables and Scopes + * Type: This + * Operands: + * Stack: this => this + */ \ + macro(JSOP_SETTHIS , 163,"setthis", NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Find the function to invoke with |super()| on the scope chain. + * + * Category: Variables and Scopes + * Type: Super + * Operands: + * Stack: => superFun + */ \ + macro(JSOP_SUPERFUN, 164,"superfun", NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Behaves exactly like JSOP_NEW, but allows JITs to distinguish the two cases. + * + * Category: Statements + * Type: Function + * Operands: uint16_t argc + * Stack: callee, this, args[0], ..., args[argc-1], newTarget => rval + * nuses: (argc+3) + */ \ + macro(JSOP_SUPERCALL, 165,"supercall", NULL, 3, -1, 1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET) \ + /* + * spreadcall variant of JSOP_SUPERCALL. + * + * Behaves exactly like JSOP_SPREADNEW. + * + * Category: Statements + * Type: Function + * Operands: + * Stack: callee, this, args, newTarget => rval + */ \ + macro(JSOP_SPREADSUPERCALL, 166, "spreadsupercall", NULL, 1, 4, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \ macro(JSOP_UNUSED167, 167,"unused167", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED168, 168,"unused168", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED169, 169,"unused169", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED170, 170,"unused170", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED171, 171,"unused171", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED172, 172,"unused172", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED173, 173,"unused173", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED174, 174,"unused174", NULL, 1, 0, 0, JOF_BYTE) \
--- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -1,16 +1,17 @@ /* -*- 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 "vm/SavedStacks.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" #include "mozilla/DebugOnly.h" #include "mozilla/Move.h" #include <algorithm> #include <math.h> #include "jsapi.h" @@ -899,16 +900,20 @@ SavedFrame::toStringMethod(JSContext* cx return false; args.rval().setString(string); return true; } bool SavedStacks::init() { + uint64_t seed[2]; + random_generateSeed(seed, mozilla::ArrayLength(seed)); + bernoulli.setRandomState(seed[0], seed[1]); + if (!pcLocationMap.init()) return false; return frames.init(); } bool SavedStacks::saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame, unsigned maxFrameCount) @@ -1321,80 +1326,55 @@ SavedStacks::getLocation(JSContext* cx, } } locationp.set(p->value()); return true; } void -SavedStacks::chooseSamplingProbability(JSContext* cx) +SavedStacks::chooseSamplingProbability(JSCompartment* compartment) { - GlobalObject::DebuggerVector* dbgs = cx->global()->getDebuggers(); + GlobalObject* global = compartment->maybeGlobal(); + if (!global) + return; + + GlobalObject::DebuggerVector* dbgs = global->getDebuggers(); if (!dbgs || dbgs->empty()) return; mozilla::DebugOnly<Debugger**> begin = dbgs->begin(); mozilla::DebugOnly<bool> foundAnyDebuggers = false; - allocationSamplingProbability = 0; + double probability = 0; for (Debugger** dbgp = dbgs->begin(); dbgp < dbgs->end(); dbgp++) { // The set of debuggers had better not change while we're iterating, // such that the vector gets reallocated. MOZ_ASSERT(dbgs->begin() == begin); if ((*dbgp)->trackingAllocationSites && (*dbgp)->enabled) { foundAnyDebuggers = true; - allocationSamplingProbability = std::max((*dbgp)->allocationSamplingProbability, - allocationSamplingProbability); + probability = std::max((*dbgp)->allocationSamplingProbability, + probability); } } MOZ_ASSERT(foundAnyDebuggers); + + bernoulli.setProbability(probability); } JSObject* SavedStacksMetadataCallback(JSContext* cx, JSObject* target) { RootedObject obj(cx, target); SavedStacks& stacks = cx->compartment()->savedStacks(); - if (stacks.allocationSkipCount > 0) { - stacks.allocationSkipCount--; - return nullptr; - } - - stacks.chooseSamplingProbability(cx); - if (stacks.allocationSamplingProbability == 0.0) + if (!stacks.bernoulli.trial()) return nullptr; - // If the sampling probability is set to 1.0, we are always taking a sample - // and can therefore leave allocationSkipCount at 0. - if (stacks.allocationSamplingProbability != 1.0) { - // Rather than generating a random number on every allocation to decide - // if we want to sample that particular allocation (which would be - // expensive), we calculate the number of allocations to skip before - // taking the next sample. - // - // P = the probability we sample any given event. - // - // ~P = 1-P, the probability we don't sample a given event. - // - // (~P)^n = the probability that we skip at least the next n events. - // - // let X = random between 0 and 1. - // - // floor(log base ~P of X) = n, aka the number of events we should skip - // until we take the next sample. Any value for X less than (~P)^n - // yields a skip count greater than n, so the likelihood of a skip count - // greater than n is (~P)^n, as required. - double notSamplingProb = 1.0 - stacks.allocationSamplingProbability; - stacks.allocationSkipCount = std::floor(std::log(random_nextDouble(&stacks.rngState)) / - std::log(notSamplingProb)); - } - AutoEnterOOMUnsafeRegion oomUnsafe; RootedSavedFrame frame(cx); if (!stacks.saveCurrentStack(cx, &frame)) oomUnsafe.crash("SavedStacksMetadataCallback"); if (!Debugger::onLogAllocationSite(cx, obj, frame, JS_GetCurrentEmbedderTime())) oomUnsafe.crash("SavedStacksMetadataCallback");
--- a/js/src/vm/SavedStacks.h +++ b/js/src/vm/SavedStacks.h @@ -2,16 +2,18 @@ * 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/. */ #ifndef vm_SavedStacks_h #define vm_SavedStacks_h +#include "mozilla/FastBernoulliTrial.h" + #include "jscntxt.h" #include "jsmath.h" #include "jswrapper.h" #include "js/HashTable.h" #include "vm/SavedFrame.h" #include "vm/Stack.h" namespace js { @@ -149,39 +151,36 @@ class SavedStacks { friend JSObject* SavedStacksMetadataCallback(JSContext* cx, JSObject* target); friend bool JS::ubi::ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFrame& ubiFrame, MutableHandleObject outSavedFrameStack); public: SavedStacks() : frames(), - allocationSamplingProbability(1.0), - allocationSkipCount(0), - rngState(0), + bernoulli(1.0, 0x59fdad7f6b4cc573, 0x91adf38db96a9354), creatingSavedFrame(false) { } bool init(); bool initialized() const { return frames.initialized(); } bool saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame, unsigned maxFrameCount = 0); void sweep(JSRuntime* rt); void trace(JSTracer* trc); uint32_t count(); void clear(); - void setRNGState(uint64_t state) { rngState = state; } + void setRNGState(uint64_t state0, uint64_t state1) { bernoulli.setRandomState(state0, state1); } + void chooseSamplingProbability(JSCompartment*); size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); private: SavedFrame::Set frames; - double allocationSamplingProbability; - uint32_t allocationSkipCount; - uint64_t rngState; - bool creatingSavedFrame; + mozilla::FastBernoulliTrial bernoulli; + bool creatingSavedFrame; // Similar to mozilla::ReentrancyGuard, but instead of asserting against // reentrancy, just change the behavior of SavedStacks::saveCurrentStack to // return a nullptr SavedFrame. struct MOZ_RAII AutoReentrancyGuard { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; SavedStacks& stacks; @@ -201,17 +200,16 @@ class SavedStacks { bool insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFrame frame, unsigned maxFrameCount = 0); bool adoptAsyncStack(JSContext* cx, HandleSavedFrame asyncStack, HandleString asyncCause, MutableHandleSavedFrame adoptedStack, unsigned maxFrameCount); SavedFrame* getOrCreateSavedFrame(JSContext* cx, SavedFrame::HandleLookup lookup); SavedFrame* createFrameFromLookup(JSContext* cx, SavedFrame::HandleLookup lookup); - void chooseSamplingProbability(JSContext* cx); // Cache for memoizing PCToLineNumber lookups. struct PCKey { PCKey(JSScript* script, jsbytecode* pc) : script(script), pc(pc) { } PreBarrieredScript script; jsbytecode* pc;
--- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -253,23 +253,29 @@ InterpreterFrame::prologue(JSContext* cx pushOnScopeChain(*scope); return probes::EnterScript(cx, script, nullptr, this); } MOZ_ASSERT(isNonEvalFunctionFrame()); if (fun()->needsCallObject() && !initFunctionScopeObjects(cx)) return false; - if (isConstructing() && functionThis().isPrimitive()) { - RootedObject callee(cx, &this->callee()); - JSObject* obj = CreateThisForFunction(cx, callee, - createSingleton() ? SingletonObject : GenericObject); - if (!obj) - return false; - functionThis() = ObjectValue(*obj); + if (isConstructing()) { + if (script->isDerivedClassConstructor()) { + MOZ_ASSERT(callee().isClassConstructor()); + functionThis() = MagicValue(JS_UNINITIALIZED_LEXICAL); + } else if (functionThis().isPrimitive()) { + RootedObject callee(cx, &this->callee()); + RootedObject newTarget(cx, &this->newTarget().toObject()); + JSObject* obj = CreateThisForFunction(cx, callee, newTarget, + createSingleton() ? SingletonObject : GenericObject); + if (!obj) + return false; + functionThis() = ObjectValue(*obj); + } } return probes::EnterScript(cx, script, script->functionNonDelazifying(), this); } void InterpreterFrame::epilogue(JSContext* cx) { @@ -312,16 +318,53 @@ InterpreterFrame::epilogue(JSContext* cx if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugScopes::onPopCall(this, cx); if (!fun()->isGenerator() && isConstructing() && thisValue().isObject() && returnValue().isPrimitive()) setReturnValue(ObjectValue(constructorThis())); } bool +InterpreterFrame::checkThis(JSContext* cx) +{ + if (script()->isDerivedClassConstructor()) { + MOZ_ASSERT(isNonEvalFunctionFrame()); + MOZ_ASSERT(fun()->isClassConstructor()); + + if (thisValue().isMagic(JS_UNINITIALIZED_LEXICAL)) { + RootedFunction func(cx, fun()); + return ThrowUninitializedThis(cx, this); + } + } + return true; +} + +bool +InterpreterFrame::checkReturn(JSContext* cx) +{ + if (script()->isDerivedClassConstructor()) { + MOZ_ASSERT(isNonEvalFunctionFrame()); + MOZ_ASSERT(callee().isClassConstructor()); + + HandleValue retVal = returnValue(); + if (retVal.isObject()) + return true; + + if (!retVal.isUndefined()) { + ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, retVal, nullptr); + return false; + } + + if (!checkThis(cx)) + return false; + } + return true; +} + +bool InterpreterFrame::pushBlock(JSContext* cx, StaticBlockObject& block) { MOZ_ASSERT(block.needsClone()); Rooted<StaticBlockObject*> blockHandle(cx, &block); ClonedBlockObject* clone = ClonedBlockObject::create(cx, blockHandle, this); if (!clone) return false;
--- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -447,16 +447,19 @@ class InterpreterFrame * over-recursed) after pushing the stack frame but before 'prologue' is * called or completes fully. To simplify usage, 'epilogue' does not assume * 'prologue' has completed and handles all the intermediate state details. */ bool prologue(JSContext* cx); void epilogue(JSContext* cx); + bool checkReturn(JSContext* cx); + bool checkThis(JSContext* cx); + bool initFunctionScopeObjects(JSContext* cx); /* * Initialize local variables of newly-pushed frame. 'var' bindings are * initialized to undefined and lexical bindings are initialized to * JS_UNINITIALIZED_LEXICAL. */ void initLocals(); @@ -733,16 +736,24 @@ class InterpreterFrame } Value& thisValue() const { if (flags_ & (EVAL | GLOBAL)) return ((Value*)this)[-1]; return argv()[-1]; } + void setDerivedConstructorThis(HandleObject thisv) { + MOZ_ASSERT(isNonEvalFunctionFrame()); + MOZ_ASSERT(script()->isDerivedClassConstructor()); + MOZ_ASSERT(callee().isClassConstructor()); + MOZ_ASSERT(thisValue().isMagic(JS_UNINITIALIZED_LEXICAL)); + argv()[-1] = ObjectValue(*thisv); + } + /* * Callee * * Only function frames have a callee. An eval frame in a function has the * same callee as its containing function frame. maybeCalleev can be used * to return a value that is either the callee object (for function frames) or * null (for global frames). */
--- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -24,21 +24,21 @@ namespace js { * versions. If deserialization fails, the data should be invalidated if * possible. * * When you change this, run make_opcode_doc.py and copy the new output into * this wiki page: * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 310; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 312; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 415, +static_assert(JSErr_Limit == 419, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's " "expected JSErr_Limit value."); class XDRBuffer { public: explicit XDRBuffer(JSContext* cx)
--- a/js/xpconnect/idl/xpccomponents.idl +++ b/js/xpconnect/idl/xpccomponents.idl @@ -118,17 +118,17 @@ interface nsIXPCComponents_utils_Sandbox interface ScheduledGCCallback : nsISupports { void callback(); }; /** * interface of Components.utils */ -[scriptable, uuid(e04a4a58-2b5e-4a74-941a-0d344b057c5a)] +[scriptable, uuid(eaeab2c0-ada3-4c22-b36d-70320dd923ba)] interface nsIXPCComponents_Utils : nsISupports { /* reportError is designed to be called from JavaScript only. * * It will report a JS Error object to the JS console, and return. It * is meant for use in exception handler blocks which want to "eat" * an exception, but still want to report it to the console. @@ -484,26 +484,16 @@ interface nsIXPCComponents_Utils : nsISu * For gecko internal automation use only. Calling this in production code * would result in security vulnerabilities, so it will crash if used outside * of automation. */ [implicit_jscontext] void forcePermissiveCOWs(); /* - * Disables the XPConnect security checks that deny access to callables and - * accessor descriptors on COWs. Do not use this unless you are bholley. - * - * For the benefit of his magesty TCPSocket while he takes his sweet time - * converting to WebIDL. See bug 885982. - */ - [implicit_jscontext] - void skipCOWCallableChecks(); - - /* * Forces the usage of a privileged |Components| object for a potentially- * unprivileged scope. This will crash if used outside of automation. */ [implicit_jscontext] void forcePrivilegedComponentsForScope(in jsval vscope); /* * This seemingly-paradoxical API allows privileged code to explicitly give
--- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -2945,23 +2945,16 @@ NS_IMETHODIMP nsXPCComponents_Utils::ForcePermissiveCOWs(JSContext* cx) { CrashIfNotInAutomation(); CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->forcePermissiveCOWs = true; return NS_OK; } NS_IMETHODIMP -nsXPCComponents_Utils::SkipCOWCallableChecks(JSContext* cx) -{ - CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->skipCOWCallableChecks = true; - return NS_OK; -} - -NS_IMETHODIMP nsXPCComponents_Utils::ForcePrivilegedComponentsForScope(HandleValue vscope, JSContext* cx) { if (!vscope.isObject()) return NS_ERROR_INVALID_ARG; CrashIfNotInAutomation(); JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject()); XPCWrappedNativeScope* scope = ObjectScope(scopeObj);
--- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -3677,17 +3677,16 @@ public: explicit CompartmentPrivate(JSCompartment* c) : wantXrays(false) , allowWaivers(true) , writeToGlobalPrototype(false) , skipWriteToGlobalPrototype(false) , universalXPConnectEnabled(false) , forcePermissiveCOWs(false) - , skipCOWCallableChecks(false) , scriptability(c) , scope(nullptr) { MOZ_COUNT_CTOR(xpc::CompartmentPrivate); mozilla::PodArrayZero(wrapperDenialWarnings); } ~CompartmentPrivate(); @@ -3738,20 +3737,16 @@ public: // This is only ever set during mochitest runs when enablePrivilege is called. // It allows the SpecialPowers scope to waive the normal chrome security // wrappers and expose properties directly to content. This lets us avoid a // bunch of overhead and complexity in our SpecialPowers automation glue. // // Using it in production is inherently unsafe. bool forcePermissiveCOWs; - // Disables the XPConnect security checks that deny access to callables and - // accessor descriptors on COWs. Do not use this unless you are bholley. - bool skipCOWCallableChecks; - // Whether we've emitted a warning about a property that was filtered out // by a security wrapper. See XrayWrapper.cpp. bool wrapperDenialWarnings[WrapperDenialTypeCount]; // The scriptability of this compartment. Scriptability scriptability; // Our XPCWrappedNativeScope. This is non-null if and only if this is an
--- a/js/xpconnect/wrappers/AccessCheck.cpp +++ b/js/xpconnect/wrappers/AccessCheck.cpp @@ -407,28 +407,27 @@ ExposedPropertiesOnly::check(JSContext* } if ((act == Wrapper::SET && !(access & WRITE)) || (act != Wrapper::SET && !(access & READ))) { return false; } // Inspect the property on the underlying object to check for red flags. - bool skipCallableChecks = CompartmentPrivate::Get(wrappedObject)->skipCOWCallableChecks; if (!JS_GetPropertyDescriptorById(cx, wrappedObject, id, &desc)) return false; // Reject accessor properties. - if (!skipCallableChecks && desc.hasGetterOrSetter()) { + if (desc.hasGetterOrSetter()) { EnterAndThrow(cx, wrapper, "Exposing privileged accessor properties is prohibited"); return false; } // Reject privileged or cross-origin callables. - if (!skipCallableChecks && desc.value().isObject()) { + if (desc.value().isObject()) { RootedObject maybeCallable(cx, js::UncheckedUnwrap(&desc.value().toObject())); if (JS::IsCallable(maybeCallable) && !AccessCheck::subsumes(wrapper, maybeCallable)) { EnterAndThrow(cx, wrapper, "Exposing privileged or cross-origin callable is prohibited"); return false; } } return true;
--- a/layout/base/AccessibleCaretManager.cpp +++ b/layout/base/AccessibleCaretManager.cpp @@ -448,33 +448,41 @@ AccessibleCaretManager::SelectWordOrShor return rv; } void AccessibleCaretManager::OnScrollStart() { AC_LOG("%s", __FUNCTION__); + if (GetCaretMode() == CaretMode::Cursor) { + mFirstCaretAppearanceOnScrollStart = mFirstCaret->GetAppearance(); + } + HideCarets(); } void AccessibleCaretManager::OnScrollEnd() { if (mLastUpdateCaretMode != GetCaretMode()) { return; } if (GetCaretMode() == CaretMode::Cursor) { - AC_LOG("%s: HideCarets()", __FUNCTION__); - HideCarets(); - } else { - AC_LOG("%s: UpdateCarets()", __FUNCTION__); - UpdateCarets(); + mFirstCaret->SetAppearance(mFirstCaretAppearanceOnScrollStart); + if (!mFirstCaret->IsLogicallyVisible()) { + // If the caret is hide (Appearance::None) due to timeout or blur, no need + // to update it. + return; + } } + + AC_LOG("%s: UpdateCarets()", __FUNCTION__); + UpdateCarets(); } void AccessibleCaretManager::OnScrollPositionChanged() { if (mLastUpdateCaretMode != GetCaretMode()) { return; }
--- a/layout/base/AccessibleCaretManager.h +++ b/layout/base/AccessibleCaretManager.h @@ -2,16 +2,17 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 AccessibleCaretManager_h #define AccessibleCaretManager_h +#include "AccessibleCaret.h" #include "nsCOMPtr.h" #include "nsCoord.h" #include "nsIFrame.h" #include "nsISelectionListener.h" #include "mozilla/nsRefPtr.h" #include "nsWeakReference.h" #include "mozilla/dom/CaretStateChangedEvent.h" #include "mozilla/EventForwards.h" @@ -25,18 +26,16 @@ struct nsPoint; namespace mozilla { namespace dom { class Element; class Selection; } // namespace dom -class AccessibleCaret; - // ----------------------------------------------------------------------------- // AccessibleCaretManager does not deal with events or callbacks directly. It // relies on AccessibleCaretEventHub to call its public methods to do the work. // All codes needed to interact with PresShell, Selection, and AccessibleCaret // should be written in AccessibleCaretManager. // // None the public methods in AccessibleCaretManager will flush layout or style // prior to performing its task. The caller must ensure the layout is up to @@ -220,16 +219,21 @@ protected: // The timer for hiding the caret in cursor mode after timeout behind the // preference "layout.accessiblecaret.timeout_ms". nsCOMPtr<nsITimer> mCaretTimeoutTimer; // The caret mode since last update carets. CaretMode mLastUpdateCaretMode = CaretMode::None; + // Store the appearance of the first caret when calling OnScrollStart so that + // it can be restored in OnScrollEnd. + AccessibleCaret::Appearance mFirstCaretAppearanceOnScrollStart = + AccessibleCaret::Appearance::None; + static const int32_t kAutoScrollTimerDelay = 30; // Clicking on the boundary of input or textarea will move the caret to the // front or end of the content. To avoid this, we need to deflate the content // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in // AppUnit.h. static const int32_t kBoundaryAppUnits = 61; };
--- a/layout/base/gtest/TestAccessibleCaretManager.cpp +++ b/layout/base/gtest/TestAccessibleCaretManager.cpp @@ -52,16 +52,17 @@ public: }; // class MockAccessibleCaret class MockAccessibleCaretManager : public AccessibleCaretManager { public: using CaretMode = AccessibleCaretManager::CaretMode; using AccessibleCaretManager::UpdateCarets; + using AccessibleCaretManager::HideCarets; MockAccessibleCaretManager() : AccessibleCaretManager(nullptr) { mFirstCaret = MakeUnique<MockAccessibleCaret>(); mSecondCaret = MakeUnique<MockAccessibleCaret>(); } @@ -374,9 +375,123 @@ TEST_F(AccessibleCaretManagerTester, Tes EXPECT_EQ(SecondCaretAppearance(), Appearance::None); check.Call("scrollstart"); mManager.OnScrollEnd(); EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown); } +TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenLogicallyVisible) +{ + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)) + .WillRepeatedly(Return(true)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition)).Times(1); + EXPECT_CALL(check, Call("updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Visibilitychange)).Times(1); + EXPECT_CALL(check, Call("scrollstart1")); + + // After scroll ended, the caret is out of scroll port. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Invisible)); + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition)).Times(1); + EXPECT_CALL(check, Call("scrollend1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Visibilitychange)).Times(1); + EXPECT_CALL(check, Call("scrollstart2")); + + // After scroll ended, the caret is visible again. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Changed)); + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition)).Times(1); + EXPECT_CALL(check, Call("scrollend2")); + } + + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("updatecarets"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("scrollstart1"); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollend1"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("scrollstart2"); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("scrollend2"); +} + +TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenHidden) +{ + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)) + .WillRepeatedly(Return(true)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition)).Times(1); + EXPECT_CALL(check, Call("updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Visibilitychange)).Times(1); + EXPECT_CALL(check, Call("hidecarets")); + + // After scroll ended, the caret is out of scroll port. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Invisible)); + EXPECT_CALL(check, Call("scrollend1")); + + // After scroll ended, the caret is visible again. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Changed)); + EXPECT_CALL(check, Call("scrollend2")); + } + + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("updatecarets"); + + mManager.HideCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("hidecarets"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("scrollend1"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("scrollend2"); +} + } // namespace mozilla
--- a/layout/style/CSSStyleSheet.cpp +++ b/layout/style/CSSStyleSheet.cpp @@ -1786,21 +1786,27 @@ CSSStyleSheet::SubjectSubsumesInnerPrinc return NS_OK; } // Allow access only if CORS mode is not NONE if (GetCORSMode() == CORS_NONE) { return NS_ERROR_DOM_SECURITY_ERR; } - // Now make sure we set the principal of our inner to the - // subjectPrincipal. That means we need a unique inner, of - // course. But we don't want to do that if we're not complete - // yet. Luckily, all the callers of this method throw anyway if - // not complete, so we can just do that here too. + // Now make sure we set the principal of our inner to the subjectPrincipal. + // We do this because we're in a situation where the caller would not normally + // be able to access the sheet, but the sheet has opted in to being read. + // Unfortunately, that means it's also opted in to being _edited_, and if the + // caller now makes edits to the sheet we want the resulting resource loads, + // if any, to look as if they are coming from the caller's principal, not the + // original sheet principal. + // + // That means we need a unique inner, of course. But we don't want to do that + // if we're not complete yet. Luckily, all the callers of this method throw + // anyway if not complete, so we can just do that here too. if (!mInner->mComplete) { return NS_ERROR_DOM_INVALID_ACCESS_ERR; } WillDirty(); mInner->mPrincipal = subjectPrincipal;
--- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -3613,29 +3613,27 @@ CSS_PROP_POSITION( CSS_PROPERTY_STORES_CALC | CSS_PROPERTY_UNITLESS_LENGTH_QUIRK | CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH, "", VARIANT_AHKLP | VARIANT_CALC, kWidthKTable, offsetof(nsStylePosition, mWidth), eStyleAnimType_Coord) -#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL CSS_PROP_USERINTERFACE( -moz-window-dragging, _moz_window_dragging, CSS_PROP_DOMPROP_PREFIXED(WindowDragging), - CSS_PROPERTY_INTERNAL | - CSS_PROPERTY_PARSE_VALUE | - CSS_PROPERTY_ENABLED_IN_UA_SHEETS_AND_CHROME, + CSS_PROPERTY_PARSE_VALUE, "", VARIANT_HK, kWindowDraggingKTable, CSS_PROP_NO_OFFSET, eStyleAnimType_None) +#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL CSS_PROP_UIRESET( -moz-window-shadow, _moz_window_shadow, CSS_PROP_DOMPROP_PREFIXED(WindowShadow), CSS_PROPERTY_INTERNAL | CSS_PROPERTY_PARSE_VALUE | CSS_PROPERTY_ENABLED_IN_UA_SHEETS_AND_CHROME, "",
--- a/layout/style/test/ListCSSProperties.cpp +++ b/layout/style/test/ListCSSProperties.cpp @@ -109,17 +109,16 @@ const char *gInaccessibleProperties[] = "-moz-control-character-visibility", "-moz-script-level", // parsed by UA sheets only "-moz-script-size-multiplier", "-moz-script-min-size", "-moz-math-variant", "-moz-math-display", // parsed by UA sheets only "-moz-top-layer", // parsed by UA sheets only "-moz-min-font-size-ratio", // parsed by UA sheets only - "-moz-window-dragging", // chrome-only internal properties "-moz-window-shadow" // chrome-only internal properties }; inline int is_inaccessible(const char* aPropName) { for (unsigned j = 0; j < MOZ_ARRAY_LENGTH(gInaccessibleProperties); ++j) { if (strcmp(aPropName, gInaccessibleProperties[j]) == 0)
--- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -3964,16 +3964,24 @@ var gCSSProperties = { "vector-effect": { domProp: "vectorEffect", inherited: false, type: CSS_TYPE_LONGHAND, initial_values: [ "none" ], other_values: [ "non-scaling-stroke" ], invalid_values: [] }, + "-moz-window-dragging": { + domProp: "MozWindowDragging", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "no-drag" ], + other_values: [ "drag" ], + invalid_values: [ "none" ] + }, "align-content": { domProp: "alignContent", inherited: false, type: CSS_TYPE_LONGHAND, initial_values: [ "stretch" ], other_values: [ "flex-start", "flex-end", @@ -6618,24 +6626,16 @@ for (var prop in gCSSProperties) { }); } } } if (false) { // TODO These properties are chrome-only, and are not exposed via CSSOM. // We may still want to find a way to test them. See bug 1206999. - gCSSProperties["-moz-window-dragging"] = { - //domProp: "MozWindowDragging", - inherited: true, - type: CSS_TYPE_LONGHAND, - initial_values: [ "no-drag" ], - other_values: [ "drag" ], - invalid_values: [ "none" ] - }; gCSSProperties["-moz-window-shadow"] = { //domProp: "MozWindowShadow", inherited: false, type: CSS_TYPE_LONGHAND, initial_values: [ "default" ], other_values: [ "none", "menu", "tooltip", "sheet" ], invalid_values: [] };
--- a/layout/xul/nsMenuPopupFrame.cpp +++ b/layout/xul/nsMenuPopupFrame.cpp @@ -1840,18 +1840,17 @@ nsMenuPopupFrame::ChangeMenuItem(nsMenuF if (aFromKey && IsOpen()) { nsIFrame* parentMenu = GetParent(); if (parentMenu) { nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent()); if (menulist) { // Fire a command event as the new item, but we don't want to close // the menu, blink it, or update any other state of the menuitem. The // command event will cause the item to be selected. - nsContentUtils::DispatchXULCommand(aMenuItem->GetContent(), - nsContentUtils::IsCallerChrome(), + nsContentUtils::DispatchXULCommand(aMenuItem->GetContent(), /* aTrusted = */ true, nullptr, PresContext()->PresShell(), false, false, false, false); } } } #endif }
--- a/mfbt/Assertions.h +++ b/mfbt/Assertions.h @@ -18,26 +18,29 @@ #include "mozilla/Likely.h" #include "mozilla/MacroArgs.h" #ifdef MOZ_DUMP_ASSERTION_STACK #include "nsTraceRefcnt.h" #endif #if defined(MOZ_CRASHREPORTER) && defined(MOZILLA_INTERNAL_API) && \ !defined(MOZILLA_EXTERNAL_LINKAGE) && defined(__cplusplus) -# define MOZ_CRASH_CRASHREPORT namespace CrashReporter { // This declaration is present here as well as in nsExceptionHandler.h // nsExceptionHandler.h is not directly included in this file as it includes // windows.h, which can cause problems when it is imported into some files due // to the number of macros defined. // XXX If you change this definition - also change the definition in // nsExceptionHandler.h void AnnotateMozCrashReason(const char* aReason); } // namespace CrashReporter + +# define MOZ_CRASH_ANNOTATE(...) CrashReporter::AnnotateMozCrashReason("" __VA_ARGS__) +#else +# define MOZ_CRASH_ANNOTATE(...) do { /* nothing */ } while (0) #endif #include <stddef.h> #include <stdio.h> #include <stdlib.h> #ifdef WIN32 /* * TerminateProcess and GetCurrentProcess are defined in <winbase.h>, which @@ -258,29 +261,26 @@ MOZ_ReportCrash(const char* aStr, const * * If we're a DEBUG build and we crash at a MOZ_CRASH which provides an * explanation-string, we print the string to stderr. Otherwise, we don't * print anything; this is because we want MOZ_CRASH to be 100% safe in release * builds, and it's hard to print to stderr safely when memory might have been * corrupted. */ #ifndef DEBUG -# ifdef MOZ_CRASH_CRASHREPORT -# define MOZ_CRASH(...) \ - do { \ - CrashReporter::AnnotateMozCrashReason("MOZ_CRASH(" __VA_ARGS__ ")"); \ - MOZ_REALLY_CRASH(); \ - } while (0) -# else -# define MOZ_CRASH(...) MOZ_REALLY_CRASH() -# endif +# define MOZ_CRASH(...) \ + do { \ + MOZ_CRASH_ANNOTATE("MOZ_CRASH(" __VA_ARGS__ ")"); \ + MOZ_REALLY_CRASH(); \ + } while (0) #else # define MOZ_CRASH(...) \ do { \ MOZ_ReportCrash("" __VA_ARGS__, __FILE__, __LINE__); \ + MOZ_CRASH_ANNOTATE("MOZ_CRASH(" __VA_ARGS__ ")"); \ MOZ_REALLY_CRASH(); \ } while (0) #endif #ifdef __cplusplus } /* extern "C" */ #endif @@ -377,25 +377,27 @@ struct AssertionConditionType #endif /* First the single-argument form. */ #define MOZ_ASSERT_HELPER1(expr) \ do { \ MOZ_VALIDATE_ASSERT_CONDITION_TYPE(expr); \ if (MOZ_UNLIKELY(!(expr))) { \ MOZ_ReportAssertionFailure(#expr, __FILE__, __LINE__); \ + MOZ_CRASH_ANNOTATE("MOZ_RELEASE_ASSERT(" #expr ")"); \ MOZ_REALLY_CRASH(); \ } \ } while (0) /* Now the two-argument form. */ #define MOZ_ASSERT_HELPER2(expr, explain) \ do { \ MOZ_VALIDATE_ASSERT_CONDITION_TYPE(expr); \ if (MOZ_UNLIKELY(!(expr))) { \ MOZ_ReportAssertionFailure(#expr " (" explain ")", __FILE__, __LINE__); \ + MOZ_CRASH_ANNOTATE("MOZ_RELEASE_ASSERT(" #expr ") (" explain ")"); \ MOZ_REALLY_CRASH(); \ } \ } while (0) #define MOZ_RELEASE_ASSERT_GLUE(a, b) a b #define MOZ_RELEASE_ASSERT(...) \ MOZ_RELEASE_ASSERT_GLUE( \ MOZ_PASTE_PREFIX_AND_ARG_COUNT(MOZ_ASSERT_HELPER, __VA_ARGS__), \
new file mode 100644 --- /dev/null +++ b/mfbt/FastBernoulliTrial.h @@ -0,0 +1,376 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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_FastBernoulliTrial_h +#define mozilla_FastBernoulliTrial_h + +#include "mozilla/Assertions.h" +#include "mozilla/XorShift128PlusRNG.h" + +#include <cmath> +#include <stdint.h> + +namespace mozilla { + +/** + * class FastBernoulliTrial: Efficient sampling with uniform probability + * + * When gathering statistics about a program's behavior, we may be observing + * events that occur very frequently (e.g., function calls or memory + * allocations) and we may be gathering information that is somewhat expensive + * to produce (e.g., call stacks). Sampling all the events could have a + * significant impact on the program's performance. + * + * Why not just sample every N'th event? This technique is called "systematic + * sampling"; it's simple and efficient, and it's fine if we imagine a + * patternless stream of events. But what if we're sampling allocations, and the + * program happens to have a loop where each iteration does exactly N + * allocations? You would end up sampling the same allocation every time through + * the loop; the entire rest of the loop becomes invisible to your measurements! + * More generally, if each iteration does M allocations, and M and N have any + * common divisor at all, most allocation sites will never be sampled. If + * they're both even, say, the odd-numbered allocations disappear from your + * results. + * + * Ideally, we'd like each event to have some probability P of being sampled, + * independent of its neighbors and of its position in the sequence. This is + * called "Bernoulli sampling", and it doesn't suffer from any of the problems + * mentioned above. + * + * One disadvantage of Bernoulli sampling is that you can't be sure exactly how + * many samples you'll get: technically, it's possible that you might sample + * none of them, or all of them. But if the number of events N is large, these + * aren't likely outcomes; you can generally expect somewhere around P * N + * events to be sampled. + * + * The other disadvantage of Bernoulli sampling is that you have to generate a + * random number for every event, which can be slow. + * + * [significant pause] + * + * BUT NOT WITH THIS CLASS! FastBernoulliTrial lets you do true Bernoulli + * sampling, while generating a fresh random number only when we do decide to + * sample an event, not on every trial. When it decides not to sample, a call to + * |FastBernoulliTrial::trial| is nothing but decrementing a counter and + * comparing it to zero. So the lower your sampling probability is, the less + * overhead FastBernoulliTrial imposes. + * + * Probabilities of 0 and 1 are handled efficiently. (In neither case need we + * ever generate a random number at all.) + * + * The essential API: + * + * - FastBernoulliTrial(double P) + * Construct an instance that selects events with probability P. + * + * - FastBernoulliTrial::trial() + * Return true with probability P. Call this each time an event occurs, to + * decide whether to sample it or not. + * + * - FastBernoulliTrial::trial(size_t n) + * Equivalent to calling trial() |n| times, and returning true if any of those + * calls do. However, like trial, this runs in fast constant time. + * + * What is this good for? In some applications, some events are "bigger" than + * others. For example, large allocations are more significant than small + * allocations. Perhaps we'd like to imagine that we're drawing allocations + * from a stream of bytes, and performing a separate Bernoulli trial on every + * byte from the stream. We can accomplish this by calling |t.trial(S)| for + * the number of bytes S, and sampling the event if that returns true. + * + * Of course, this style of sampling needs to be paired with analysis and + * presentation that makes the size of the event apparent, lest trials with + * large values for |n| appear to be indistinguishable from those with small + * values for |n|. + */ +class FastBernoulliTrial { + /* + * This comment should just read, "Generate skip counts with a geometric + * distribution", and leave everyone to go look that up and see why it's the + * right thing to do, if they don't know already. + * + * BUT IF YOU'RE CURIOUS, COMMENTS ARE FREE... + * + * Instead of generating a fresh random number for every trial, we can + * randomly generate a count of how many times we should return false before + * the next time we return true. We call this a "skip count". Once we've + * returned true, we generate a fresh skip count, and begin counting down + * again. + * + * Here's an awesome fact: by exercising a little care in the way we generate + * skip counts, we can produce results indistinguishable from those we would + * get "rolling the dice" afresh for every trial. + * + * In short, skip counts in Bernoulli trials of probability P obey a geometric + * distribution. If a random variable X is uniformly distributed from [0..1), + * then std::floor(std::log(X) / std::log(1-P)) has the appropriate geometric + * distribution for the skip counts. + * + * Why that formula? + * + * Suppose we're to return |true| with some probability P, say, 0.3. Spread + * all possible futures along a line segment of length 1. In portion P of + * those cases, we'll return true on the next call to |trial|; the skip count + * is 0. For the remaining portion 1-P of cases, the skip count is 1 or more. + * + * skip: 0 1 or more + * |------------------^-----------------------------------------| + * portion: 0.3 0.7 + * P 1-P + * + * But the "1 or more" section of the line is subdivided the same way: *within + * that section*, in portion P the second call to |trial()| returns true, and in + * portion 1-P it returns false a second time; the skip count is two or more. + * So we return true on the second call in proportion 0.7 * 0.3, and skip at + * least the first two in proportion 0.7 * 0.7. + * + * skip: 0 1 2 or more + * |------------------^------------^----------------------------| + * portion: 0.3 0.7 * 0.3 0.7 * 0.7 + * P (1-P)*P (1-P)^2 + * + * We can continue to subdivide: + * + * skip >= 0: |------------------------------------------------- (1-P)^0 --| + * skip >= 1: | ------------------------------- (1-P)^1 --| + * skip >= 2: | ------------------ (1-P)^2 --| + * skip >= 3: | ^ ---------- (1-P)^3 --| + * skip >= 4: | . --- (1-P)^4 --| + * . + * ^X, see below + * + * In other words, the likelihood of the next n calls to |trial| returning + * false is (1-P)^n. The longer a run we require, the more the likelihood + * drops. Further calls may return false too, but this is the probability + * we'll skip at least n. + * + * This is interesting, because we can pick a point along this line segment + * and see which skip count's range it falls within; the point X above, for + * example, is within the ">= 2" range, but not within the ">= 3" range, so it + * designates a skip count of 2. So if we pick points on the line at random + * and use the skip counts they fall under, that will be indistinguishable + * from generating a fresh random number between 0 and 1 for each trial and + * comparing it to P. + * + * So to find the skip count for a point X, we must ask: To what whole power + * must we raise 1-P such that we include X, but the next power would exclude + * it? This is exactly std::floor(std::log(X) / std::log(1-P)). + * + * Our algorithm is then, simply: When constructed, compute an initial skip + * count. Return false from |trial| that many times, and then compute a new skip + * count. + * + * For a call to |trial(n)|, if the skip count is greater than n, return false + * and subtract n from the skip count. If the skip count is less than n, + * return true and compute a new skip count. Since each trial is independent, + * it doesn't matter by how much n overshoots the skip count; we can actually + * compute a new skip count at *any* time without affecting the distribution. + * This is really beautiful. + */ + public: + /** + * Construct a fast Bernoulli trial generator. Calls to |trial()| return true + * with probability |aProbability|. Use |aState0| and |aState1| to seed the + * random number generator; both may not be zero. + */ + FastBernoulliTrial(double aProbability, uint64_t aState0, uint64_t aState1) + : mGenerator(aState0, aState1) + { + setProbability(aProbability); + } + + /** + * Return true with probability |mProbability|. Call this each time an event + * occurs, to decide whether to sample it or not. The lower |mProbability| is, + * the faster this function runs. + */ + bool trial() { + if (mSkipCount) { + mSkipCount--; + return false; + } + + return chooseSkipCount(); + } + + /** + * Equivalent to calling trial() |n| times, and returning true if any of those + * calls do. However, like trial, this runs in fast constant time. + * + * What is this good for? In some applications, some events are "bigger" than + * others. For example, large allocations are more significant than small + * allocations. Perhaps we'd like to imagine that we're drawing allocations + * from a stream of bytes, and performing a separate Bernoulli trial on every + * byte from the stream. We can accomplish this by calling |t.trial(S)| for + * the number of bytes S, and sampling the event if that returns true. + * + * Of course, this style of sampling needs to be paired with analysis and + * presentation that makes the "size" of the event apparent, lest trials with + * large values for |n| appear to be indistinguishable from those with small + * values for |n|, despite being potentially much more likely to be sampled. + */ + bool trial(size_t aCount) { + if (mSkipCount > aCount) { + mSkipCount -= aCount; + return false; + } + + return chooseSkipCount(); + } + + void setRandomState(uint64_t aState0, uint64_t aState1) { + mGenerator.setState(aState0, aState1); + } + + void setProbability(double aProbability) { + MOZ_ASSERT(0 <= aProbability && aProbability <= 1); + mProbability = aProbability; + if (0 < mProbability && mProbability < 1) { + /* + * Let's look carefully at how this calculation plays out in floating- + * point arithmetic. We'll assume IEEE, but the final C++ code we arrive + * at would still be fine if our numbers were mathematically perfect. So, + * while we've considered IEEE's edge cases, we haven't done anything that + * should be actively bad when using other representations. + * + * (In the below, read comparisons as exact mathematical comparisons: when + * we say something "equals 1", that means it's exactly equal to 1. We + * treat approximation using intervals with open boundaries: saying a + * value is in (0,1) doesn't specify how close to 0 or 1 the value gets. + * When we use closed boundaries like [1, 2**-53], we're careful to ensure + * the boundary values are actually representable.) + * + * - After the comparison above, we know mProbability is in (0,1). + * + * - The gaps below 1 are 2**-53, so that interval is (0, 1-2**-53]. + * + * - Because the floating-point gaps near 1 are wider than those near + * zero, there are many small positive doubles ε such that 1-ε rounds to + * exactly 1. However, 2**-53 can be represented exactly. So + * 1-mProbability is in [2**-53, 1]. + * + * - log(1 - mProbability) is thus in (-37, 0]. + * + * That range includes zero, but when we use mInvLogNotProbability, it + * would be helpful if we could trust that it's negative. So when log(1 + * - mProbability) is 0, we'll just set mProbability to 0, so that + * mInvLogNotProbability is not used in chooseSkipCount. + * + * - How much of the range of mProbability does this cause us to ignore? + * The only value for which log returns 0 is exactly 1; the slope of log + * at 1 is 1, so for small ε such that 1 - ε != 1, log(1 - ε) is -ε, + * never 0. The gaps near one are larger than the gaps near zero, so if + * 1 - ε wasn't 1, then -ε is representable. So if log(1 - mProbability) + * isn't 0, then 1 - mProbability isn't 1, which means that mProbability + * is at least 2**-53, as discussed earlier. This is a sampling + * likelihood of roughly one in ten trillion, which is unlikely to be + * distinguishable from zero in practice. + * + * So by forbidding zero, we've tightened our range to (-37, -2**-53]. + * + * - Finally, 1 / log(1 - mProbability) is in [-2**53, -1/37). This all + * falls readily within the range of an IEEE double. + * + * ALL THAT HAVING BEEN SAID: here are the five lines of actual code: + */ + double logNotProbability = std::log(1 - mProbability); + if (logNotProbability == 0.0) + mProbability = 0.0; + else + mInvLogNotProbability = 1 / logNotProbability; + } + + chooseSkipCount(); + } + + private: + /* The likelihood that any given call to |trial| should return true. */ + double mProbability; + + /* + * The value of 1/std::log(1 - mProbability), cached for repeated use. + * + * If mProbability is exactly 0 or exactly 1, we don't use this value. + * Otherwise, we guarantee this value is in the range [-2**53, -1/37), i.e. + * definitely negative, as required by chooseSkipCount. See setProbability for + * the details. + */ + double mInvLogNotProbability; + + /* Our random number generator. */ + non_crypto::XorShift128PlusRNG mGenerator; + + /* The number of times |trial| should return false before next returning true. */ + size_t mSkipCount; + + /* + * Choose the next skip count. This also returns the value that |trial| should + * return, since we have to check for the extreme values for mProbability + * anyway, and |trial| should never return true at all when mProbability is 0. + */ + bool chooseSkipCount() { + /* + * If the probability is 1.0, every call to |trial| returns true. Make sure + * mSkipCount is 0. + */ + if (mProbability == 1.0) { + mSkipCount = 0; + return true; + } + + /* + * If the probabilility is zero, |trial| never returns true. Don't bother us + * for a while. + */ + if (mProbability == 0.0) { + mSkipCount = SIZE_MAX; + return false; + } + + /* + * What sorts of values can this call to std::floor produce? + * + * Since mGenerator.nextDouble returns a value in [0, 1-2**-53], std::log + * returns a value in the range [-infinity, -2**-53], all negative. Since + * mInvLogNotProbability is negative (see its comments), the product is + * positive and possibly infinite. std::floor returns +infinity unchanged. + * So the result will always be positive. + * + * Converting a double to an integer that is out of range for that integer + * is undefined behavior, so we must clamp our result to SIZE_MAX, to ensure + * we get an acceptable value for mSkipCount. + * + * The clamp is written carefully. Note that if we had said: + * + * if (skipCount > SIZE_MAX) + * skipCount = SIZE_MAX; + * + * that leads to undefined behavior 64-bit machines: SIZE_MAX coerced to + * double is 2^64, not 2^64-1, so this doesn't actually set skipCount to a + * value that can be safely assigned to mSkipCount. + * + * Jakub Oleson cleverly suggested flipping the sense of the comparison: if + * we require that skipCount < SIZE_MAX, then because of the gaps (2048) + * between doubles at that magnitude, the highest double less than 2^64 is + * 2^64 - 2048, which is fine to store in a size_t. + * + * (On 32-bit machines, all size_t values can be represented exactly in + * double, so all is well.) + */ + double skipCount = std::floor(std::log(mGenerator.nextDouble()) + * mInvLogNotProbability); + if (skipCount < SIZE_MAX) + mSkipCount = skipCount; + else + mSkipCount = SIZE_MAX; + + return true; + } +}; + +} /* namespace mozilla */ + +#endif /* mozilla_FastBernoulliTrial_h */
--- a/mfbt/moz.build +++ b/mfbt/moz.build @@ -34,16 +34,17 @@ EXPORTS.mozilla = [ 'DebugOnly.h', 'decimal/Decimal.h', 'double-conversion/double-conversion.h', 'double-conversion/utils.h', 'Endian.h', 'EnumeratedArray.h', 'EnumeratedRange.h', 'EnumSet.h', + 'FastBernoulliTrial.h', 'FloatingPoint.h', 'Function.h', 'GuardObjects.h', 'HashFunctions.h', 'IndexSequence.h', 'IntegerPrintfMacros.h', 'IntegerRange.h', 'IntegerTypeTraits.h',
new file mode 100644 --- /dev/null +++ b/mfbt/tests/TestFastBernoulliTrial.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/FastBernoulliTrial.h" + +#include <math.h> + +// Note that because we always provide FastBernoulliTrial with a fixed +// pseudorandom seed in these tests, the results here are completely +// deterministic. +// +// A non-optimized version of this test runs in .009s on my laptop. Using larger +// sample sizes lets us meet tighter bounds on the counts. + +static void +TestProportions() +{ + mozilla::FastBernoulliTrial bernoulli(1.0, + 698079309544035222ULL, + 6012389156611637584ULL); + + for (size_t i = 0; i < 100; i++) + MOZ_RELEASE_ASSERT(bernoulli.trial()); + + { + bernoulli.setProbability(0.5); + size_t count = 0; + for (size_t i = 0; i < 1000; i++) + count += bernoulli.trial(); + MOZ_RELEASE_ASSERT(count == 496); + } + + { + bernoulli.setProbability(0.001); + size_t count = 0; + for (size_t i = 0; i < 1000; i++) + count += bernoulli.trial(); + MOZ_RELEASE_ASSERT(count == 2); + } + + { + bernoulli.setProbability(0.85); + size_t count = 0; + for (size_t i = 0; i < 1000; i++) + count += bernoulli.trial(); + MOZ_RELEASE_ASSERT(count == 852); + } + + bernoulli.setProbability(0.0); + for (size_t i = 0; i < 100; i++) + MOZ_RELEASE_ASSERT(!bernoulli.trial()); +} + +static void +TestHarmonics() +{ + mozilla::FastBernoulliTrial bernoulli(0.1, + 698079309544035222ULL, + 6012389156611637584ULL); + + const size_t n = 100000; + bool trials[n]; + for (size_t i = 0; i < n; i++) + trials[i] = bernoulli.trial(); + + // For each harmonic and phase, check that the proportion sampled is + // within acceptable bounds. + for (size_t harmonic = 1; harmonic < 20; harmonic++) { + size_t expected = n / harmonic / 10; + size_t low_expected = expected * 85 / 100; + size_t high_expected = expected * 115 / 100; + + for (size_t phase = 0; phase < harmonic; phase++) { + size_t count = 0; + for (size_t i = phase; i < n; i += harmonic) + count += trials[i]; + + MOZ_RELEASE_ASSERT(low_expected <= count && count <= high_expected); + } + } +} + +static void +TestTrialN() +{ + mozilla::FastBernoulliTrial bernoulli(0.01, + 0x67ff17e25d855942ULL, + 0x74f298193fe1c5b1ULL); + + { + size_t count = 0; + for (size_t i = 0; i < 10000; i++) + count += bernoulli.trial(1); + + // Expected value: 0.01 * 10000 == 100 + MOZ_RELEASE_ASSERT(count == 97); + } + + { + size_t count = 0; + for (size_t i = 0; i < 10000; i++) + count += bernoulli.trial(3); + + // Expected value: (1 - (1 - 0.01) ** 3) == 0.0297, + // 0.0297 * 10000 == 297 + MOZ_RELEASE_ASSERT(count == 304); + } + + { + size_t count = 0; + for (size_t i = 0; i < 10000; i++) + count += bernoulli.trial(10); + + // Expected value: (1 - (1 - 0.01) ** 10) == 0.0956, + // 0.0956 * 10000 == 956 + MOZ_RELEASE_ASSERT(count == 936); + } + + { + size_t count = 0; + for (size_t i = 0; i < 10000; i++) + count += bernoulli.trial(100); + + // Expected value: (1 - (1 - 0.01) ** 100) == 0.6339 + // 0.6339 * 10000 == 6339 + MOZ_RELEASE_ASSERT(count == 6372); + } + + { + size_t count = 0; + for (size_t i = 0; i < 10000; i++) + count += bernoulli.trial(1000); + + // Expected value: (1 - (1 - 0.01) ** 1000) == 0.9999 + // 0.9999 * 10000 == 9999 + MOZ_RELEASE_ASSERT(count == 9998); + } +} + +static void +TestChangeProbability() +{ + mozilla::FastBernoulliTrial bernoulli(1.0, + 0x67ff17e25d855942ULL, + 0x74f298193fe1c5b1ULL); + + // Establish a very high skip count. + bernoulli.setProbability(0.0); + + // This should re-establish a zero skip count. + bernoulli.setProbability(1.0); + + // So this should return true. + MOZ_RELEASE_ASSERT(bernoulli.trial()); +} + +static void +TestCuspProbabilities() +{ + /* + * FastBernoulliTrial takes care to avoid screwing up on edge cases. The + * checks here all look pretty dumb, but they exercise paths in the code that + * could exhibit undefined behavior if coded naïvely. + */ + + /* + * This should not be perceptibly different from 1; for 64-bit doubles, this + * is a one in ten trillion chance of the trial not succeeding. Overflows + * converting doubles to size_t skip counts may change this, though. + */ + mozilla::FastBernoulliTrial bernoulli(nextafter(1, 0), + 0x67ff17e25d855942ULL, + 0x74f298193fe1c5b1ULL); + + for (size_t i = 0; i < 1000; i++) + MOZ_RELEASE_ASSERT(bernoulli.trial()); + + /* + * This should not be perceptibly different from 0; for 64-bit doubles, + * the FastBernoulliTrial will actually treat this as exactly zero. + */ + bernoulli.setProbability(nextafter(0, 1)); + for (size_t i = 0; i < 1000; i++) + MOZ_RELEASE_ASSERT(!bernoulli.trial()); + + /* + * This should be a vanishingly low probability which FastBernoulliTrial does + * *not* treat as exactly zero. + */ + bernoulli.setProbability(1 - nextafter(1, 0)); + for (size_t i = 0; i < 1000; i++) + MOZ_RELEASE_ASSERT(!bernoulli.trial()); +} + +int +main() +{ + TestProportions(); + TestHarmonics(); + TestTrialN(); + TestChangeProbability(); + TestCuspProbabilities(); + + return 0; +}
--- a/mfbt/tests/moz.build +++ b/mfbt/tests/moz.build @@ -11,16 +11,17 @@ CppUnitTests([ 'TestBloomFilter', 'TestCasting', 'TestCeilingFloor', 'TestCheckedInt', 'TestCountPopulation', 'TestCountZeroes', 'TestEndian', 'TestEnumSet', + 'TestFastBernoulliTrial', 'TestFloatingPoint', 'TestFunction', 'TestIntegerPrintfMacros', 'TestIntegerRange', 'TestJSONWriter', 'TestMacroArgs', 'TestMacroForEach', 'TestMathAlgorithms',
--- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -128,18 +128,16 @@ import android.widget.Toast; public class GeckoAppShell { private static final String LOGTAG = "GeckoAppShell"; private static final boolean LOGGING = false; // We have static members only. private GeckoAppShell() { } - private static GeckoEditableListener editableListener; - private static final CrashHandler CRASH_HANDLER = new CrashHandler() { @Override protected String getAppPackageName() { return AppConstants.ANDROID_PACKAGE_NAME; } @Override protected Context getAppContext() { @@ -320,27 +318,16 @@ public class GeckoAppShell private static LayerView sLayerView; public static void setLayerView(LayerView lv) { if (sLayerView == lv) { return; } sLayerView = lv; - - // We should have a unique GeckoEditable instance per nsWindow instance, - // so even though we have a new view here, the underlying nsWindow is the same, - // and we don't create a new GeckoEditable. - if (editableListener == null) { - // Starting up; istall new Gecko-to-Java editable listener. - editableListener = new GeckoEditable(); - } else { - // Bind the existing GeckoEditable instance to the new LayerView - GeckoAppShell.notifyIMEContext(GeckoEditableListener.IME_STATE_DISABLED, "", "", ""); - } } @RobocopTarget public static LayerView getLayerView() { return sLayerView; } /** @@ -415,41 +402,16 @@ public class GeckoAppShell * The Gecko-side API: API methods that Gecko calls */ @WrapForJNI(allowMultithread = true, noThrow = true) public static void handleUncaughtException(Thread thread, Throwable e) { CRASH_HANDLER.uncaughtException(thread, e); } - @WrapForJNI - public static void notifyIME(int type) { - if (editableListener != null) { - editableListener.notifyIME(type); - } - } - - @WrapForJNI - public static void notifyIMEContext(int state, String typeHint, - String modeHint, String actionHint) { - if (editableListener != null) { - editableListener.notifyIMEContext(state, typeHint, - modeHint, actionHint); - } - } - - @WrapForJNI - public static void notifyIMEChange(String text, int start, int end, int newEnd) { - if (newEnd < 0) { // Selection change - editableListener.onSelectionChange(start, end); - } else { // Text change - editableListener.onTextChange(text, start, end, newEnd); - } - } - private static final Object sEventAckLock = new Object(); private static boolean sWaitingForEventAck; // Block the current thread until the Gecko event loop is caught up public static void sendEventToGeckoSync(GeckoEvent e) { e.setAckNeeded(true); long time = SystemClock.uptimeMillis(); @@ -2441,17 +2403,17 @@ public class GeckoAppShell @WrapForJNI @RobocopTarget public static boolean isTablet() { return HardwareUtils.isTablet(); } private static boolean sImeWasEnabledOnLastResize = false; public static void viewSizeChanged() { - LayerView v = getLayerView(); + GeckoView v = (GeckoView) getLayerView(); if (v == null) { return; } boolean imeIsEnabled = v.isIMEEnabled(); if (imeIsEnabled && !sImeWasEnabledOnLastResize) { // The IME just came up after not being up, so let's scroll // to the focused input. sendEventToGecko(GeckoEvent.createBroadcastEvent(
--- a/mobile/android/base/GeckoEditable.java +++ b/mobile/android/base/GeckoEditable.java @@ -11,17 +11,17 @@ import java.lang.reflect.InvocationHandl import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; import org.json.JSONObject; import org.mozilla.gecko.AppConstants.Versions; -import org.mozilla.gecko.gfx.InputConnectionHandler; +import org.mozilla.gecko.annotation.WrapForJNI; import org.mozilla.gecko.gfx.LayerView; import org.mozilla.gecko.util.GeckoEventListener; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.util.ThreadUtils.AssertBehavior; import android.os.Handler; import android.os.Looper; import android.text.Editable; @@ -343,33 +343,67 @@ final class GeckoEditable } } boolean isEmpty() { return mActions.isEmpty(); } } + @WrapForJNI GeckoEditable() { + if (DEBUG) { + // Called by nsWindow. + ThreadUtils.assertOnGeckoThread(); + } mActionQueue = new ActionQueue(); mSavedSelectionStart = -1; mUpdateGecko = true; mText = new SpannableStringBuilder(); mChangedText = new SpannableStringBuilder(); final Class<?>[] PROXY_INTERFACES = { Editable.class }; mProxy = (Editable)Proxy.newProxyInstance( Editable.class.getClassLoader(), PROXY_INTERFACES, this); - LayerView v = GeckoAppShell.getLayerView(); - mListener = GeckoInputConnection.create(v, this); + mIcRunHandler = mIcPostHandler = ThreadUtils.getUiHandler(); + } + + @WrapForJNI + /* package */ void onViewChange(final GeckoView v) { + if (DEBUG) { + // Called by nsWindow. + ThreadUtils.assertOnGeckoThread(); + Log.d(LOGTAG, "onViewChange(" + v + ")"); + } - mIcRunHandler = mIcPostHandler = ThreadUtils.getUiHandler(); + final GeckoEditableListener newListener = GeckoInputConnection.create(v, this); + geckoPostToIc(new Runnable() { + @Override + public void run() { + if (DEBUG) { + Log.d(LOGTAG, "onViewChange (set listener)"); + } + // Make sure there are no other things going on + mActionQueue.syncWithGecko(); + mListener = newListener; + } + }); + + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + if (DEBUG) { + Log.d(LOGTAG, "onViewChange (set IC)"); + } + v.setInputConnectionListener((InputConnectionListener) newListener); + } + }); } private boolean onIcThread() { return mIcRunHandler.getLooper() == Looper.myLooper(); } private void assertOnIcThread() { ThreadUtils.assertOnThread(mIcRunHandler.getLooper().getThread(), AssertBehavior.THROW); @@ -764,17 +798,17 @@ final class GeckoEditable for (Object span : spans) { if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) { throw new IllegalStateException("composition not cancelled"); } } } } - @Override + @WrapForJNI @Override public void notifyIME(final int type) { if (DEBUG) { // GeckoEditableListener methods should all be called from the Gecko thread ThreadUtils.assertOnGeckoThread(); // NOTIFY_IME_REPLY_EVENT is logged separately, inside geckoActionReply() if (type != NOTIFY_IME_REPLY_EVENT) { Log.d(LOGTAG, "notifyIME(" + getConstantName(GeckoEditableListener.class, "NOTIFY_IME_", type) + @@ -840,45 +874,35 @@ final class GeckoEditable } else if (type == NOTIFY_IME_OF_FOCUS) { mGeckoFocused = true; mSuppressCompositions = false; EventDispatcher.getInstance(). registerGeckoThreadListener(this, "TextSelection:DraggingHandle"); } } - @Override + @WrapForJNI @Override public void notifyIMEContext(final int state, final String typeHint, - final String modeHint, final String actionHint) { - // Because we want to be able to bind GeckoEditable to the newest LayerView instance, - // this can be called from the Java IC thread in addition to the Gecko thread. + final String modeHint, final String actionHint) { if (DEBUG) { + // GeckoEditableListener methods should all be called from the Gecko thread + ThreadUtils.assertOnGeckoThread(); Log.d(LOGTAG, "notifyIMEContext(" + getConstantName(GeckoEditableListener.class, "IME_STATE_", state) + ", \"" + typeHint + "\", \"" + modeHint + "\", \"" + actionHint + "\")"); } geckoPostToIc(new Runnable() { @Override public void run() { - // Make sure there are no other things going on - mActionQueue.syncWithGecko(); - // Set InputConnectionHandler in notifyIMEContext because - // GeckoInputConnection.notifyIMEContext calls restartInput() which will invoke - // InputConnectionHandler.onCreateInputConnection - LayerView v = GeckoAppShell.getLayerView(); - if (v != null) { - mListener = GeckoInputConnection.create(v, GeckoEditable.this); - v.setInputConnectionHandler((InputConnectionHandler)mListener); - mListener.notifyIMEContext(state, typeHint, modeHint, actionHint); - } + mListener.notifyIMEContext(state, typeHint, modeHint, actionHint); } }); } - @Override + @WrapForJNI @Override public void onSelectionChange(final int start, final int end) { if (DEBUG) { // GeckoEditableListener methods should all be called from the Gecko thread ThreadUtils.assertOnGeckoThread(); Log.d(LOGTAG, "onSelectionChange(" + start + ", " + end + ")"); } if (start < 0 || start > mText.length() || end < 0 || end > mText.length()) { Log.e(LOGTAG, "invalid selection notification range: " + @@ -924,19 +948,19 @@ final class GeckoEditable mText.insert(start, newText); } private boolean isSameText(int start, int oldEnd, CharSequence newText) { return oldEnd - start == newText.length() && TextUtils.regionMatches(mText, start, newText, 0, oldEnd - start); } - @Override + @WrapForJNI @Override public void onTextChange(final CharSequence text, final int start, - final int unboundedOldEnd, final int unboundedNewEnd) { + final int unboundedOldEnd, final int unboundedNewEnd) { if (DEBUG) { // GeckoEditableListener methods should all be called from the Gecko thread ThreadUtils.assertOnGeckoThread(); StringBuilder sb = new StringBuilder("onTextChange("); debugAppend(sb, text); sb.append(", ").append(start).append(", ") .append(unboundedOldEnd).append(", ") .append(unboundedNewEnd).append(")");
--- a/mobile/android/base/GeckoEditableListener.java +++ b/mobile/android/base/GeckoEditableListener.java @@ -1,22 +1,26 @@ /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- * 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/. */ package org.mozilla.gecko; +import org.mozilla.gecko.annotation.WrapForJNI; + /** * Interface for the Editable to listen on the Gecko thread, as well as for the IC thread to listen * to the Editable. */ interface GeckoEditableListener { // IME notification type for notifyIME(), corresponding to NotificationToIME enum in Gecko + @WrapForJNI int NOTIFY_IME_OPEN_VKB = -2; + @WrapForJNI int NOTIFY_IME_REPLY_EVENT = -1; int NOTIFY_IME_OF_FOCUS = 1; int NOTIFY_IME_OF_BLUR = 2; int NOTIFY_IME_TO_COMMIT_COMPOSITION = 8; int NOTIFY_IME_TO_CANCEL_COMPOSITION = 9; // IME enabled state for notifyIMEContext() int IME_STATE_DISABLED = 0; int IME_STATE_ENABLED = 1;
--- a/mobile/android/base/GeckoEvent.java +++ b/mobile/android/base/GeckoEvent.java @@ -68,17 +68,16 @@ public class GeckoEvent { // Make sure to keep these values in sync with the enum in // AndroidGeckoEvent in widget/android/AndroidJavaWrappers.h @JNITarget private enum NativeGeckoEvent { NATIVE_POKE(0), KEY_EVENT(1), MOTION_EVENT(2), SENSOR_EVENT(3), - PROCESS_OBJECT(4), LOCATION_EVENT(5), IME_EVENT(6), SIZE_CHANGED(8), APP_BACKGROUNDING(9), APP_FOREGROUNDING(10), LOAD_URI(12), NOOP(15), BROADCAST(19), @@ -154,18 +153,16 @@ public class GeckoEvent { public static final int ACTION_MAGNIFY_END = 13; public static final int ACTION_GAMEPAD_ADDED = 1; public static final int ACTION_GAMEPAD_REMOVED = 2; public static final int ACTION_GAMEPAD_BUTTON = 1; public static final int ACTION_GAMEPAD_AXES = 2; - public static final int ACTION_OBJECT_LAYER_CLIENT = 1; - private final int mType; private int mAction; private boolean mAckNeeded; private long mTime; private Point[] mPoints; private int[] mPointIndicies; private int mPointerIndex; // index of the point that has changed private float[] mOrientations; @@ -217,18 +214,16 @@ public class GeckoEvent { private int mHeight; private int mID; private int mGamepadButton; private boolean mGamepadButtonPressed; private float mGamepadButtonValue; private float[] mGamepadValues; - private Object mObject; - private GeckoEvent(NativeGeckoEvent event) { mType = event.value; } public static GeckoEvent createAppBackgroundingEvent() { return GeckoEvent.get(NativeGeckoEvent.APP_BACKGROUNDING); } @@ -579,23 +574,16 @@ public class GeckoEvent { event.mY = s.values[1]; event.mZ = s.values[2]; event.mW = s.values[3]; break; } return event; } - public static GeckoEvent createObjectEvent(final int action, final Object object) { - GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.PROCESS_OBJECT); - event.mAction = action; - event.mObject = object; - return event; - } - public static GeckoEvent createLocationEvent(Location l) { GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOCATION_EVENT); event.mLocation = l; return event; } public static GeckoEvent createIMEEvent(ImeAction action) { GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
--- a/mobile/android/base/GeckoInputConnection.java +++ b/mobile/android/base/GeckoInputConnection.java @@ -6,17 +6,16 @@ package org.mozilla.gecko; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.SynchronousQueue; import org.mozilla.gecko.AppConstants.Versions; -import org.mozilla.gecko.gfx.InputConnectionHandler; import org.mozilla.gecko.util.Clipboard; import org.mozilla.gecko.util.GamepadUtils; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.util.ThreadUtils.AssertBehavior; import android.content.Context; import android.os.Handler; import android.os.Looper; @@ -35,17 +34,17 @@ import android.view.inputmethod.BaseInpu import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; class GeckoInputConnection extends BaseInputConnection - implements InputConnectionHandler, GeckoEditableListener { + implements InputConnectionListener, GeckoEditableListener { private static final boolean DEBUG = false; protected static final String LOGTAG = "GeckoInputConnection"; private static final String CUSTOM_HANDLER_TEST_METHOD = "testInputConnection"; private static final String CUSTOM_HANDLER_TEST_CLASS = "org.mozilla.gecko.tests.components.GeckoViewComponent$TextInput"; @@ -1002,17 +1001,17 @@ final class DebugGeckoInputConnection GeckoEditableClient editable) { super(targetView, editable); mCallLevel = new StringBuilder(); } public static GeckoEditableListener create(View targetView, GeckoEditableClient editable) { final Class<?>[] PROXY_INTERFACES = { InputConnection.class, - InputConnectionHandler.class, + InputConnectionListener.class, GeckoEditableListener.class }; DebugGeckoInputConnection dgic = new DebugGeckoInputConnection(targetView, editable); dgic.mProxy = (InputConnection)Proxy.newProxyInstance( GeckoInputConnection.class.getClassLoader(), PROXY_INTERFACES, dgic); return (GeckoEditableListener)dgic.mProxy; }
--- a/mobile/android/base/GeckoThread.java +++ b/mobile/android/base/GeckoThread.java @@ -160,52 +160,64 @@ public class GeckoThread extends Thread throw new UnsupportedOperationException("Cannot make call", e.getCause()); } } // Queue a call to the given method. private static void queueNativeCallLocked(final Class<?> cls, final String methodName, final Object obj, final Object[] args, final State state) { - final Class<?>[] argTypes = new Class<?>[args.length]; + final ArrayList<Class<?>> argTypes = new ArrayList<>(args.length); + final ArrayList<Object> argValues = new ArrayList<>(args.length); + for (int i = 0; i < args.length; i++) { + if (args[i] instanceof Class) { + argTypes.add((Class<?>) args[i]); + argValues.add(args[++i]); + continue; + } Class<?> argType = args[i].getClass(); if (argType == Boolean.class) argType = Boolean.TYPE; else if (argType == Byte.class) argType = Byte.TYPE; else if (argType == Character.class) argType = Character.TYPE; else if (argType == Double.class) argType = Double.TYPE; else if (argType == Float.class) argType = Float.TYPE; else if (argType == Integer.class) argType = Integer.TYPE; else if (argType == Long.class) argType = Long.TYPE; else if (argType == Short.class) argType = Short.TYPE; - argTypes[i] = argType; + argTypes.add(argType); + argValues.add(args[i]); } final Method method; try { - method = cls.getDeclaredMethod(methodName, argTypes); + method = cls.getDeclaredMethod( + methodName, argTypes.toArray(new Class<?>[argTypes.size()])); } catch (final NoSuchMethodException e) { throw new UnsupportedOperationException("Cannot find method", e); } if (QUEUED_CALLS.size() == 0 && isStateAtLeast(state)) { - invokeMethod(method, obj, args); + invokeMethod(method, obj, argValues.toArray()); return; } - QUEUED_CALLS.add(new QueuedCall(method, obj, args, state)); + + QUEUED_CALLS.add(new QueuedCall( + method, obj, argValues.toArray(), state)); } /** * Queue a call to the given static method until Gecko is in the given state. * * @param state The Gecko state in which the native call could be executed. * Default is State.RUNNING, which means this queued call will * run when Gecko is at or after RUNNING state. * @param cls Class that declares the static method. * @param methodName Name of the static method. - * @param args Args to call the static method with. + * @param args Args to call the static method with; to specify a parameter type, + * pass in a Class instance first, followed by the value. */ public static void queueNativeCallUntil(final State state, final Class<?> cls, final String methodName, final Object... args) { synchronized (QUEUED_CALLS) { queueNativeCallLocked(cls, methodName, null, args, state); } } @@ -220,17 +232,18 @@ public class GeckoThread extends Thread } /** * Queue a call to the given instance method until Gecko is in the given state. * * @param state The Gecko state in which the native call could be executed. * @param obj Object that declares the instance method. * @param methodName Name of the instance method. - * @param args Args to call the instance method with. + * @param args Args to call the instance method with; to specify a parameter type, + * pass in a Class instance first, followed by the value. */ public static void queueNativeCallUntil(final State state, final Object obj, final String methodName, final Object... args) { synchronized (QUEUED_CALLS) { queueNativeCallLocked(obj.getClass(), methodName, obj, args, state); } }
--- a/mobile/android/base/GeckoView.java +++ b/mobile/android/base/GeckoView.java @@ -25,30 +25,36 @@ import org.mozilla.gecko.util.NativeJSOb import org.mozilla.gecko.util.ThreadUtils; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.os.Bundle; +import android.os.Handler; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; +import android.view.KeyEvent; import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; public class GeckoView extends LayerView implements ContextGetter { private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView"; private static final String LOGTAG = "GeckoView"; private ChromeDelegate mChromeDelegate; private ContentDelegate mContentDelegate; + private InputConnectionListener mInputConnectionListener; + private final GeckoEventListener mGeckoEventListener = new GeckoEventListener() { @Override public void handleMessage(final String event, final JSONObject message) { ThreadUtils.postToUiThread(new Runnable() { @Override public void run() { try { if (event.equals("Gecko:Ready")) { @@ -104,17 +110,18 @@ public class GeckoView extends LayerView } catch (Exception e) { Log.w(LOGTAG, "handleMessage threw for " + event, e); } } }; @WrapForJNI private static final class Window extends JNIObject { - static native void open(Window instance, int width, int height); + static native void open(Window instance, GeckoView view, int width, int height); + static native void setLayerClient(Object client); @Override protected native void disposeNative(); } private final Window window = new Window(); public GeckoView(Context context) { super(context); init(context, null, true); @@ -137,18 +144,24 @@ public class GeckoView extends LayerView setGeckoInterface(new BaseGeckoInterface(context)); GeckoAppShell.setContextGetter(this); } // Perform common initialization for Fennec/GeckoView. GeckoAppShell.setLayerView(this); initializeView(EventDispatcher.getInstance()); - GeckoAppShell.sendEventToGecko(GeckoEvent.createObjectEvent( - GeckoEvent.ACTION_OBJECT_LAYER_CLIENT, getLayerClientObject())); + + if (GeckoThread.isStateAtLeast(GeckoThread.State.JNI_READY)) { + Window.setLayerClient(getLayerClientObject()); + } else { + GeckoThread.queueNativeCallUntil(GeckoThread.State.JNI_READY, + Window.class, "setLayerClient", + Object.class, getLayerClientObject()); + } // TODO: Fennec currently takes care of its own initialization, so this // flag is a hack used in Fennec to prevent GeckoView initialization. // This should go away once Fennec also uses GeckoView for // initialization. if (!doInit) return; @@ -207,30 +220,101 @@ public class GeckoView extends LayerView @Override public void onAttachedToWindow() { super.onAttachedToWindow(); final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { - Window.open(window, metrics.widthPixels, metrics.heightPixels); + Window.open(window, this, metrics.widthPixels, metrics.heightPixels); } else { GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, Window.class, - "open", window, metrics.widthPixels, metrics.heightPixels); + "open", window, GeckoView.class, this, + metrics.widthPixels, metrics.heightPixels); } } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); window.disposeNative(); } + /* package */ void setInputConnectionListener(final InputConnectionListener icl) { + mInputConnectionListener = icl; + } + + @Override + public Handler getHandler() { + if (mInputConnectionListener != null) { + return mInputConnectionListener.getHandler(super.getHandler()); + } + return super.getHandler(); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + if (mInputConnectionListener != null) { + return mInputConnectionListener.onCreateInputConnection(outAttrs); + } + return null; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (super.onKeyPreIme(keyCode, event)) { + return true; + } + return mInputConnectionListener != null && + mInputConnectionListener.onKeyPreIme(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (super.onKeyUp(keyCode, event)) { + return true; + } + return mInputConnectionListener != null && + mInputConnectionListener.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (super.onKeyDown(keyCode, event)) { + return true; + } + return mInputConnectionListener != null && + mInputConnectionListener.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (super.onKeyLongPress(keyCode, event)) { + return true; + } + return mInputConnectionListener != null && + mInputConnectionListener.onKeyLongPress(keyCode, event); + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + if (super.onKeyMultiple(keyCode, repeatCount, event)) { + return true; + } + return mInputConnectionListener != null && + mInputConnectionListener.onKeyMultiple(keyCode, repeatCount, event); + } + + /* package */ boolean isIMEEnabled() { + return mInputConnectionListener != null && + mInputConnectionListener.isIMEEnabled(); + } + /** * Add a Browser to the GeckoView container. * @param url The URL resource to load into the new Browser. */ public Browser addBrowser(String url) { Tab tab = Tabs.getInstance().loadUrl(url, Tabs.LOADURL_NEW_TAB); if (tab != null) { return new Browser(tab.getId());
new file mode 100644 --- /dev/null +++ b/mobile/android/base/InputConnectionListener.java @@ -0,0 +1,25 @@ +/* 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/. */ + +package org.mozilla.gecko; + +import android.os.Handler; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +/** + * Interface for interacting with GeckoInputConnection from GeckoView. + */ +interface InputConnectionListener +{ + Handler getHandler(Handler defHandler); + InputConnection onCreateInputConnection(EditorInfo outAttrs); + boolean onKeyPreIme(int keyCode, KeyEvent event); + boolean onKeyDown(int keyCode, KeyEvent event); + boolean onKeyLongPress(int keyCode, KeyEvent event); + boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event); + boolean onKeyUp(int keyCode, KeyEvent event); + boolean isIMEEnabled(); +}
deleted file mode 100644 --- a/mobile/android/base/gfx/InputConnectionHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -/* 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/. */ - -package org.mozilla.gecko.gfx; - -import android.os.Handler; -import android.view.KeyEvent; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; - -public interface InputConnectionHandler -{ - Handler getHandler(Handler defHandler); - InputConnection onCreateInputConnection(EditorInfo outAttrs); - boolean onKeyPreIme(int keyCode, KeyEvent event); - boolean onKeyDown(int keyCode, KeyEvent event); - boolean onKeyLongPress(int keyCode, KeyEvent event); - boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event); - boolean onKeyUp(int keyCode, KeyEvent event); - boolean isIMEEnabled(); -}
--- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -26,43 +26,39 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.SurfaceTexture; -import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; import android.view.InputDevice; import android.widget.LinearLayout; import android.widget.ScrollView; /** * A view rendered by the layer compositor. */ public class LayerView extends ScrollView implements Tabs.OnTabsChangedListener { private static final String LOGTAG = "GeckoLayerView"; private GeckoLayerClient mLayerClient; private PanZoomController mPanZoomController; private DynamicToolbarAnimator mToolbarAnimator; private final GLController mGLController; - private InputConnectionHandler mInputConnectionHandler; private LayerRenderer mRenderer; /* Must be a PAINT_xxx constant */ private int mPaintState; private int mBackgroundColor; private FullScreenState mFullScreenState; private SurfaceView mSurfaceView; private TextureView mTextureView; @@ -131,17 +127,16 @@ public class LayerView extends ScrollVie if (mOverscroll != null) { mLayerClient.setOverscrollHandler(mOverscroll); } mPanZoomController = mLayerClient.getPanZoomController(); mToolbarAnimator = mLayerClient.getDynamicToolbarAnimator(); mRenderer = new LayerRenderer(this); - mInputConnectionHandler = null; setFocusable(true); setFocusableInTouchMode(true); GeckoAccessibility.setDelegate(this); GeckoAccessibility.setAccessibilityStateChangeListener(getContext()); } @@ -359,81 +354,21 @@ public class LayerView extends ScrollVie public void setZoomConstraints(ZoomConstraints constraints) { mLayerClient.setZoomConstraints(constraints); } public void setIsRTL(boolean aIsRTL) { mLayerClient.setIsRTL(aIsRTL); } - public void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler) { - mInputConnectionHandler = inputConnectionHandler; - } - - @Override - public Handler getHandler() { - if (mInputConnectionHandler != null) - return mInputConnectionHandler.getHandler(super.getHandler()); - return super.getHandler(); - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - if (mInputConnectionHandler != null) - return mInputConnectionHandler.onCreateInputConnection(outAttrs); - return null; - } - - @Override - public boolean onKeyPreIme(int keyCode, KeyEvent event) { - if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyPreIme(keyCode, event)) { - return true; - } - return false; - } - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mPanZoomController != null && mPanZoomController.onKeyEvent(event)) { return true; } - if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyDown(keyCode, event)) { - return true; - } - return false; - } - - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyLongPress(keyCode, event)) { - return true; - } - return false; - } - - @Override - public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { - if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyMultiple(keyCode, repeatCount, event)) { - return true; - } - return false; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyUp(keyCode, event)) { - return true; - } - return false; - } - - public boolean isIMEEnabled() { - if (mInputConnectionHandler != null) { - return mInputConnectionHandler.isIMEEnabled(); - } return false; } public void requestRender() { if (mListener != null) { mListener.renderRequested(); } }
--- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -283,17 +283,16 @@ gbjar.sources += [ 'gfx/DisplayPortMetrics.java', 'gfx/DrawTimingQueue.java', 'gfx/DynamicToolbarAnimator.java', 'gfx/FloatSize.java', 'gfx/FullScreenState.java', 'gfx/GeckoLayerClient.java', 'gfx/GLController.java', 'gfx/ImmutableViewportMetrics.java', - 'gfx/InputConnectionHandler.java', 'gfx/IntSize.java', 'gfx/JavaPanZoomController.java', 'gfx/Layer.java', 'gfx/LayerRenderer.java', 'gfx/LayerView.java', 'gfx/NativePanZoomController.java', 'gfx/Overscroll.java', 'gfx/OverscrollEdgeEffect.java', @@ -376,16 +375,17 @@ gbjar.sources += [ 'home/TabMenuStrip.java', 'home/TabMenuStripLayout.java', 'home/TopSitesGridItemView.java', 'home/TopSitesGridView.java', 'home/TopSitesPanel.java', 'home/TopSitesThumbnailView.java', 'home/TransitionAwareCursorLoaderCallbacks.java', 'home/TwoLinePageRow.java', + 'InputConnectionListener.java', 'InputMethods.java', 'IntentHelper.java', 'javaaddons/JavaAddonManager.java', 'javaaddons/JavaAddonManagerV1.java', 'LayoutInterceptor.java', 'LocaleManager.java', 'Locales.java', 'lwt/LightweightTheme.java',
--- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -2881,17 +2881,20 @@ Http2Session::ProcessSlowConsumer(Http2S nsresult rv = slowConsumer->WriteSegments(this, count, countWritten); mSegmentWriter = nullptr; LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %X %d\n", this, slowConsumer->StreamID(), rv, *countWritten)); if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) { rv = NS_BASE_STREAM_CLOSED; } - if (NS_SUCCEEDED(rv)) { + if (NS_SUCCEEDED(rv) && (*countWritten > 0)) { + // There have been buffered bytes successfully fed into the + // formerly blocked consumer. Repeat until buffer empty or + // consumer is blocked again. UpdateLocalRwin(slowConsumer, 0); ConnectSlowConsumer(slowConsumer); } if (rv == NS_BASE_STREAM_CLOSED) { CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR); rv = NS_OK; } @@ -3184,17 +3187,18 @@ Http2Session::OnWriteSegment(char *buf, nsresult rv; if (!mSegmentWriter) { // the only way this could happen would be if Close() were called on the // stack with WriteSegments() return NS_ERROR_FAILURE; } - if (mDownstreamState == NOT_USING_NETWORK) { + if (mDownstreamState == NOT_USING_NETWORK || + mDownstreamState == BUFFERING_FRAME_HEADER) { return NS_BASE_STREAM_WOULD_BLOCK; } if (mDownstreamState == PROCESSING_DATA_FRAME) { if (mInputFrameFinal && mInputFrameDataRead == mInputFrameDataSize) { *countWritten = 0;
--- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -319,18 +319,30 @@ Http2Stream::WriteSegments(nsAHttpSegmen mSegmentWriter = writer; nsresult rv = mTransaction->WriteSegments(this, count, countWritten); if (rv == NS_BASE_STREAM_WOULD_BLOCK) { // consuming transaction won't take data. but we need to read it into a buffer so that it // won't block other streams. but we should not advance the flow control window // so that we'll eventually push back on the sender. + // with tunnels you need to make sure that this is an underlying connction established + // that can be meaningfully giving this signal + bool doBuffer = true; + if (mIsTunnel) { + nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction()); + if (qiTrans) { + doBuffer = qiTrans->ConnectedReadyForInput(); + } + } // stash this data - rv = BufferInput(count, countWritten); + if (doBuffer) { + rv = BufferInput(count, countWritten); + LOG3(("Http2Stream::WriteSegments %p Buffered %X %d\n", this, rv, *countWritten)); + } } mSegmentWriter = nullptr; return rv; } nsresult Http2Stream::MakeOriginURL(const nsACString &origin, nsRefPtr<nsStandardURL> &url) {
--- a/netwerk/protocol/http/TunnelUtils.cpp +++ b/netwerk/protocol/http/TunnelUtils.cpp @@ -1258,16 +1258,22 @@ SpdyConnectTransaction::WriteSegments(ns "goodput %p out %llu\n", this, mTunneledConn.get(), mTunneledConn->ContentBytesWritten())); if (NS_SUCCEEDED(rv) && !mTunneledConn->ContentBytesWritten()) { mTunnelStreamOut->AsyncWait(mTunnelStreamOut->mCallback, 0, 0, nullptr); } return rv; } +bool +SpdyConnectTransaction::ConnectedReadyForInput() +{ + return mTunneledConn && mTunnelStreamIn->mCallback; +} + nsHttpRequestHead * SpdyConnectTransaction::RequestHead() { return mRequestHead; } void SpdyConnectTransaction::Close(nsresult code)
--- a/netwerk/protocol/http/TunnelUtils.h +++ b/netwerk/protocol/http/TunnelUtils.h @@ -196,16 +196,20 @@ public: nsresult ReadSegments(nsAHttpSegmentReader *reader, uint32_t count, uint32_t *countRead) override final; nsresult WriteSegments(nsAHttpSegmentWriter *writer, uint32_t count, uint32_t *countWritten) override final; nsHttpRequestHead *RequestHead() override final; void Close(nsresult reason) override final; + // ConnectedReadyForInput() tests whether the spdy connect transaction is attached to + // an nsHttpConnection that can properly deal with flow control, etc.. + bool ConnectedReadyForInput(); + private: friend class InputStreamShim; friend class OutputStreamShim; nsresult Flush(uint32_t count, uint32_t *countRead); void CreateShimError(nsresult code); nsCString mConnectString;
--- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -2109,19 +2109,27 @@ nsHttpConnection::GetInterface(const nsI return callbacks->GetInterface(iid, result); return NS_ERROR_NO_INTERFACE; } void nsHttpConnection::CheckForTraffic(bool check) { if (check) { + LOG((" CheckForTraffic conn %p\n", this)); if (mSpdySession) { - // Send a ping to verify it is still alive - mSpdySession->SendPing(); + if (PR_IntervalToMilliseconds(IdleTime()) >= 500) { + // Send a ping to verify it is still alive if it has been idle + // more than half a second, the network changed events are + // rate-limited to one per 1000 ms. + LOG((" SendPing\n")); + mSpdySession->SendPing(); + } else { + LOG((" SendPing skipped due to network activity\n")); + } } else { // If not SPDY, Store snapshot amount of data right now mTrafficCount = mTotalBytesWritten + mTotalBytesRead; mTrafficStamp = true; } } else { // mark it as not checked mTrafficStamp = false;
--- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -535,17 +535,17 @@ nsHttpTransaction::OnTransportStatus(nsI // If the timing is enabled, and we are not using a persistent connection // then the requestStart timestamp will be null, so we mark the timestamps // for domainLookupStart/End and connectStart/End // If we are using a persistent connection they will remain null, // and the correct value will be returned in nsPerformance. if (TimingEnabled() && GetRequestStart().IsNull()) { if (status == NS_NET_STATUS_RESOLVING_HOST) { - SetDomainLookupStart(TimeStamp::Now()); + SetDomainLookupStart(TimeStamp::Now(), true); } else if (status == NS_NET_STATUS_RESOLVED_HOST) { SetDomainLookupEnd(TimeStamp::Now()); } else if (status == NS_NET_STATUS_CONNECTING_TO) { SetConnectStart(TimeStamp::Now()); } else if (status == NS_NET_STATUS_CONNECTED_TO) { SetConnectEnd(TimeStamp::Now()); } }
--- a/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp +++ b/netwerk/system/linux/nsNotifyAddrListener_Linux.cpp @@ -31,16 +31,20 @@ #ifdef MOZ_WIDGET_GONK #include <cutils/properties.h> #endif /* a shorter name that better explains what it does */ #define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x) +// period during which to absorb subsequent network change events, in +// milliseconds +static const unsigned int kNetworkChangeCoalescingPeriod = 1000; + using namespace mozilla; static PRLogModuleInfo *gNotifyAddrLog = nullptr; #define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args) #define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed" NS_IMPL_ISUPPORTS(nsNotifyAddrListener, @@ -48,16 +52,17 @@ NS_IMPL_ISUPPORTS(nsNotifyAddrListener, nsIRunnable, nsIObserver) nsNotifyAddrListener::nsNotifyAddrListener() : mLinkUp(true) // assume true by default , mStatusKnown(false) , mAllowChangedEvent(true) , mChildThreadShutdown(false) + , mCoalescingActive(false) { mShutdownPipe[0] = -1; mShutdownPipe[1] = -1; } nsNotifyAddrListener::~nsNotifyAddrListener() { MOZ_ASSERT(!mThread, "nsNotifyAddrListener thread shutdown failed"); @@ -218,17 +223,17 @@ void nsNotifyAddrListener::OnNetlinkMess break; default: continue; } } if (networkChange && mAllowChangedEvent) { - SendEvent(NS_NETWORK_LINK_DATA_CHANGED); +