diff --git a/browser/locales/all-locales b/browser/locales/all-locales index 65c45d76e1f..51bdab26d02 100644 --- a/browser/locales/all-locales +++ b/browser/locales/all-locales @@ -1,88 +1,40 @@ -af -ak ar -as ast -be -bg -bn-BD -bn-IN -br -bs -ca cs -cy -da de -el en-GB -en-ZA eo es-AR es-CL es-ES es-MX -et -eu fa -fi fr fy-NL -ga-IE -gd gl -gu-IN he -hi-IN -hr hu -hy-AM id -is it ja ja-JP-mac -ka kk -km -kn ko -ku -lg lt lv -mai -mk -ml -mn -mr nb-NO nl nn-NO -nso oc -or -pa-IN pl pt-BR pt-PT -rm -ro ru -si sk -sl -son -sq -sr sv-SE -ta -ta-LK -te th tr uk vi zh-CN zh-TW -zu diff --git a/configure.in b/configure.in index a7412ab73ab..5f492842986 100644 --- a/configure.in +++ b/configure.in @@ -6483,7 +6483,8 @@ fi MOZ_ARG_ENABLE_BOOL(content-sandbox, [ --enable-content-sandbox Enable sandboxing support for content-processes], - MOZ_CONTENT_SANDBOX=1) + MOZ_CONTENT_SANDBOX=1, + MOZ_CONTENT_SANDBOX=) if test -n "$MOZ_CONTENT_SANDBOX"; then AC_DEFINE(MOZ_CONTENT_SANDBOX) @@ -6493,7 +6494,8 @@ AC_SUBST(MOZ_CONTENT_SANDBOX) MOZ_ARG_ENABLE_BOOL(content-sandbox-reporter, [ --enable-content-sandbox-reporter Enable syscall reporter to troubleshoot syscalls denied by the content-processes sandbox], - MOZ_CONTENT_SANDBOX_REPORTER=1) + MOZ_CONTENT_SANDBOX_REPORTER=1, + MOZ_CONTENT_SANDBOX_REPORTER=) if test -n "$MOZ_CONTENT_SANDBOX_REPORTER"; then AC_DEFINE(MOZ_CONTENT_SANDBOX_REPORTER) diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index e54dec3abe4..07f66f6d593 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -26,6 +26,8 @@ #include "nsPrintfCString.h" #include "prprf.h" +#include "mozilla/dom/DOMError.h" +#include "mozilla/dom/DOMErrorBinding.h" #include "mozilla/dom/HTMLObjectElement.h" #include "mozilla/dom/HTMLObjectElementBinding.h" #include "mozilla/dom/HTMLSharedObjectElement.h" @@ -185,6 +187,41 @@ ErrorResult::ReportJSException(JSContext* cx) JS_RemoveValueRoot(cx, &mJSException); } +void +ErrorResult::ReportJSExceptionFromJSImplementation(JSContext* aCx) +{ + MOZ_ASSERT(!mMightHaveUnreportedJSException, + "Why didn't you tell us you planned to handle JS exceptions?"); + + dom::DOMError* domError; + nsresult rv = UNWRAP_OBJECT(DOMError, aCx, &mJSException.toObject(), + domError); + if (NS_FAILED(rv)) { + // Unwrapping really shouldn't fail here, if mExceptionHandling is set to + // eRethrowContentExceptions then the CallSetup destructor only stores an + // exception if it unwraps to DOMError. If we reach this then either + // mExceptionHandling wasn't set to eRethrowContentExceptions and we + // shouldn't be calling ReportJSExceptionFromJSImplementation or something + // went really wrong. + NS_RUNTIMEABORT("We stored a non-DOMError exception!"); + } + + nsString message; + domError->GetMessage(message); + + JSErrorReport errorReport; + memset(&errorReport, 0, sizeof(JSErrorReport)); + errorReport.errorNumber = JSMSG_USER_DEFINED_ERROR; + errorReport.ucmessage = message.get(); + errorReport.exnType = JSEXN_ERR; + JS_ThrowReportedError(aCx, nullptr, &errorReport); + JS_RemoveValueRoot(aCx, &mJSException); + + // We no longer have a useful exception but we do want to signal that an error + // occured. + mResult = NS_ERROR_FAILURE; +} + void ErrorResult::StealJSException(JSContext* cx, JS::MutableHandle value) diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index c469cee4737..884875ca1bb 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -89,14 +89,19 @@ template inline bool ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv, const char* ifaceName, - const char* memberName) + const char* memberName, + bool reportJSContentExceptions = false) { if (rv.IsTypeError()) { rv.ReportTypeError(cx); return false; } if (rv.IsJSException()) { - rv.ReportJSException(cx); + if (reportJSContentExceptions) { + rv.ReportJSExceptionFromJSImplementation(cx); + } else { + rv.ReportJSException(cx); + } return false; } if (rv.IsNotEnoughArgsError()) { @@ -183,8 +188,8 @@ IsDOMObject(JSObject* obj) } #define UNWRAP_OBJECT(Interface, cx, obj, value) \ - UnwrapObject(cx, obj, value) + mozilla::dom::UnwrapObject(cx, obj, value) // Some callers don't want to set an exception when unwrapping fails // (for example, overload resolution uses unwrapping to tell what sort diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp index 6632cc807fb..c296545381c 100644 --- a/dom/bindings/CallbackObject.cpp +++ b/dom/bindings/CallbackObject.cpp @@ -5,6 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/CallbackObject.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMError.h" +#include "mozilla/dom/DOMErrorBinding.h" #include "jsfriendapi.h" #include "nsIScriptGlobalObject.h" #include "nsIXPConnect.h" @@ -40,8 +43,10 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END CallbackObject::CallSetup::CallSetup(JS::Handle aCallback, ErrorResult& aRv, - ExceptionHandling aExceptionHandling) + ExceptionHandling aExceptionHandling, + JSCompartment* aCompartment) : mCx(nullptr) + , mCompartment(aCompartment) , mErrorResult(aRv) , mExceptionHandling(aExceptionHandling) { @@ -123,25 +128,55 @@ CallbackObject::CallSetup::CallSetup(JS::Handle aCallback, mCx = cx; // Make sure the JS engine doesn't report exceptions we want to re-throw - if (mExceptionHandling == eRethrowExceptions) { + if (mExceptionHandling == eRethrowContentExceptions || + mExceptionHandling == eRethrowExceptions) { mSavedJSContextOptions = JS_GetOptions(cx); JS_SetOptions(cx, mSavedJSContextOptions | JSOPTION_DONT_REPORT_UNCAUGHT); } } +bool +CallbackObject::CallSetup::ShouldRethrowException(JS::Handle aException) +{ + if (mExceptionHandling == eRethrowExceptions) { + return true; + } + + MOZ_ASSERT(mExceptionHandling == eRethrowContentExceptions); + + // For eRethrowContentExceptions we only want to throw an exception if the + // object that was thrown is a DOMError object in the caller compartment + // (which we stored in mCompartment). + + if (!aException.isObject()) { + return false; + } + + JS::Rooted obj(mCx, &aException.toObject()); + obj = js::UncheckedUnwrap(obj, /* stopAtOuter = */ false); + if (js::GetObjectCompartment(obj) != mCompartment) { + return false; + } + + DOMError* domError; + return NS_SUCCEEDED(UNWRAP_OBJECT(DOMError, mCx, obj, domError)); +} + CallbackObject::CallSetup::~CallSetup() { // First things first: if we have a JSContext, report any pending // errors on it, unless we were told to re-throw them. if (mCx) { bool dealtWithPendingException = false; - if (mExceptionHandling == eRethrowExceptions) { + if (mExceptionHandling == eRethrowContentExceptions || + mExceptionHandling == eRethrowExceptions) { // Restore the old context options JS_SetOptions(mCx, mSavedJSContextOptions); mErrorResult.MightThrowJSException(); if (JS_IsExceptionPending(mCx)) { JS::Rooted exn(mCx); - if (JS_GetPendingException(mCx, exn.address())) { + if (JS_GetPendingException(mCx, exn.address()) && + ShouldRethrowException(exn)) { mErrorResult.ThrowJSException(mCx, exn); JS_ClearPendingException(mCx); dealtWithPendingException = true; diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h index 248597287a6..f6ca144eed2 100644 --- a/dom/bindings/CallbackObject.h +++ b/dom/bindings/CallbackObject.h @@ -78,7 +78,13 @@ public: } enum ExceptionHandling { + // Report any exception and don't throw it to the caller code. eReportExceptions, + // Throw an exception to the caller code if the thrown exception is a + // binding object for a DOMError from the caller's scope, otherwise report + // it. + eRethrowContentExceptions, + // Throw any exception to the caller code. eRethrowExceptions }; @@ -118,8 +124,11 @@ protected: * non-null. */ public: + // If aExceptionHandling == eRethrowContentExceptions then aCompartment + // needs to be set to the caller's compartment. CallSetup(JS::Handle aCallable, ErrorResult& aRv, - ExceptionHandling aExceptionHandling); + ExceptionHandling aExceptionHandling, + JSCompartment* aCompartment = nullptr); ~CallSetup(); JSContext* GetContext() const @@ -131,9 +140,15 @@ protected: // We better not get copy-constructed CallSetup(const CallSetup&) MOZ_DELETE; + bool ShouldRethrowException(JS::Handle aException); + // Members which can go away whenever JSContext* mCx; + // Caller's compartment. This will only have a sensible value if + // mExceptionHandling == eRethrowContentExceptions. + JSCompartment* mCompartment; + // And now members whose construction/destruction order we need to control. // Put our nsAutoMicrotask first, so it gets destroyed after everything else diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index cfbf934e372..3b4f7c978f8 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -4807,15 +4807,26 @@ if (global.Failed()) { if needsCx and not (static and descriptor.workers): argsPre.append("cx") - needsUnwrap = isConstructor - if needScopeObject(returnType, arguments, self.extendedAttributes, - descriptor, descriptor.wrapperCache, - not descriptor.interface.isJSImplemented()): + needsUnwrap = False + if isConstructor: + needsUnwrap = True + needsUnwrappedVar = False + unwrappedVar = "obj" + elif descriptor.interface.isJSImplemented(): + needsUnwrap = True + needsUnwrappedVar = True + argsPre.append("js::GetObjectCompartment(unwrappedObj.empty() ? obj : unwrappedObj.ref())") + elif needScopeObject(returnType, arguments, self.extendedAttributes, + descriptor, descriptor.wrapperCache, True): + needsUnwrap = True + needsUnwrappedVar = True + argsPre.append("unwrappedObj.empty() ? obj : unwrappedObj.ref()") + + if needsUnwrap and needsUnwrappedVar: # We cannot assign into obj because it's a Handle, not a # MutableHandle, so we need a separate Rooted. cgThings.append(CGGeneric("Maybe > unwrappedObj;")) - argsPre.append("unwrappedObj.empty() ? obj : unwrappedObj.ref()") - needsUnwrap = True + unwrappedVar = "unwrappedObj.ref()" if idlNode.isMethod() and idlNode.isLegacycaller(): # If we can have legacycaller with identifier, we can't @@ -4844,7 +4855,7 @@ if (global.Failed()) { if needsUnwrap: # Something depends on having the unwrapped object, so unwrap it now. xraySteps = [] - if not isConstructor: + if needsUnwrappedVar: xraySteps.append( CGGeneric("unwrappedObj.construct(cx, obj);")) @@ -4854,7 +4865,7 @@ if (global.Failed()) { CGGeneric(string.Template("""${obj} = js::CheckedUnwrap(${obj}); if (!${obj}) { return false; -}""").substitute({ 'obj' : 'obj' if isConstructor else 'unwrappedObj.ref()' }))) +}""").substitute({ 'obj' : unwrappedVar }))) if isConstructor: # If we're called via an xray, we need to enter the underlying # object's compartment and then wrap up all of our arguments into @@ -4915,10 +4926,14 @@ if (!${obj}) { self.idlNode.identifier.name)) def getErrorReport(self): - return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s");' + jsImplemented = "" + if self.descriptor.interface.isJSImplemented(): + jsImplemented = ", true" + return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s"%s);' % (toStringBool(not self.descriptor.workers), self.descriptor.interface.identifier.name, - self.idlNode.identifier.name)) + self.idlNode.identifier.name, + jsImplemented)) def define(self): return (self.cgRoot.define() + "\n" + self.wrap_return_value()) @@ -9509,9 +9524,38 @@ class CGExampleRoot(CGThing): def jsImplName(name): return name + "JSImpl" -class CGJSImplMethod(CGNativeMember): +class CGJSImplMember(CGNativeMember): + """ + Base class for generating code for the members of the implementation class + for a JS-implemented WebIDL interface. + """ + def __init__(self, descriptorProvider, member, name, signature, + extendedAttrs, breakAfter=True, passJSBitsAsNeeded=True, + visibility="public", jsObjectsArePtr=False, + variadicIsSequence=False): + CGNativeMember.__init__(self, descriptorProvider, member, name, + signature, extendedAttrs, breakAfter=breakAfter, + passJSBitsAsNeeded=passJSBitsAsNeeded, + visibility=visibility, + jsObjectsArePtr=jsObjectsArePtr, + variadicIsSequence=variadicIsSequence) + self.body = self.getImpl() + + def getArgs(self, returnType, argList): + args = CGNativeMember.getArgs(self, returnType, argList) + args.insert(0, Argument("JSCompartment*", "aCompartment")) + return args + +class CGJSImplMethod(CGJSImplMember): + """ + Class for generating code for the methods for a JS-implemented WebIDL + interface. + """ def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True): - CGNativeMember.__init__(self, descriptor, method, + self.signature = signature + self.descriptor = descriptor + self.isConstructor = isConstructor + CGJSImplMember.__init__(self, descriptor, method, CGSpecializedMethod.makeNativeName(descriptor, method), signature, @@ -9519,29 +9563,29 @@ class CGJSImplMethod(CGNativeMember): breakAfter=breakAfter, variadicIsSequence=True, passJSBitsAsNeeded=False) - self.signature = signature - self.descriptor = descriptor - if isConstructor: - self.body = self.getConstructorImpl() - else: - self.body = self.getImpl() + + def getArgs(self, returnType, argList): + if self.isConstructor: + # Skip the JSCompartment bits for constructors; it's handled + # manually in getImpl. + return CGNativeMember.getArgs(self, returnType, argList) + return CGJSImplMember.getArgs(self, returnType, argList) def getImpl(self): - callbackArgs = [arg.name for arg in self.getArgs(self.signature[0], self.signature[1])] - return 'return mImpl->%s(%s);' % (self.name, ", ".join(callbackArgs)) + args = self.getArgs(self.signature[0], self.signature[1]) + if not self.isConstructor: + return 'return mImpl->%s(%s);' % (self.name, ", ".join(arg.name for arg in args)) - def getConstructorImpl(self): assert self.descriptor.interface.isJSImplemented() if self.name != 'Constructor': raise TypeError("Named constructors are not supported for JS implemented WebIDL. See bug 851287.") if len(self.signature[1]) != 0: - args = self.getArgs(self.signature[0], self.signature[1]) # The first two arguments to the constructor implementation are not # arguments to the WebIDL constructor, so don't pass them to __Init() assert args[0].argType == 'const GlobalObject&' assert args[1].argType == 'JSContext*' - args = args[2:] - constructorArgs = [arg.name for arg in args] + constructorArgs = [arg.name for arg in args[2:]] + constructorArgs.insert(0, "js::GetObjectCompartment(scopeObj)") initCall = """ // Wrap the object before calling __Init so that __DOM_IMPL__ is available. nsCOMPtr globalHolder = do_QueryInterface(window); @@ -9588,24 +9632,31 @@ def callbackGetterName(attr): def callbackSetterName(attr): return "Set" + MakeNativeName(attr.identifier.name) -class CGJSImplGetter(CGNativeMember): +class CGJSImplGetter(CGJSImplMember): + """ + Class for generating code for the getters of attributes for a JS-implemented + WebIDL interface. + """ def __init__(self, descriptor, attr): - CGNativeMember.__init__(self, descriptor, attr, + CGJSImplMember.__init__(self, descriptor, attr, CGSpecializedGetter.makeNativeName(descriptor, attr), (attr.type, []), descriptor.getExtendedAttributes(attr, getter=True), passJSBitsAsNeeded=False) - self.body = self.getImpl() def getImpl(self): callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])] return 'return mImpl->%s(%s);' % (callbackGetterName(self.member), ", ".join(callbackArgs)) -class CGJSImplSetter(CGNativeMember): +class CGJSImplSetter(CGJSImplMember): + """ + Class for generating code for the setters of attributes for a JS-implemented + WebIDL interface. + """ def __init__(self, descriptor, attr): - CGNativeMember.__init__(self, descriptor, attr, + CGJSImplMember.__init__(self, descriptor, attr, CGSpecializedSetter.makeNativeName(descriptor, attr), (BuiltinTypes[IDLBuiltinType.Types.void], @@ -9613,7 +9664,6 @@ class CGJSImplSetter(CGNativeMember): descriptor.getExtendedAttributes(attr, setter=True), passJSBitsAsNeeded=False) - self.body = self.getImpl() def getImpl(self): callbackArgs = [arg.name for arg in self.getArgs(BuiltinTypes[IDLBuiltinType.Types.void], @@ -9929,11 +9979,13 @@ class FakeMember(): return None class CallbackMember(CGNativeMember): - def __init__(self, sig, name, descriptorProvider, needThisHandling): + def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False): """ needThisHandling is True if we need to be able to accept a specified thisObj, False otherwise. """ + assert not rethrowContentException or not needThisHandling + self.retvalType = sig[0] self.originalSig = sig args = sig[1] @@ -9951,6 +10003,7 @@ class CallbackMember(CGNativeMember): # If needThisHandling, we generate ourselves as private and the caller # will handle generating public versions that handle the "this" stuff. visibility = "private" if needThisHandling else "public" + self.rethrowContentException = rethrowContentException # We don't care, for callback codegen, whether our original member was # a method or attribute or whatnot. Just always pass FakeMember() # here. @@ -10109,8 +10162,12 @@ class CallbackMember(CGNativeMember): if not self.needThisHandling: # Since we don't need this handling, we're the actual method that # will be called, so we need an aRethrowExceptions argument. - return args + [Argument("ExceptionHandling", "aExceptionHandling", - "eReportExceptions")] + if self.rethrowContentException: + args.insert(0, Argument("JSCompartment*", "aCompartment")) + else: + args.append(Argument("ExceptionHandling", "aExceptionHandling", + "eReportExceptions")) + return args # We want to allow the caller to pass in a "this" object, as # well as a JSContext. return [Argument("JSContext*", "cx"), @@ -10120,13 +10177,22 @@ class CallbackMember(CGNativeMember): if self.needThisHandling: # It's been done for us already return "" + callSetup = "CallSetup s(CallbackPreserveColor(), aRv" + if self.rethrowContentException: + # getArgs doesn't add the aExceptionHandling argument but does add + # aCompartment for us. + callSetup += ", eRethrowContentExceptions, aCompartment" + else: + callSetup += ", aExceptionHandling" + callSetup += ");" return string.Template( - "CallSetup s(CallbackPreserveColor(), aRv, aExceptionHandling);\n" + "${callSetup}\n" "JSContext* cx = s.GetContext();\n" "if (!cx) {\n" " aRv.Throw(NS_ERROR_UNEXPECTED);\n" " return${errorReturn};\n" "}\n").substitute({ + "callSetup": callSetup, "errorReturn" : self.getDefaultRetval(), }) @@ -10149,9 +10215,9 @@ class CallbackMember(CGNativeMember): idlObject.location)) class CallbackMethod(CallbackMember): - def __init__(self, sig, name, descriptorProvider, needThisHandling): + def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False): CallbackMember.__init__(self, sig, name, descriptorProvider, - needThisHandling) + needThisHandling, rethrowContentException) def getRvalDecl(self): return "JS::Rooted rval(cx, JS::UndefinedValue());\n" @@ -10189,10 +10255,10 @@ class CallbackOperationBase(CallbackMethod): """ Common class for implementing various callback operations. """ - def __init__(self, signature, jsName, nativeName, descriptor, singleOperation): + def __init__(self, signature, jsName, nativeName, descriptor, singleOperation, rethrowContentException=False): self.singleOperation = singleOperation self.methodName = jsName - CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation) + CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException) def getThisObj(self): if not self.singleOperation: @@ -10232,7 +10298,8 @@ class CallbackOperation(CallbackOperationBase): jsName = method.identifier.name CallbackOperationBase.__init__(self, signature, jsName, MakeNativeName(jsName), - descriptor, descriptor.interface.isSingleOperationInterface()) + descriptor, descriptor.interface.isSingleOperationInterface(), + rethrowContentException=descriptor.interface.isJSImplemented()) class CallbackGetter(CallbackMember): def __init__(self, attr, descriptor): @@ -10242,7 +10309,8 @@ class CallbackGetter(CallbackMember): (attr.type, []), callbackGetterName(attr), descriptor, - needThisHandling=False) + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) def getRvalDecl(self): return "JS::Rooted rval(cx, JS::UndefinedValue());\n" @@ -10267,7 +10335,8 @@ class CallbackSetter(CallbackMember): [FakeArgument(attr.type, attr)]), callbackSetterName(attr), descriptor, - needThisHandling=False) + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) def getRvalDecl(self): # We don't need an rval @@ -10296,7 +10365,7 @@ class CGJSImplInitOperation(CallbackOperationBase): def __init__(self, sig, descriptor): assert sig in descriptor.interface.ctor().signatures() CallbackOperationBase.__init__(self, (BuiltinTypes[IDLBuiltinType.Types.void], sig[1]), - "__init", "__Init", descriptor, False) + "__init", "__Init", descriptor, False, True) class GlobalGenRoots(): """ diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h index 51b10eb3f83..19ecc238a40 100644 --- a/dom/bindings/ErrorResult.h +++ b/dom/bindings/ErrorResult.h @@ -70,11 +70,14 @@ public: // Facilities for throwing a preexisting JS exception value via this // ErrorResult. The contract is that any code which might end up calling // ThrowJSException() must call MightThrowJSException() even if no exception - // is being thrown. Code that would call ReportJSException or + // is being thrown. Code that would call ReportJSException* or // StealJSException as needed must first call WouldReportJSException even if // this ErrorResult has not failed. void ThrowJSException(JSContext* cx, JS::Handle exn); void ReportJSException(JSContext* cx); + // Used to implement throwing exceptions from the JS implementation of + // bindings to callers of the binding. + void ReportJSExceptionFromJSImplementation(JSContext* aCx); bool IsJSException() const { return ErrorCode() == NS_ERROR_DOM_JS_EXCEPTION; } void ThrowNotEnoughArgsError() { mResult = NS_ERROR_XPC_NOT_ENOUGH_ARGS; } diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js index 4306a0d52a6..44c0d6b6068 100644 --- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -359,30 +359,28 @@ RTCPeerConnection.prototype = { * ErrorMsg is passed in to detail which array-entry failed, if any. */ _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) { + var errorCtor = this._win.DOMError; function nicerNewURI(uriStr, errorMsg) { let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService); try { return ios.newURI(uriStr, null, null); } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) { - throw new Components.Exception(errorMsg + " - malformed URI: " + uriStr, - Cr.NS_ERROR_MALFORMED_URI); + throw new errorCtor("", errorMsg + " - malformed URI: " + uriStr); } } function mustValidateServer(server) { let url = nicerNewURI(server.url, errorMsg); if (url.scheme in { turn:1, turns:1 }) { if (!server.username) { - throw new Components.Exception(errorMsg + " - missing username: " + - server.url, Cr.NS_ERROR_MALFORMED_URI); + throw new errorCtor("", errorMsg + " - missing username: " + server.url); } if (!server.credential) { - throw new Components.Exception(errorMsg + " - missing credential: " + - server.url, Cr.NS_ERROR_MALFORMED_URI); + throw new errorCtor("", errorMsg + " - missing credential: " + + server.url); } } else if (!(url.scheme in { stun:1, stuns:1 })) { - throw new Components.Exception(errorMsg + " - improper scheme: " + url.scheme, - Cr.NS_ERROR_MALFORMED_URI); + throw new errorCtor("", errorMsg + " - improper scheme: " + url.scheme); } } if (rtcConfig.iceServers) { diff --git a/dom/tests/mochitest/chrome/window_focus.xul b/dom/tests/mochitest/chrome/window_focus.xul index bab772948c7..34a22d9f136 100644 --- a/dom/tests/mochitest/chrome/window_focus.xul +++ b/dom/tests/mochitest/chrome/window_focus.xul @@ -1127,9 +1127,16 @@ function otherWindowFocused(otherWindow) otherWindow.close(); - // next, check modal dialogs getById("n2").focus(); - var nextWindow = window.openDialog("focus_window2.xul", "_blank", "chrome,modal", modalWindowOpened); + + // next, check modal dialogs + // XXXndeakin Bug 621399 - modal dialog test sometimes fails on Windows 8 so disable it. + if (navigator.userAgent.indexOf("Windows NT 6.2") >= 0) { + window.open("focus_frameset.html", "_blank", "width=400,height=400,toolbar=no"); + } + else { + window.openDialog("focus_window2.xul", "_blank", "chrome,modal", modalWindowOpened); + } } function modalWindowOpened(modalWindow) diff --git a/js/src/TraceLogging.cpp b/js/src/TraceLogging.cpp index 6ff75ebd942..86692c52c38 100644 --- a/js/src/TraceLogging.cpp +++ b/js/src/TraceLogging.cpp @@ -65,7 +65,7 @@ rdtsc(void) } #endif -const char* const TraceLogging::type_name[] = { +const char* const TraceLogging::typeName[] = { "1,s", // start script "0,s", // stop script "1,c", // start ion compilation @@ -86,29 +86,31 @@ const char* const TraceLogging::type_name[] = { "e,b", // engine baseline "e,o" // engine ionmonkey }; -TraceLogging* TraceLogging::_defaultLogger = NULL; +TraceLogging* TraceLogging::loggers[] = {NULL, NULL}; +bool TraceLogging::atexitSet = false; +uint64_t TraceLogging::startupTime = 0; -TraceLogging::TraceLogging() - : startupTime(rdtsc()), - loggingTime(0), +TraceLogging::TraceLogging(Logger id) + : loggingTime(0), nextTextId(1), entries(NULL), curEntry(0), numEntries(1000000), fileno(0), - out(NULL) + out(NULL), + id(id) { textMap.init(); } TraceLogging::~TraceLogging() { - if (out != NULL) { + if (out) { fclose(out); out = NULL; } - if (entries != NULL) { + if (entries) { flush(); free(entries); entries = NULL; @@ -122,7 +124,7 @@ TraceLogging::grow() // Allocating a bigger array failed. // Keep using the current storage, but remove all entries by flushing them. - if (nentries == NULL) { + if (!nentries) { flush(); return; } @@ -195,8 +197,19 @@ void TraceLogging::flush() { // Open the logging file, when not opened yet. - if (out == NULL) - out = fopen(TRACE_LOG_DIR "tracelogging.log", "w"); + if (!out) { + switch(id) { + case DEFAULT: + out = fopen(TRACE_LOG_DIR "tracelogging.log", "w"); + break; + case ION_BACKGROUND_COMPILER: + out = fopen(TRACE_LOG_DIR "tracelogging-compile.log", "w"); + break; + default: + MOZ_ASSUME_UNREACHABLE("Bad trigger"); + return; + } + } // Print all log entries into the file for (unsigned int i = 0; i < curEntry; i++) { @@ -209,36 +222,29 @@ TraceLogging::flush() if (entry.text()) { written = fprintf(out, "%llu,%s,%s,%d\n", (unsigned long long)entry.tick(), - type_name[entry.type()], + typeName[entry.type()], entry.text(), entry.lineno()); } else { written = fprintf(out, "%llu,%s,%d,%d\n", (unsigned long long)entry.tick(), - type_name[entry.type()], + typeName[entry.type()], entry.textId(), entry.lineno()); } } else { written = fprintf(out, "%llu,%s\n", (unsigned long long)entry.tick(), - type_name[entry.type()]); + typeName[entry.type()]); } } // A logging file can only be 2GB of length (fwrite limit). - // When we exceed this limit, the writing will fail. - // In that case try creating a extra file to write the log entries. if (written < 0) { + fprintf(stderr, "Writing tracelog to disk failed,"); + fprintf(stderr, "probably because the file would've exceeded the maximum size of 2GB"); fclose(out); - if (fileno >= 9999) - exit(-1); - - char filename[21 + sizeof(TRACE_LOG_DIR)]; - sprintf (filename, TRACE_LOG_DIR "tracelogging-%d.log", ++fileno); - out = fopen(filename, "w"); - i--; // Try to print message again - continue; + exit(-1); } if (entries[i].text() != NULL) { @@ -250,21 +256,29 @@ TraceLogging::flush() } TraceLogging* -TraceLogging::defaultLogger() +TraceLogging::getLogger(Logger id) { - if (_defaultLogger == NULL) { - _defaultLogger = new TraceLogging(); - atexit (releaseDefaultLogger); + if (!loggers[id]) { + loggers[id] = new TraceLogging(id); + if (!atexitSet) { + startupTime = rdtsc(); + atexit (releaseLoggers); + atexitSet = true; + } } - return _defaultLogger; + + return loggers[id]; } void -TraceLogging::releaseDefaultLogger() +TraceLogging::releaseLoggers() { - if (_defaultLogger != NULL) { - delete _defaultLogger; - _defaultLogger = NULL; + for (size_t i = 0; i < LAST_LOGGER; i++) { + if (!loggers[i]) + continue; + + delete loggers[i]; + loggers[i] = NULL; } } diff --git a/js/src/TraceLogging.h b/js/src/TraceLogging.h index f1b0ff12aef..c301c6b032e 100644 --- a/js/src/TraceLogging.h +++ b/js/src/TraceLogging.h @@ -46,6 +46,12 @@ class TraceLogging INFO_ENGINE_IONMONKEY, INFO }; + enum Logger { + DEFAULT, + ION_BACKGROUND_COMPILER, + + LAST_LOGGER + }; private: struct Entry { @@ -74,7 +80,6 @@ class TraceLogging PointerHasher, SystemAllocPolicy> TextHashMap; - uint64_t startupTime; uint64_t loggingTime; TextHashMap textMap; uint32_t nextTextId; @@ -83,11 +88,14 @@ class TraceLogging unsigned int numEntries; int fileno; FILE *out; + Logger id; - static const char * const type_name[]; - static TraceLogging* _defaultLogger; + static bool atexitSet; + static const char * const typeName[]; + static TraceLogging* loggers[]; + static uint64_t startupTime; public: - TraceLogging(); + TraceLogging(Logger id); ~TraceLogging(); void log(Type type, const char* text = NULL, unsigned int number = 0); @@ -96,8 +104,11 @@ class TraceLogging void log(const char* log); void flush(); - static TraceLogging* defaultLogger(); - static void releaseDefaultLogger(); + static TraceLogging* getLogger(Logger id); + static TraceLogging* defaultLogger() { + return getLogger(DEFAULT); + } + static void releaseLoggers(); private: void grow(); diff --git a/js/src/assembler/assembler/X86Assembler.h b/js/src/assembler/assembler/X86Assembler.h index 04cf1b92632..538339ea81b 100644 --- a/js/src/assembler/assembler/X86Assembler.h +++ b/js/src/assembler/assembler/X86Assembler.h @@ -1496,6 +1496,24 @@ public: m_formatter.oneByteOp_disp32(OP_MOV_EvGv, src, base, offset); } + void movw_rm(RegisterID src, int offset, RegisterID base, RegisterID index, int scale) + { + spew("movw %s, %d(%s,%s,%d)", + nameIReg(2, src), offset, nameIReg(base), nameIReg(index), 1<>0]|0; } return f'), this, null, buf)(),255); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[8192&8191] = -1; u8[0] = 0; return u8[8192&8191]|0; } return f'), this, null, buf)(),0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[(8192&8191)>>0] = -1; u8[0] = 0; return u8[(8192&8191)>>0]|0; } return f'), this, null, buf)(),0); +new Int8Array(buf)[8191] = -1; +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i8[8191&8191]|0; } return f'), this, null, buf)(),-1); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i8[(8191&8191)>>0]|0; } return f'), this, null, buf)(),-1); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[8192&8191] = -1; i8[0] = 0; return i8[8192&8191]|0; } return f'), this, null, buf)(),0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[(8192&8191)>>0] = -1; i8[0] = 0; return i8[(8192&8191)>>0]|0; } return f'), this, null, buf)(),0); +var buf = new ArrayBuffer(8192); +new Uint16Array(buf)[4095] = -1; +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u16[(8190&8191)>>1]|0; } return f'), this, null, buf)(),65535); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u16[(8191&8191)>>1]|0; } return f'), this, null, buf)(),65535); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u16[(8192&8191)>>1] = -1; u16[0] = 0; return u16[(8192&8191)>>1]|0; } return f'), this, null, buf)(),0); +new Int16Array(buf)[4095] = -1; +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i16[(8190&8191)>>1]|0; } return f'), this, null, buf)(),-1); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i16[(8191&8191)>>1]|0; } return f'), this, null, buf)(),-1); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i16[(8192&8191)>>1] = -1; i16[0] = 0; return i16[(8192&8191)>>1]|0; } return f'), this, null, buf)(),0); + +var buf = new ArrayBuffer(8192); +new Uint32Array(buf)[2047] = -1; +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(8188&8191)>>2]|0; } return f'), this, null, buf)(),-1); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(8191&8191)>>2]|0; } return f'), this, null, buf)(),-1); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u32[(8192&8191)>>2] = -1; u32[0] = 0; return u32[(8192&8191)>>2]|0; } return f'), this, null, buf)(),0); +new Int32Array(buf)[2047] = -1; +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i32[(8188&8191)>>2]|0; } return f'), this, null, buf)(),-1); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i32[(8191&8191)>>2]|0; } return f'), this, null, buf)(),-1); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i32[(8192&8191)>>2] = -1; i32[0] = 0; return i32[(8192&8191)>>2]|0; } return f'), this, null, buf)(),0); + +var buf = new ArrayBuffer(8192); +new Float32Array(buf)[2047] = -1.0; +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[(8188&8191)>>2]; } return f'), this, null, buf)(),-1.0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[(8191&8191)>>2]; } return f'), this, null, buf)(),-1.0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f32[(8192&8191)>>2] = -1.0; f32[0] = 0.0; return +f32[(8192&8191)>>2]; } return f'), this, null, buf)(),0.0); + +var buf = new ArrayBuffer(8192); +new Float64Array(buf)[1023] = -1.0; +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[(8184&8191)>>3]; } return f'), this, null, buf)(),-1.0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[(8191&8191)>>3]; } return f'), this, null, buf)(),-1.0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f64[(8192&8191)>>3] = -1.0; f64[0] = 0.0; return +f64[(8192&8191)>>3]; } return f'), this, null, buf)(),0.0); + + // Bug 882012 assertEq(asmLink(asmCompile('stdlib', 'foreign', 'heap', USE_ASM + "var id=foreign.id;var doubles=new stdlib.Float64Array(heap);function g(){doubles[0]=+id(2.0);return +doubles[0];}return g"), this, {id: function(x){return x;}}, BUF_64KB)(), 2.0); diff --git a/js/src/jit-test/tests/asm.js/testZOOB.js b/js/src/jit-test/tests/asm.js/testZOOB.js index 48979ca71ad..59370282005 100644 --- a/js/src/jit-test/tests/asm.js/testZOOB.js +++ b/js/src/jit-test/tests/asm.js/testZOOB.js @@ -2,15 +2,49 @@ load(libdir + "asm.js"); // constants var buf = new ArrayBuffer(4096); -assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x7fffffff]|0 } return f'), this, null, buf)(), 0); -assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x80000000]|0 } return f'), this, null, buf)(), 0); -assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x8fffffff]|0 } return f'), this, null, buf)(), 0); -assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0xffffffff]|0 } return f'), this, null, buf)(), 0); -assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[-1]|0 } return f'), this, null, buf)(), 0); -assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[-2]|0 } return f'), this, null, buf)(), 0); + +// An unshifted literal constant byte index in the range 0 to 2^31-1 inclusive should give a link failure. +assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x7fffffff]|0 } return f'), this, null, buf); +assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x1fffffff]|0 } return f'), this, null, buf); + + +// An unshifted literal constant byte index outside the range 0 to 2^31-1 inclusive should cause an error compiling. +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x20000000]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x3fffffff]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x40000000]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x7fffffff]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x80000000]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x8fffffff]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0xffffffff]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x100000000]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x80000000]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0xffffffff]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x100000000]|0 } return f'); assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int16Array(b); function f() {return arr[-1]|0 } return f'); assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[-2]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[10-12]|0 } return f'); + +// An intish shifted literal constant index should not fail to compile or link. +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x3fffffff>>0]|0 } return f'), this, null, buf)(), 0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x3fffffff>>2]|0 } return f'), this, null, buf)(), 0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0xffffffff>>0]|0 } return f'), this, null, buf)(), 0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0xffffffff>>2]|0 } return f'), this, null, buf)(), 0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[-1>>0]|0 } return f'), this, null, buf)(), 0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[-1>>2]|0 } return f'), this, null, buf)(), 0); +// Unsigned (intish) folded constant index. +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0xffffffff>>>0]|0 } return f'), this, null, buf)(), 0); +assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {arr[0] = 1; return arr[(0xffffffff+1)>>>0]|0 } return f'), this, null, buf)(), 1); + +// A non-intish shifted literal constant index should cause an error compiling. +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x100000000>>0]|0 } return f'); +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x100000000>>2]|0 } return f'); + +// Folded non-intish constant expressions should cause an error compiling. +assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0xffffffff+1]|0 } return f'); + + + function testInt(ctor, shift, scale, disp) { var ab = new ArrayBuffer(4096); var arr = new ctor(ab); diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp index 1017cddd76e..f960fde3772 100644 --- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -909,6 +909,11 @@ TypedArrayStoreType(ArrayBufferView::ViewType viewType) MOZ_ASSUME_UNREACHABLE("Unexpected array type"); } +enum NeedsBoundsCheck { + NO_BOUNDS_CHECK, + NEEDS_BOUNDS_CHECK +}; + /*****************************************************************************/ namespace { @@ -1473,6 +1478,15 @@ class MOZ_STACK_CLASS ModuleCompiler return globalAccesses_.append(access); } + // Note a constraint on the minimum size of the heap. The heap size is + // constrained when linking to be at least the maximum of all such constraints. + void requireHeapLengthToBeAtLeast(uint32_t len) { + module_->requireHeapLengthToBeAtLeast(len); + } + uint32_t minHeapLength() const { + return module_->minHeapLength(); + } + bool collectAccesses(MIRGenerator &gen) { if (!module_->addHeapAccesses(gen.heapAccesses())) return false; @@ -1951,20 +1965,25 @@ class FunctionCompiler curBlock_->setSlot(info().localSlot(local.slot), def); } - MDefinition *loadHeap(ArrayBufferView::ViewType vt, MDefinition *ptr) + MDefinition *loadHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, NeedsBoundsCheck chk) { if (!curBlock_) return NULL; MAsmJSLoadHeap *load = MAsmJSLoadHeap::New(vt, ptr); curBlock_->add(load); + if (chk == NO_BOUNDS_CHECK) + load->setSkipBoundsCheck(true); return load; } - void storeHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v) + void storeHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v, NeedsBoundsCheck chk) { if (!curBlock_) return; - curBlock_->add(MAsmJSStoreHeap::New(vt, ptr, v)); + MAsmJSStoreHeap *store = MAsmJSStoreHeap::New(vt, ptr, v); + curBlock_->add(store); + if (chk == NO_BOUNDS_CHECK) + store->setSkipBoundsCheck(true); } MDefinition *loadGlobalVar(const ModuleCompiler::Global &global) @@ -3087,12 +3106,34 @@ CheckVarRef(FunctionCompiler &f, ParseNode *varRef, MDefinition **def, Type *typ return f.failName(varRef, "'%s' not found in local or asm.js module scope", name); } +static bool +FoldMaskedArrayIndex(FunctionCompiler &f, ParseNode **indexExpr, int32_t *mask, + NeedsBoundsCheck *needsBoundsCheck) +{ + ParseNode *indexNode = BinaryLeft(*indexExpr); + ParseNode *maskNode = BinaryRight(*indexExpr); + + uint32_t mask2; + if (IsLiteralUint32(maskNode, &mask2)) { + // Flag the access to skip the bounds check if the mask ensures that an 'out of + // bounds' access can not occur based on the current heap length constraint. + if (mozilla::CountLeadingZeroes32(f.m().minHeapLength() - 1) <= mozilla::CountLeadingZeroes32(mask2)) + *needsBoundsCheck = NO_BOUNDS_CHECK; + *mask &= mask2; + *indexExpr = indexNode; + return true; + } + + return false; +} + static bool CheckArrayAccess(FunctionCompiler &f, ParseNode *elem, ArrayBufferView::ViewType *viewType, - MDefinition **def) + MDefinition **def, NeedsBoundsCheck *needsBoundsCheck) { ParseNode *viewName = ElemBase(elem); ParseNode *indexExpr = ElemIndex(elem); + *needsBoundsCheck = NEEDS_BOUNDS_CHECK; if (!viewName->isKind(PNK_NAME)) return f.fail(viewName, "base of array access must be a typed array view name"); @@ -3105,11 +3146,23 @@ CheckArrayAccess(FunctionCompiler &f, ParseNode *elem, ArrayBufferView::ViewType uint32_t pointer; if (IsLiteralUint32(indexExpr, &pointer)) { + if (pointer > (uint32_t(INT32_MAX) >> TypedArrayShift(*viewType))) + return f.fail(indexExpr, "constant index out of range"); pointer <<= TypedArrayShift(*viewType); + // It is adequate to note pointer+1 rather than rounding up to the next + // access-size boundary because access is always aligned and the constraint + // will be rounded up to a larger alignment later. + f.m().requireHeapLengthToBeAtLeast(uint32_t(pointer) + 1); + *needsBoundsCheck = NO_BOUNDS_CHECK; *def = f.constant(Int32Value(pointer)); return true; } + // Mask off the low bits to account for the clearing effect of a right shift + // followed by the left shift implicit in the array access. E.g., H32[i>>2] + // loses the low two bits. + int32_t mask = ~((uint32_t(1) << TypedArrayShift(*viewType)) - 1); + MDefinition *pointerDef; if (indexExpr->isKind(PNK_RSH)) { ParseNode *shiftNode = BinaryRight(indexExpr); @@ -3123,6 +3176,21 @@ CheckArrayAccess(FunctionCompiler &f, ParseNode *elem, ArrayBufferView::ViewType if (shift != requiredShift) return f.failf(shiftNode, "shift amount must be %u", requiredShift); + if (pointerNode->isKind(PNK_BITAND)) + FoldMaskedArrayIndex(f, &pointerNode, &mask, needsBoundsCheck); + + // Fold a 'literal constant right shifted' now, and skip the bounds check if + // currently possible. This handles the optimization of many of these uses without + // the need for range analysis, and saves the generation of a MBitAnd op. + if (IsLiteralUint32(pointerNode, &pointer) && pointer <= uint32_t(INT32_MAX)) { + // Cases: b[c>>n], and b[(c&m)>>n] + pointer &= mask; + if (pointer < f.m().minHeapLength()) + *needsBoundsCheck = NO_BOUNDS_CHECK; + *def = f.constant(Int32Value(pointer)); + return true; + } + Type pointerType; if (!CheckExpr(f, pointerNode, &pointerDef, &pointerType)) return false; @@ -3133,19 +3201,32 @@ CheckArrayAccess(FunctionCompiler &f, ParseNode *elem, ArrayBufferView::ViewType if (TypedArrayShift(*viewType) != 0) return f.fail(indexExpr, "index expression isn't shifted; must be an Int8/Uint8 access"); + JS_ASSERT(mask == -1); + bool folded = false; + + if (indexExpr->isKind(PNK_BITAND)) + folded = FoldMaskedArrayIndex(f, &indexExpr, &mask, needsBoundsCheck); + Type pointerType; if (!CheckExpr(f, indexExpr, &pointerDef, &pointerType)) return false; - if (!pointerType.isInt()) - return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars()); + if (folded) { + if (!pointerType.isIntish()) + return f.failf(indexExpr, "%s is not a subtype of intish", pointerType.toChars()); + } else { + if (!pointerType.isInt()) + return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars()); + } } - // Mask off the low bits to account for clearing effect of a right shift - // followed by the left shift implicit in the array access. E.g., H32[i>>2] - // loses the low two bits. - int32_t mask = ~((uint32_t(1) << TypedArrayShift(*viewType)) - 1); - *def = f.bitwise(pointerDef, f.constant(Int32Value(mask))); + // Don't generate the mask op if there is no need for it which could happen for + // a shift of zero. + if (mask == -1) + *def = pointerDef; + else + *def = f.bitwise(pointerDef, f.constant(Int32Value(mask))); + return true; } @@ -3154,10 +3235,11 @@ CheckArrayLoad(FunctionCompiler &f, ParseNode *elem, MDefinition **def, Type *ty { ArrayBufferView::ViewType viewType; MDefinition *pointerDef; - if (!CheckArrayAccess(f, elem, &viewType, &pointerDef)) + NeedsBoundsCheck needsBoundsCheck; + if (!CheckArrayAccess(f, elem, &viewType, &pointerDef, &needsBoundsCheck)) return false; - *def = f.loadHeap(viewType, pointerDef); + *def = f.loadHeap(viewType, pointerDef, needsBoundsCheck); *type = TypedArrayLoadType(viewType); return true; } @@ -3167,7 +3249,8 @@ CheckStoreArray(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition { ArrayBufferView::ViewType viewType; MDefinition *pointerDef; - if (!CheckArrayAccess(f, lhs, &viewType, &pointerDef)) + NeedsBoundsCheck needsBoundsCheck; + if (!CheckArrayAccess(f, lhs, &viewType, &pointerDef, &needsBoundsCheck)) return false; MDefinition *rhsDef; @@ -3186,7 +3269,7 @@ CheckStoreArray(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition break; } - f.storeHeap(viewType, pointerDef, rhsDef); + f.storeHeap(viewType, pointerDef, rhsDef, needsBoundsCheck); *def = rhsDef; *type = rhsType; @@ -4713,7 +4796,14 @@ CheckFunction(ModuleCompiler &m, LifoAlloc &lifo, MIRGenerator **mir, ModuleComp m.parser().release(mark); + // Copy the cumulative minimum heap size constraint to the MIR for use in analysis. The length + // is also constrained to be a power of two, so firstly round up - a larger 'heap required + // length' can help range analysis to prove that bounds checks are not needed. + size_t len = mozilla::RoundUpPow2((size_t) m.minHeapLength()); + m.requireHeapLengthToBeAtLeast(len); + *mir = f.extractMIR(); + (*mir)->noteMinAsmJSHeapLength(len); *funcOut = func; return true; } diff --git a/js/src/jit/AsmJSLink.cpp b/js/src/jit/AsmJSLink.cpp index ca65384f403..46c6fdd559c 100644 --- a/js/src/jit/AsmJSLink.cpp +++ b/js/src/jit/AsmJSLink.cpp @@ -218,6 +218,12 @@ DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) if (!IsPowerOfTwo(heap->byteLength()) || heap->byteLength() < AsmJSAllocationGranularity) return LinkFail(cx, "ArrayBuffer byteLength must be a power of two greater than or equal to 4096"); + // This check is sufficient without considering the size of the loaded datum because heap + // loads and stores start on an aligned boundary and the heap byteLength has larger alignment. + JS_ASSERT((module.minHeapLength() - 1) <= INT32_MAX); + if (heap->byteLength() < module.minHeapLength()) + return LinkFail(cx, "ArrayBuffer byteLength less than the largest source code heap length constraint."); + if (!ArrayBufferObject::prepareForAsmJS(cx, heap)) return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); diff --git a/js/src/jit/AsmJSModule.cpp b/js/src/jit/AsmJSModule.cpp index 4b175d50777..1d3331963d6 100644 --- a/js/src/jit/AsmJSModule.cpp +++ b/js/src/jit/AsmJSModule.cpp @@ -26,11 +26,15 @@ AsmJSModule::patchHeapAccesses(ArrayBufferObject *heap, JSContext *cx) { JS_ASSERT(IsPowerOfTwo(heap->byteLength())); #if defined(JS_CPU_X86) - void *heapOffset = (void*)heap->dataPointer(); + uint8_t *heapOffset = heap->dataPointer(); void *heapLength = (void*)heap->byteLength(); for (unsigned i = 0; i < heapAccesses_.length(); i++) { - JSC::X86Assembler::setPointer(heapAccesses_[i].patchLengthAt(code_), heapLength); - JSC::X86Assembler::setPointer(heapAccesses_[i].patchOffsetAt(code_), heapOffset); + const jit::AsmJSHeapAccess &access = heapAccesses_[i]; + if (access.hasLengthCheck()) + JSC::X86Assembler::setPointer(access.patchLengthAt(code_), heapLength); + void *addr = access.patchOffsetAt(code_); + uint32_t disp = reinterpret_cast(JSC::X86Assembler::getPointer(addr)); + JSC::X86Assembler::setPointer(addr, (void *)(heapOffset + disp)); } #elif defined(JS_CPU_ARM) uint32_t bits = mozilla::CeilingLog2(heap->byteLength()); diff --git a/js/src/jit/AsmJSModule.h b/js/src/jit/AsmJSModule.h index 3c79280054f..f8648ccc909 100644 --- a/js/src/jit/AsmJSModule.h +++ b/js/src/jit/AsmJSModule.h @@ -318,6 +318,7 @@ class AsmJSModule ExitVector exits_; ExportedFunctionVector exports_; HeapAccessVector heapAccesses_; + uint32_t minHeapLength_; #if defined(MOZ_VTUNE) or defined(JS_ION_PERF) ProfiledFunctionVector profiledFunctions_; #endif @@ -349,6 +350,7 @@ class AsmJSModule : globalArgumentName_(NULL), importArgumentName_(NULL), bufferArgumentName_(NULL), + minHeapLength_(AsmJSAllocationGranularity), code_(NULL), operationCallbackExit_(NULL), linked_(false) @@ -630,6 +632,14 @@ class AsmJSModule } void patchHeapAccesses(ArrayBufferObject *heap, JSContext *cx); + void requireHeapLengthToBeAtLeast(uint32_t len) { + if (len > minHeapLength_) + minHeapLength_ = len; + } + uint32_t minHeapLength() const { + return minHeapLength_; + } + uint8_t *allocateCodeAndGlobalSegment(ExclusiveContext *cx, size_t bytesNeeded); uint8_t *functionCode() const { diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 11f317771bb..c765ea32294 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -1306,7 +1306,7 @@ OptimizeMIR(MIRGenerator *mir) } if (js_IonOptions.rangeAnalysis) { - RangeAnalysis r(graph); + RangeAnalysis r(mir, graph); if (!r.addBetaNobes()) return false; IonSpewPass("Beta"); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index e67bc27442a..eb40ee9a00c 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2789,13 +2789,6 @@ LIRGenerator::visitHaveSameClass(MHaveSameClass *ins) return define(new LHaveSameClass(useRegister(lhs), useRegister(rhs), temp()), ins); } -bool -LIRGenerator::visitAsmJSLoadHeap(MAsmJSLoadHeap *ins) -{ - LAsmJSLoadHeap *lir = new LAsmJSLoadHeap(useRegisterAtStart(ins->ptr())); - return define(lir, ins); -} - bool LIRGenerator::visitAsmJSLoadGlobalVar(MAsmJSLoadGlobalVar *ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index ffa7424e6cc..e54362606c9 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -237,7 +237,6 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitFunctionBoundary(MFunctionBoundary *ins); bool visitIsCallable(MIsCallable *ins); bool visitHaveSameClass(MHaveSameClass *ins); - bool visitAsmJSLoadHeap(MAsmJSLoadHeap *ins); bool visitAsmJSLoadGlobalVar(MAsmJSLoadGlobalVar *ins); bool visitAsmJSStoreGlobalVar(MAsmJSStoreGlobalVar *ins); bool visitAsmJSLoadFFIFunc(MAsmJSLoadFFIFunc *ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 8fa61626cb0..c722ab1362e 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -8158,10 +8158,25 @@ class MAsmJSUMod : public MBinaryInstruction } }; -class MAsmJSLoadHeap : public MUnaryInstruction +class MAsmJSHeapAccess +{ + ArrayBufferView::ViewType viewType_; + bool skipBoundsCheck_; + + public: + MAsmJSHeapAccess(ArrayBufferView::ViewType vt, bool s) + : viewType_(vt), skipBoundsCheck_(s) + {} + + ArrayBufferView::ViewType viewType() const { return viewType_; } + bool skipBoundsCheck() const { return skipBoundsCheck_; } + void setSkipBoundsCheck(bool v) { skipBoundsCheck_ = v; } +}; + +class MAsmJSLoadHeap : public MUnaryInstruction, public MAsmJSHeapAccess { MAsmJSLoadHeap(ArrayBufferView::ViewType vt, MDefinition *ptr) - : MUnaryInstruction(ptr), viewType_(vt) + : MUnaryInstruction(ptr), MAsmJSHeapAccess(vt, false) { if (vt == ArrayBufferView::TYPE_FLOAT32 || vt == ArrayBufferView::TYPE_FLOAT64) setResultType(MIRType_Double); @@ -8169,8 +8184,6 @@ class MAsmJSLoadHeap : public MUnaryInstruction setResultType(MIRType_Int32); } - ArrayBufferView::ViewType viewType_; - public: INSTRUCTION_HEADER(AsmJSLoadHeap); @@ -8178,18 +8191,15 @@ class MAsmJSLoadHeap : public MUnaryInstruction return new MAsmJSLoadHeap(vt, ptr); } - ArrayBufferView::ViewType viewType() const { return viewType_; } MDefinition *ptr() const { return getOperand(0); } }; -class MAsmJSStoreHeap : public MBinaryInstruction +class MAsmJSStoreHeap : public MBinaryInstruction, public MAsmJSHeapAccess { MAsmJSStoreHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v) - : MBinaryInstruction(ptr, v), viewType_(vt) + : MBinaryInstruction(ptr, v) , MAsmJSHeapAccess(vt, false) {} - ArrayBufferView::ViewType viewType_; - public: INSTRUCTION_HEADER(AsmJSStoreHeap); @@ -8197,7 +8207,6 @@ class MAsmJSStoreHeap : public MBinaryInstruction return new MAsmJSStoreHeap(vt, ptr, v); } - ArrayBufferView::ViewType viewType() const { return viewType_; } MDefinition *ptr() const { return getOperand(0); } MDefinition *value() const { return getOperand(1); } }; diff --git a/js/src/jit/MIRGenerator.h b/js/src/jit/MIRGenerator.h index 34c047f0b05..91a4ee365ba 100644 --- a/js/src/jit/MIRGenerator.h +++ b/js/src/jit/MIRGenerator.h @@ -122,6 +122,12 @@ class MIRGenerator const Vector &heapAccesses() const { return asmJSHeapAccesses_; } + void noteMinAsmJSHeapLength(uint32_t len) { + minAsmJSHeapLength_ = len; + } + uint32_t minAsmJSHeapLength() const { + return minAsmJSHeapLength_; + } bool noteGlobalAccess(unsigned offset, unsigned globalDataOffset) { return asmJSGlobalAccesses_.append(AsmJSGlobalAccess(offset, globalDataOffset)); } @@ -145,6 +151,7 @@ class MIRGenerator bool performsAsmJSCall_; AsmJSHeapAccessVector asmJSHeapAccesses_; AsmJSGlobalAccessVector asmJSGlobalAccesses_; + uint32_t minAsmJSHeapLength_; #if defined(JS_ION_PERF) AsmJSPerfSpewer asmJSPerfSpewer_; diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 4c37c4f8293..29f34871224 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -8,6 +8,7 @@ #include "jsanalyze.h" +#include "jit/AsmJS.h" #include "jit/Ion.h" #include "jit/IonBuilder.h" #include "jit/IonSpewer.h" @@ -27,7 +28,8 @@ MIRGenerator::MIRGenerator(JSCompartment *compartment, error_(false), cancelBuild_(0), maxAsmJSStackArgBytes_(0), - performsAsmJSCall_(false) + performsAsmJSCall_(false), + minAsmJSHeapLength_(AsmJSAllocationGranularity) { } bool diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 87715381a2e..08e2fc41d83 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1619,6 +1619,26 @@ RangeAnalysis::analyze() if (block->isLoopHeader()) analyzeLoop(block); + + if (mir->compilingAsmJS()) { + for (MInstructionIterator i = block->begin(); i != block->end(); i++) { + if (i->isAsmJSLoadHeap()) { + MAsmJSLoadHeap *ins = i->toAsmJSLoadHeap(); + Range *range = ins->ptr()->range(); + if (range && !range->isLowerInfinite() && range->lower() >= 0 && + !range->isUpperInfinite() && + (uint32_t) range->upper() < mir->minAsmJSHeapLength()) + ins->setSkipBoundsCheck(true); + } else if (i->isAsmJSStoreHeap()) { + MAsmJSStoreHeap *ins = i->toAsmJSStoreHeap(); + Range *range = ins->ptr()->range(); + if (range && !range->isLowerInfinite() && range->lower() >= 0 && + !range->isUpperInfinite() && + (uint32_t) range->upper() < mir->minAsmJSHeapLength()) + ins->setSkipBoundsCheck(true); + } + } + } } return true; diff --git a/js/src/jit/RangeAnalysis.h b/js/src/jit/RangeAnalysis.h index d6d8a293f17..2e08a0cab56 100644 --- a/js/src/jit/RangeAnalysis.h +++ b/js/src/jit/RangeAnalysis.h @@ -74,11 +74,12 @@ class RangeAnalysis MBasicBlock *block); protected: + MIRGenerator *mir; MIRGraph &graph_; public: - MOZ_CONSTEXPR RangeAnalysis(MIRGraph &graph) : - graph_(graph) {} + MOZ_CONSTEXPR RangeAnalysis(MIRGenerator *mir, MIRGraph &graph) : + mir(mir), graph_(graph) {} bool addBetaNobes(); bool analyze(); bool addRangeAssertions(); diff --git a/js/src/jit/RegisterSets.h b/js/src/jit/RegisterSets.h index b19d422c3fc..b142b4ab8e3 100644 --- a/js/src/jit/RegisterSets.h +++ b/js/src/jit/RegisterSets.h @@ -804,11 +804,13 @@ class AsmJSHeapAccess public: #if defined(JS_CPU_X86) || defined(JS_CPU_X64) + // If 'cmp' equals 'offset' or if it is not supplied then the + // cmpDelta_ is zero indicating that there is no length to patch. AsmJSHeapAccess(uint32_t offset, uint32_t after, ArrayBufferView::ViewType vt, AnyRegister loadedReg, uint32_t cmp = UINT32_MAX) : offset_(offset), # if defined(JS_CPU_X86) - cmpDelta_(offset - cmp), + cmpDelta_(cmp == UINT32_MAX ? 0 : offset - cmp), # endif opLength_(after - offset), isFloat32Load_(vt == ArrayBufferView::TYPE_FLOAT32), @@ -817,7 +819,7 @@ class AsmJSHeapAccess AsmJSHeapAccess(uint32_t offset, uint8_t after, uint32_t cmp = UINT32_MAX) : offset_(offset), # if defined(JS_CPU_X86) - cmpDelta_(offset - cmp), + cmpDelta_(cmp == UINT32_MAX ? 0 : offset - cmp), # endif opLength_(after - offset), isFloat32Load_(false), @@ -832,6 +834,7 @@ class AsmJSHeapAccess uint32_t offset() const { return offset_; } void setOffset(uint32_t offset) { offset_ = offset; } #if defined(JS_CPU_X86) + bool hasLengthCheck() const { return cmpDelta_ > 0; } void *patchLengthAt(uint8_t *code) const { return code + (offset_ - cmpDelta_); } void *patchOffsetAt(uint8_t *code) const { return code + (offset_ + opLength_); } #endif diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index a6b92f64718..6d6bf5240d9 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -1779,32 +1779,68 @@ CodeGeneratorARM::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins) int size; bool isFloat = false; switch (mir->viewType()) { - case ArrayBufferView::TYPE_INT8: isSigned = true; size = 8; break; - case ArrayBufferView::TYPE_UINT8: isSigned = false; size = 8; break; - case ArrayBufferView::TYPE_INT16: isSigned = true; size = 16; break; + case ArrayBufferView::TYPE_INT8: isSigned = true; size = 8; break; + case ArrayBufferView::TYPE_UINT8: isSigned = false; size = 8; break; + case ArrayBufferView::TYPE_INT16: isSigned = true; size = 16; break; case ArrayBufferView::TYPE_UINT16: isSigned = false; size = 16; break; case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: isSigned = true; size = 32; break; case ArrayBufferView::TYPE_FLOAT64: isFloat = true; size = 64; break; - case ArrayBufferView::TYPE_FLOAT32: - isFloat = true; - size = 32; - break; + case ArrayBufferView::TYPE_FLOAT32: isFloat = true; size = 32; break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } - Register index = ToRegister(ins->ptr()); - BufferOffset bo = masm.ma_BoundsCheck(index); + + const LAllocation *ptr = ins->ptr(); + + if (ptr->isConstant()) { + JS_ASSERT(mir->skipBoundsCheck()); + int32_t ptrImm = ptr->toConstant()->toInt32(); + JS_ASSERT(ptrImm >= 0); + if (isFloat) { + VFPRegister vd(ToFloatRegister(ins->output())); + if (size == 32) { + masm.ma_vldr(Operand(HeapReg, ptrImm), vd.singleOverlay(), Assembler::Always); + masm.as_vcvt(vd, vd.singleOverlay(), false, Assembler::Always); + } else { + masm.ma_vldr(Operand(HeapReg, ptrImm), vd, Assembler::Always); + } + } else { + masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, Imm32(ptrImm), + ToRegister(ins->output()), Offset, Assembler::Always); + } + return true; + } + + Register ptrReg = ToRegister(ptr); + + if (mir->skipBoundsCheck()) { + if (isFloat) { + VFPRegister vd(ToFloatRegister(ins->output())); + if (size == 32) { + masm.ma_vldr(vd.singleOverlay(), HeapReg, ptrReg, 0, Assembler::Always); + masm.as_vcvt(vd, vd.singleOverlay(), false, Assembler::Always); + } else { + masm.ma_vldr(vd, HeapReg, ptrReg, 0, Assembler::Always); + } + } else { + masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, ptrReg, + ToRegister(ins->output()), Offset, Assembler::Always); + } + return true; + } + + BufferOffset bo = masm.ma_BoundsCheck(ptrReg); if (isFloat) { VFPRegister vd(ToFloatRegister(ins->output())); if (size == 32) { - masm.ma_vldr(vd.singleOverlay(), HeapReg, index, 0, Assembler::Zero); + masm.ma_vldr(vd.singleOverlay(), HeapReg, ptrReg, 0, Assembler::Zero); masm.as_vcvt(vd, vd.singleOverlay(), false, Assembler::Zero); } else { - masm.ma_vldr(vd, HeapReg, index, 0, Assembler::Zero); + masm.ma_vldr(vd, HeapReg, ptrReg, 0, Assembler::Zero); } masm.ma_vmov(NANReg, ToFloatRegister(ins->output()), Assembler::NonZero); - } else { - masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, index, + } else { + masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, ptrReg, ToRegister(ins->output()), Offset, Assembler::Zero); masm.ma_mov(Imm32(0), ToRegister(ins->output()), NoSetCond, Assembler::NonZero); } @@ -1825,25 +1861,56 @@ CodeGeneratorARM::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins) case ArrayBufferView::TYPE_UINT16: isSigned = false; size = 16; break; case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: isSigned = true; size = 32; break; - case ArrayBufferView::TYPE_FLOAT64: isFloat = true; size = 64; break; - case ArrayBufferView::TYPE_FLOAT32: - isFloat = true; - size = 32; - break; + case ArrayBufferView::TYPE_FLOAT64: isFloat = true; size = 64; break; + case ArrayBufferView::TYPE_FLOAT32: isFloat = true; size = 32; break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } - Register index = ToRegister(ins->ptr()); + const LAllocation *ptr = ins->ptr(); + if (ptr->isConstant()) { + JS_ASSERT(mir->skipBoundsCheck()); + int32_t ptrImm = ptr->toConstant()->toInt32(); + JS_ASSERT(ptrImm >= 0); + if (isFloat) { + VFPRegister vd(ToFloatRegister(ins->value())); + if (size == 32) { + masm.as_vcvt(VFPRegister(ScratchFloatReg).singleOverlay(), vd, false, Assembler::Always); + masm.ma_vstr(VFPRegister(ScratchFloatReg).singleOverlay(), Operand(HeapReg, ptrImm), Assembler::Always); + } else { + masm.ma_vstr(vd, Operand(HeapReg, ptrImm), Assembler::Always); + } + } else { + masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, Imm32(ptrImm), + ToRegister(ins->value()), Offset, Assembler::Always); + } + return true; + } - BufferOffset bo = masm.ma_BoundsCheck(index); + Register ptrReg = ToRegister(ptr); + + if (mir->skipBoundsCheck()) { + Register ptrReg = ToRegister(ptr); + if (isFloat) { + VFPRegister vd(ToFloatRegister(ins->value())); + if (size == 32) + masm.storeFloat(vd, HeapReg, ptrReg, Assembler::Always); + else + masm.ma_vstr(vd, HeapReg, ptrReg, 0, Assembler::Always); + } else { + masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, ptrReg, + ToRegister(ins->value()), Offset, Assembler::Always); + } + return true; + } + + BufferOffset bo = masm.ma_BoundsCheck(ptrReg); if (isFloat) { VFPRegister vd(ToFloatRegister(ins->value())); - if (size == 32) { - masm.storeFloat(vd, HeapReg, index, Assembler::Zero); - } else { - masm.ma_vstr(vd, HeapReg, index, 0, Assembler::Zero); - } - } else { - masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, index, + if (size == 32) + masm.storeFloat(vd, HeapReg, ptrReg, Assembler::Zero); + else + masm.ma_vstr(vd, HeapReg, ptrReg, 0, Assembler::Zero); + } else { + masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, ptrReg, ToRegister(ins->value()), Offset, Assembler::Zero); } return gen->noteHeapAccess(AsmJSHeapAccess(bo.getOffset())); diff --git a/js/src/jit/arm/Lowering-arm.cpp b/js/src/jit/arm/Lowering-arm.cpp index f7968a61035..67537d21508 100644 --- a/js/src/jit/arm/Lowering-arm.cpp +++ b/js/src/jit/arm/Lowering-arm.cpp @@ -501,26 +501,39 @@ LIRGeneratorARM::visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins) return define(lir, ins); } +bool +LIRGeneratorARM::visitAsmJSLoadHeap(MAsmJSLoadHeap *ins) +{ + MDefinition *ptr = ins->ptr(); + JS_ASSERT(ptr->type() == MIRType_Int32); + LAllocation ptrAlloc; + + // For the ARM it is best to keep the 'ptr' in a register if a bounds check is needed. + if (ptr->isConstant() && ins->skipBoundsCheck()) { + int32_t ptrValue = ptr->toConstant()->value().toInt32(); + // A bounds check is only skipped for a positive index. + JS_ASSERT(ptrValue >= 0); + ptrAlloc = LAllocation(ptr->toConstant()->vp()); + } else + ptrAlloc = useRegisterAtStart(ptr); + + return define(new LAsmJSLoadHeap(ptrAlloc), ins); +} + bool LIRGeneratorARM::visitAsmJSStoreHeap(MAsmJSStoreHeap *ins) { - LAsmJSStoreHeap *lir; - switch (ins->viewType()) { - case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8: - case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16: - case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: - lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()), - useRegisterAtStart(ins->value())); - break; - case ArrayBufferView::TYPE_FLOAT32: - case ArrayBufferView::TYPE_FLOAT64: - lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()), - useRegisterAtStart(ins->value())); - break; - default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); - } + MDefinition *ptr = ins->ptr(); + JS_ASSERT(ptr->type() == MIRType_Int32); + LAllocation ptrAlloc; - return add(lir, ins); + if (ptr->isConstant() && ins->skipBoundsCheck()) { + JS_ASSERT(ptr->toConstant()->value().toInt32() >= 0); + ptrAlloc = LAllocation(ptr->toConstant()->vp()); + } else + ptrAlloc = useRegisterAtStart(ptr); + + return add(new LAsmJSStoreHeap(ptrAlloc, useRegisterAtStart(ins->value())), ins); } bool diff --git a/js/src/jit/arm/Lowering-arm.h b/js/src/jit/arm/Lowering-arm.h index 48dfbc7774e..882ecb86b1b 100644 --- a/js/src/jit/arm/Lowering-arm.h +++ b/js/src/jit/arm/Lowering-arm.h @@ -78,6 +78,7 @@ class LIRGeneratorARM : public LIRGeneratorShared bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins); bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins); bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins); + bool visitAsmJSLoadHeap(MAsmJSLoadHeap *ins); bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins); bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins); bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins); diff --git a/js/src/jit/shared/Lowering-shared-inl.h b/js/src/jit/shared/Lowering-shared-inl.h index 39c610a441d..463c292b803 100644 --- a/js/src/jit/shared/Lowering-shared-inl.h +++ b/js/src/jit/shared/Lowering-shared-inl.h @@ -263,6 +263,14 @@ LIRGeneratorShared::useRegisterOrConstantAtStart(MDefinition *mir) return useRegisterAtStart(mir); } +LAllocation +LIRGeneratorShared::useRegisterOrNonNegativeConstantAtStart(MDefinition *mir) +{ + if (mir->isConstant() && mir->toConstant()->value().toInt32() >= 0) + return LAllocation(mir->toConstant()->vp()); + return useRegisterAtStart(mir); +} + LAllocation LIRGeneratorShared::useRegisterOrNonDoubleConstant(MDefinition *mir) { diff --git a/js/src/jit/shared/Lowering-shared.h b/js/src/jit/shared/Lowering-shared.h index af822b0205b..7f42cc80630 100644 --- a/js/src/jit/shared/Lowering-shared.h +++ b/js/src/jit/shared/Lowering-shared.h @@ -86,6 +86,7 @@ class LIRGeneratorShared : public MInstructionVisitorWithDefaults inline LAllocation useKeepaliveOrConstant(MDefinition *mir); inline LAllocation useRegisterOrConstant(MDefinition *mir); inline LAllocation useRegisterOrConstantAtStart(MDefinition *mir); + inline LAllocation useRegisterOrNonNegativeConstantAtStart(MDefinition *mir); inline LAllocation useRegisterOrNonDoubleConstant(MDefinition *mir); #ifdef JS_NUNBOX32 diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp index bc4761866b2..1bd45b9b583 100644 --- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -394,8 +394,21 @@ CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins) { MAsmJSLoadHeap *mir = ins->mir(); ArrayBufferView::ViewType vt = mir->viewType(); + const LAllocation *ptr = ins->ptr(); - Operand srcAddr(HeapReg, ToRegister(ins->ptr()), TimesOne); + // No need to note the access if it will never fault. + bool skipNote = mir->skipBoundsCheck(); + Operand srcAddr(HeapReg); + + if (ptr->isConstant()) { + int32_t ptrImm = ptr->toConstant()->toInt32(); + // Note only a positive index is accepted here because a negative offset would + // not wrap back into the protected area reserved for the heap. + JS_ASSERT(ptrImm >= 0); + srcAddr = Operand(HeapReg, ptrImm); + } else { + srcAddr = Operand(HeapReg, ToRegister(ptr), TimesOne); + } if (vt == ArrayBufferView::TYPE_FLOAT32) { FloatRegister dest = ToFloatRegister(ins->output()); @@ -403,7 +416,7 @@ CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins) masm.movss(srcAddr, dest); uint32_t after = masm.size(); masm.cvtss2sd(dest, dest); - return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(ins->output()))); + return skipNote || gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(ins->output()))); } uint32_t before = masm.size(); @@ -412,13 +425,13 @@ CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins) case ArrayBufferView::TYPE_UINT8: masm.movzbl(srcAddr, ToRegister(ins->output())); break; case ArrayBufferView::TYPE_INT16: masm.movswl(srcAddr, ToRegister(ins->output())); break; case ArrayBufferView::TYPE_UINT16: masm.movzwl(srcAddr, ToRegister(ins->output())); break; - case ArrayBufferView::TYPE_INT32: masm.movl(srcAddr, ToRegister(ins->output())); break; + case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: masm.movl(srcAddr, ToRegister(ins->output())); break; case ArrayBufferView::TYPE_FLOAT64: masm.movsd(srcAddr, ToFloatRegister(ins->output())); break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } uint32_t after = masm.size(); - return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(ins->output()))); + return skipNote || gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(ins->output()))); } bool @@ -426,42 +439,54 @@ CodeGeneratorX64::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins) { MAsmJSStoreHeap *mir = ins->mir(); ArrayBufferView::ViewType vt = mir->viewType(); + const LAllocation *ptr = ins->ptr(); + // No need to note the access if it will never fault. + bool skipNote = mir->skipBoundsCheck(); + Operand dstAddr(HeapReg); - Operand dstAddr(HeapReg, ToRegister(ins->ptr()), TimesOne); + if (ptr->isConstant()) { + int32_t ptrImm = ptr->toConstant()->toInt32(); + // Note only a positive index is accepted here because a negative offset would + // not wrap back into the protected area reserved for the heap. + JS_ASSERT(ptrImm >= 0); + dstAddr = Operand(HeapReg, ptrImm); + } else { + dstAddr = Operand(HeapReg, ToRegister(ins->ptr()), TimesOne); + } if (vt == ArrayBufferView::TYPE_FLOAT32) { masm.convertDoubleToFloat(ToFloatRegister(ins->value()), ScratchFloatReg); uint32_t before = masm.size(); masm.movss(ScratchFloatReg, dstAddr); uint32_t after = masm.size(); - return gen->noteHeapAccess(AsmJSHeapAccess(before, after)); + return skipNote || gen->noteHeapAccess(AsmJSHeapAccess(before, after)); } uint32_t before = masm.size(); if (ins->value()->isConstant()) { switch (vt) { - case ArrayBufferView::TYPE_INT8: masm.movb(Imm32(ToInt32(ins->value())), dstAddr); break; + case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8: masm.movb(Imm32(ToInt32(ins->value())), dstAddr); break; - case ArrayBufferView::TYPE_INT16: masm.movw(Imm32(ToInt32(ins->value())), dstAddr); break; + case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16: masm.movw(Imm32(ToInt32(ins->value())), dstAddr); break; - case ArrayBufferView::TYPE_INT32: masm.movl(Imm32(ToInt32(ins->value())), dstAddr); break; + case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: masm.movl(Imm32(ToInt32(ins->value())), dstAddr); break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } } else { switch (vt) { - case ArrayBufferView::TYPE_INT8: masm.movb(ToRegister(ins->value()), dstAddr); break; + case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8: masm.movb(ToRegister(ins->value()), dstAddr); break; - case ArrayBufferView::TYPE_INT16: masm.movw(ToRegister(ins->value()), dstAddr); break; + case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16: masm.movw(ToRegister(ins->value()), dstAddr); break; - case ArrayBufferView::TYPE_INT32: masm.movl(ToRegister(ins->value()), dstAddr); break; + case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: masm.movl(ToRegister(ins->value()), dstAddr); break; case ArrayBufferView::TYPE_FLOAT64: masm.movsd(ToFloatRegister(ins->value()), dstAddr); break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } } uint32_t after = masm.size(); - return gen->noteHeapAccess(AsmJSHeapAccess(before, after)); + return skipNote || gen->noteHeapAccess(AsmJSHeapAccess(before, after)); } bool diff --git a/js/src/jit/x64/Lowering-x64.cpp b/js/src/jit/x64/Lowering-x64.cpp index 4b715ca5318..cbc6aa7accb 100644 --- a/js/src/jit/x64/Lowering-x64.cpp +++ b/js/src/jit/x64/Lowering-x64.cpp @@ -142,21 +142,42 @@ LIRGeneratorX64::visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins) return define(lir, ins); } +bool +LIRGeneratorX64::visitAsmJSLoadHeap(MAsmJSLoadHeap *ins) +{ + MDefinition *ptr = ins->ptr(); + JS_ASSERT(ptr->type() == MIRType_Int32); + + // The X64 does not inline an explicit bounds check so has no need to keep the + // index in a register, however only a positive index is accepted because a + // negative offset encoded as an offset in the addressing mode would not wrap + // back into the protected area reserved for the heap. + if (ptr->isConstant() && ptr->toConstant()->value().toInt32() >= 0) { + LAsmJSLoadHeap *lir = new LAsmJSLoadHeap(LAllocation(ptr->toConstant()->vp())); + return define(lir, ins); + } + return define(new LAsmJSLoadHeap(useRegisterAtStart(ptr)), ins); +} + bool LIRGeneratorX64::visitAsmJSStoreHeap(MAsmJSStoreHeap *ins) { + MDefinition *ptr = ins->ptr(); + JS_ASSERT(ptr->type() == MIRType_Int32); LAsmJSStoreHeap *lir; + + // Note only a positive constant index is accepted because a negative offset + // encoded as an offset in the addressing mode would not wrap back into the + // protected area reserved for the heap. + LAllocation ptrAlloc = useRegisterOrNonNegativeConstantAtStart(ptr); switch (ins->viewType()) { case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8: case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16: case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: - lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()), - useRegisterOrConstantAtStart(ins->value())); + lir = new LAsmJSStoreHeap(ptrAlloc, useRegisterOrConstantAtStart(ins->value())); break; - case ArrayBufferView::TYPE_FLOAT32: - case ArrayBufferView::TYPE_FLOAT64: - lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()), - useRegisterAtStart(ins->value())); + case ArrayBufferView::TYPE_FLOAT32: case ArrayBufferView::TYPE_FLOAT64: + lir = new LAsmJSStoreHeap(ptrAlloc, useRegisterAtStart(ins->value())); break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } diff --git a/js/src/jit/x64/Lowering-x64.h b/js/src/jit/x64/Lowering-x64.h index 3f9f26c7441..9dcd6950385 100644 --- a/js/src/jit/x64/Lowering-x64.h +++ b/js/src/jit/x64/Lowering-x64.h @@ -40,6 +40,7 @@ class LIRGeneratorX64 : public LIRGeneratorX86Shared bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins); bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins); bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins); + bool visitAsmJSLoadHeap(MAsmJSLoadHeap *ins); bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins); bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins); bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins); diff --git a/js/src/jit/x86/Assembler-x86.h b/js/src/jit/x86/Assembler-x86.h index 48ab8c608e6..8932e7a211c 100644 --- a/js/src/jit/x86/Assembler-x86.h +++ b/js/src/jit/x86/Assembler-x86.h @@ -534,6 +534,63 @@ class Assembler : public AssemblerX86Shared masm.movl_mr(addr, index.code(), scale, dest.code()); return masm.currentOffset(); } + + // Load from *address where address can be patched. + CodeOffsetLabel movsblWithPatch(const AbsoluteAddress &src, Register dest) { + masm.movsbl_mr(src.addr, dest.code()); + return masm.currentOffset(); + } + CodeOffsetLabel movzblWithPatch(const AbsoluteAddress &src, Register dest) { + masm.movzbl_mr(src.addr, dest.code()); + return masm.currentOffset(); + } + CodeOffsetLabel movswlWithPatch(const AbsoluteAddress &src, Register dest) { + masm.movswl_mr(src.addr, dest.code()); + return masm.currentOffset(); + } + CodeOffsetLabel movzwlWithPatch(const AbsoluteAddress &src, Register dest) { + masm.movzwl_mr(src.addr, dest.code()); + return masm.currentOffset(); + } + CodeOffsetLabel movlWithPatch(const AbsoluteAddress &src, Register dest) { + masm.movl_mr(src.addr, dest.code()); + return masm.currentOffset(); + } + CodeOffsetLabel movssWithPatch(const AbsoluteAddress &src, FloatRegister dest) { + JS_ASSERT(HasSSE2()); + masm.movss_mr(src.addr, dest.code()); + return masm.currentOffset(); + } + CodeOffsetLabel movsdWithPatch(const AbsoluteAddress &src, FloatRegister dest) { + JS_ASSERT(HasSSE2()); + masm.movsd_mr(src.addr, dest.code()); + return masm.currentOffset(); + } + + // Store to *address where address can be patched. + CodeOffsetLabel movbWithPatch(Register src, const AbsoluteAddress &dest) { + masm.movb_rm(src.code(), dest.addr); + return masm.currentOffset(); + } + CodeOffsetLabel movwWithPatch(Register src, const AbsoluteAddress &dest) { + masm.movw_rm(src.code(), dest.addr); + return masm.currentOffset(); + } + CodeOffsetLabel movlWithPatch(Register src, const AbsoluteAddress &dest) { + masm.movl_rm(src.code(), dest.addr); + return masm.currentOffset(); + } + CodeOffsetLabel movssWithPatch(FloatRegister src, const AbsoluteAddress &dest) { + JS_ASSERT(HasSSE2()); + masm.movss_rm(src.code(), dest.addr); + return masm.currentOffset(); + } + CodeOffsetLabel movsdWithPatch(FloatRegister src, const AbsoluteAddress &dest) { + JS_ASSERT(HasSSE2()); + masm.movsd_rm(src.code(), dest.addr); + return masm.currentOffset(); + } + }; // Get a register in which we plan to put a quantity that will be used as an diff --git a/js/src/jit/x86/CodeGenerator-x86.cpp b/js/src/jit/x86/CodeGenerator-x86.cpp index 5c20143b6bc..5d37ceae193 100644 --- a/js/src/jit/x86/CodeGenerator-x86.cpp +++ b/js/src/jit/x86/CodeGenerator-x86.cpp @@ -403,9 +403,10 @@ class jit::OutOfLineLoadTypedArrayOutOfBounds : public OutOfLineCodeBasevisitOutOfLineLoadTypedArrayOutOfBounds(this); } }; +template void -CodeGeneratorX86::loadViewTypeElement(ArrayBufferView::ViewType vt, const Address &srcAddr, - const LDefinition *out) +CodeGeneratorX86::loadNonFloat32ViewTypeElement(ArrayBufferView::ViewType vt, const T &srcAddr, + const LDefinition *out) { switch (vt) { case ArrayBufferView::TYPE_INT8: masm.movsblWithPatch(srcAddr, ToRegister(out)); break; @@ -413,13 +414,32 @@ CodeGeneratorX86::loadViewTypeElement(ArrayBufferView::ViewType vt, const Addres case ArrayBufferView::TYPE_UINT8: masm.movzblWithPatch(srcAddr, ToRegister(out)); break; case ArrayBufferView::TYPE_INT16: masm.movswlWithPatch(srcAddr, ToRegister(out)); break; case ArrayBufferView::TYPE_UINT16: masm.movzwlWithPatch(srcAddr, ToRegister(out)); break; - case ArrayBufferView::TYPE_INT32: masm.movlWithPatch(srcAddr, ToRegister(out)); break; + case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: masm.movlWithPatch(srcAddr, ToRegister(out)); break; case ArrayBufferView::TYPE_FLOAT64: masm.movsdWithPatch(srcAddr, ToFloatRegister(out)); break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } } +template +bool +CodeGeneratorX86::loadViewTypeElement(ArrayBufferView::ViewType vt, const T &srcAddr, + const LDefinition *out) +{ + if (vt == ArrayBufferView::TYPE_FLOAT32) { + FloatRegister dest = ToFloatRegister(out); + uint32_t before = masm.size(); + masm.movssWithPatch(srcAddr, dest); + uint32_t after = masm.size(); + masm.cvtss2sd(dest, dest); + return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, AnyRegister(dest))); + } + uint32_t before = masm.size(); + loadNonFloat32ViewTypeElement(vt, srcAddr, out); + uint32_t after = masm.size(); + return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(out))); +} + bool CodeGeneratorX86::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins) { @@ -452,7 +472,7 @@ CodeGeneratorX86::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic masm.bind(ool->rejoin()); return true; } - loadViewTypeElement(vt, srcAddr, out); + loadNonFloat32ViewTypeElement(vt, srcAddr, out); if (vt == ArrayBufferView::TYPE_FLOAT64) masm.canonicalizeDouble(ToFloatRegister(out)); if (ool) @@ -463,23 +483,32 @@ CodeGeneratorX86::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic bool CodeGeneratorX86::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins) { - // This is identical to LoadTypedArrayElementStatic, except that the - // array's base and length are not known ahead of time and can be patched - // later on, and the instruction is always infallible. const MAsmJSLoadHeap *mir = ins->mir(); ArrayBufferView::ViewType vt = mir->viewType(); - - Register ptr = ToRegister(ins->ptr()); + const LAllocation *ptr = ins->ptr(); const LDefinition *out = ins->output(); + if (ptr->isConstant()) { + JS_ASSERT(mir->skipBoundsCheck()); + int32_t ptrImm = ptr->toConstant()->toInt32(); + JS_ASSERT(ptrImm >= 0); + AbsoluteAddress srcAddr((void *) ptrImm); + return loadViewTypeElement(vt, srcAddr, out); + } + + Register ptrReg = ToRegister(ptr); + Address srcAddr(ptrReg, 0); + + if (mir->skipBoundsCheck()) + return loadViewTypeElement(vt, srcAddr, out); + OutOfLineLoadTypedArrayOutOfBounds *ool = new OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out)); if (!addOutOfLineCode(ool)) return false; - CodeOffsetLabel cmp = masm.cmplWithPatch(ptr, Imm32(0)); + CodeOffsetLabel cmp = masm.cmplWithPatch(ptrReg, Imm32(0)); masm.j(Assembler::AboveOrEqual, ool->entry()); - Address srcAddr(ptr, 0); if (vt == ArrayBufferView::TYPE_FLOAT32) { FloatRegister dest = ToFloatRegister(out); uint32_t before = masm.size(); @@ -490,7 +519,7 @@ CodeGeneratorX86::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins) return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, AnyRegister(dest), cmp.offset())); } uint32_t before = masm.size(); - loadViewTypeElement(vt, srcAddr, out); + loadNonFloat32ViewTypeElement(vt, srcAddr, out); uint32_t after = masm.size(); masm.bind(ool->rejoin()); return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(out), cmp.offset())); @@ -509,23 +538,42 @@ CodeGeneratorX86::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArra return true; } +template void -CodeGeneratorX86::storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value, - const Address &dstAddr) +CodeGeneratorX86::storeNonFloat32ViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value, + const T &dstAddr) { switch (vt) { - case ArrayBufferView::TYPE_INT8: masm.movbWithPatch(ToRegister(value), dstAddr); break; + case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8_CLAMPED: case ArrayBufferView::TYPE_UINT8: masm.movbWithPatch(ToRegister(value), dstAddr); break; - case ArrayBufferView::TYPE_INT16: masm.movwWithPatch(ToRegister(value), dstAddr); break; + case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16: masm.movwWithPatch(ToRegister(value), dstAddr); break; - case ArrayBufferView::TYPE_INT32: masm.movlWithPatch(ToRegister(value), dstAddr); break; + case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: masm.movlWithPatch(ToRegister(value), dstAddr); break; case ArrayBufferView::TYPE_FLOAT64: masm.movsdWithPatch(ToFloatRegister(value), dstAddr); break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } } +template +bool +CodeGeneratorX86::storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value, + const T &dstAddr) +{ + if (vt == ArrayBufferView::TYPE_FLOAT32) { + masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg); + uint32_t before = masm.size(); + masm.movssWithPatch(ScratchFloatReg, dstAddr); + uint32_t after = masm.size(); + return gen->noteHeapAccess(AsmJSHeapAccess(before, after)); + } + uint32_t before = masm.size(); + storeNonFloat32ViewTypeElement(vt, value, dstAddr); + uint32_t after = masm.size(); + return gen->noteHeapAccess(AsmJSHeapAccess(before, after)); +} + bool CodeGeneratorX86::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins) { @@ -546,7 +594,7 @@ CodeGeneratorX86::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStati masm.bind(&rejoin); return true; } - storeViewTypeElement(vt, value, dstAddr); + storeNonFloat32ViewTypeElement(vt, value, dstAddr); masm.bind(&rejoin); return true; } @@ -554,20 +602,29 @@ CodeGeneratorX86::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStati bool CodeGeneratorX86::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins) { - // This is identical to StoreTypedArrayElementStatic, except that the - // array's base and length are not known ahead of time and can be patched - // later on. MAsmJSStoreHeap *mir = ins->mir(); ArrayBufferView::ViewType vt = mir->viewType(); - - Register ptr = ToRegister(ins->ptr()); const LAllocation *value = ins->value(); + const LAllocation *ptr = ins->ptr(); - CodeOffsetLabel cmp = masm.cmplWithPatch(ptr, Imm32(0)); + if (ptr->isConstant()) { + JS_ASSERT(mir->skipBoundsCheck()); + int32_t ptrImm = ptr->toConstant()->toInt32(); + JS_ASSERT(ptrImm >= 0); + AbsoluteAddress dstAddr((void *) ptrImm); + return storeViewTypeElement(vt, value, dstAddr); + } + + Register ptrReg = ToRegister(ptr); + Address dstAddr(ptrReg, 0); + + if (mir->skipBoundsCheck()) + return storeViewTypeElement(vt, value, dstAddr); + + CodeOffsetLabel cmp = masm.cmplWithPatch(ptrReg, Imm32(0)); Label rejoin; masm.j(Assembler::AboveOrEqual, &rejoin); - Address dstAddr(ptr, 0); if (vt == ArrayBufferView::TYPE_FLOAT32) { masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg); uint32_t before = masm.size(); @@ -577,7 +634,7 @@ CodeGeneratorX86::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins) return gen->noteHeapAccess(AsmJSHeapAccess(before, after, cmp.offset())); } uint32_t before = masm.size(); - storeViewTypeElement(vt, value, dstAddr); + storeNonFloat32ViewTypeElement(vt, value, dstAddr); uint32_t after = masm.size(); masm.bind(&rejoin); return gen->noteHeapAccess(AsmJSHeapAccess(before, after, cmp.offset())); diff --git a/js/src/jit/x86/CodeGenerator-x86.h b/js/src/jit/x86/CodeGenerator-x86.h index cc1fc525a48..0d7afd16857 100644 --- a/js/src/jit/x86/CodeGenerator-x86.h +++ b/js/src/jit/x86/CodeGenerator-x86.h @@ -28,10 +28,18 @@ class CodeGeneratorX86 : public CodeGeneratorX86Shared ValueOperand ToOutValue(LInstruction *ins); ValueOperand ToTempValue(LInstruction *ins, size_t pos); - void loadViewTypeElement(ArrayBufferView::ViewType vt, const Address &srcAddr, + template + bool loadViewTypeElement(ArrayBufferView::ViewType vt, const T &srcAddr, const LDefinition *out); - void storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value, - const Address &dstAddr); + template + void loadNonFloat32ViewTypeElement(ArrayBufferView::ViewType vt, const T &srcAddr, + const LDefinition *out); + template + bool storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value, + const T &dstAddr); + template + void storeNonFloat32ViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value, + const T &dstAddr); void storeElementTyped(const LAllocation *value, MIRType valueType, MIRType elementType, const Register &elements, const LAllocation *index); diff --git a/js/src/jit/x86/Lowering-x86.cpp b/js/src/jit/x86/Lowering-x86.cpp index 49b5d571bfb..5e816792eda 100644 --- a/js/src/jit/x86/Lowering-x86.cpp +++ b/js/src/jit/x86/Lowering-x86.cpp @@ -216,10 +216,53 @@ LIRGeneratorX86::visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins) return define(lir, ins); } +bool +LIRGeneratorX86::visitAsmJSLoadHeap(MAsmJSLoadHeap *ins) +{ + MDefinition *ptr = ins->ptr(); + LAllocation ptrAlloc; + JS_ASSERT(ptr->type() == MIRType_Int32); + + // For the x86 it is best to keep the 'ptr' in a register if a bounds check is needed. + if (ptr->isConstant() && ins->skipBoundsCheck()) { + int32_t ptrValue = ptr->toConstant()->value().toInt32(); + // A bounds check is only skipped for a positive index. + JS_ASSERT(ptrValue >= 0); + ptrAlloc = LAllocation(ptr->toConstant()->vp()); + } else { + ptrAlloc = useRegisterAtStart(ptr); + } + LAsmJSLoadHeap *lir = new LAsmJSLoadHeap(ptrAlloc); + return define(lir, ins); +} + bool LIRGeneratorX86::visitAsmJSStoreHeap(MAsmJSStoreHeap *ins) { + MDefinition *ptr = ins->ptr(); LAsmJSStoreHeap *lir; + JS_ASSERT(ptr->type() == MIRType_Int32); + + if (ptr->isConstant() && ins->skipBoundsCheck()) { + int32_t ptrValue = ptr->toConstant()->value().toInt32(); + JS_ASSERT(ptrValue >= 0); + LAllocation ptrAlloc = LAllocation(ptr->toConstant()->vp()); + switch (ins->viewType()) { + case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8: + // See comment below. + lir = new LAsmJSStoreHeap(ptrAlloc, useFixed(ins->value(), eax)); + break; + case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16: + case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: + case ArrayBufferView::TYPE_FLOAT32: case ArrayBufferView::TYPE_FLOAT64: + // See comment below. + lir = new LAsmJSStoreHeap(ptrAlloc, useRegisterAtStart(ins->value())); + break; + default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); + } + return add(lir, ins); + } + switch (ins->viewType()) { case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8: // It's a trap! On x86, the 1-byte store can only use one of @@ -227,16 +270,14 @@ LIRGeneratorX86::visitAsmJSStoreHeap(MAsmJSStoreHeap *ins) // gives us one of {edi,esi,ebp,esp}, we're out of luck. (The formatter // will assert on us.) Ideally, we'd just ask the register allocator to // give us one of {al,bl,cl,dl}. For now, just useFixed(al). - lir = new LAsmJSStoreHeap(useRegister(ins->ptr()), - useFixed(ins->value(), eax)); + lir = new LAsmJSStoreHeap(useRegister(ins->ptr()), useFixed(ins->value(), eax)); break; case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16: case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32: case ArrayBufferView::TYPE_FLOAT32: case ArrayBufferView::TYPE_FLOAT64: - // For now, don't allow constants. The immediate operand affects - // instruction layout which affects patching. - lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()), - useRegisterAtStart(ins->value())); + // For now, don't allow constant values. The immediate operand + // affects instruction layout which affects patching. + lir = new LAsmJSStoreHeap(useRegisterAtStart(ptr), useRegisterAtStart(ins->value())); break; default: MOZ_ASSUME_UNREACHABLE("unexpected array type"); } diff --git a/js/src/jit/x86/Lowering-x86.h b/js/src/jit/x86/Lowering-x86.h index d8e302f64ed..cc2ea1f7c8c 100644 --- a/js/src/jit/x86/Lowering-x86.h +++ b/js/src/jit/x86/Lowering-x86.h @@ -43,6 +43,7 @@ class LIRGeneratorX86 : public LIRGeneratorX86Shared bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins); bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins); bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins); + bool visitAsmJSLoadHeap(MAsmJSLoadHeap *ins); bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins); bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins); bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins); diff --git a/js/src/jsworkers.cpp b/js/src/jsworkers.cpp index 72e832c8d6f..e48ef4d006d 100644 --- a/js/src/jsworkers.cpp +++ b/js/src/jsworkers.cpp @@ -650,6 +650,13 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state) DebugOnly executionMode = ionBuilder->info().executionMode(); JS_ASSERT(GetIonScript(ionBuilder->script(), executionMode) == ION_COMPILING_SCRIPT); +#if JS_TRACE_LOGGING + AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::ION_BACKGROUND_COMPILER), + TraceLogging::ION_COMPILE_START, + TraceLogging::ION_COMPILE_STOP, + ionBuilder->script()); +#endif + state.unlock(); { jit::IonContext ictx(runtime, ionBuilder->script()->compartment(), &ionBuilder->temp()); diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg index 80cdc7a748d..eab596ffae1 100644 --- a/js/xpconnect/src/xpc.msg +++ b/js/xpconnect/src/xpc.msg @@ -166,6 +166,8 @@ XPC_MSG_DEF(NS_ERROR_DNS_LOOKUP_QUEUE_FULL , "The DNS lookup queue is f XPC_MSG_DEF(NS_ERROR_UNKNOWN_PROXY_HOST , "The lookup of the proxy hostname failed") XPC_MSG_DEF(NS_ERROR_UNKNOWN_SOCKET_TYPE , "The specified socket type does not exist") XPC_MSG_DEF(NS_ERROR_SOCKET_CREATE_FAILED , "The specified socket type could not be created") +XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED , "The specified socket address type is not supported") +XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_IN_USE , "Some other socket is already using the specified address.") XPC_MSG_DEF(NS_ERROR_CACHE_KEY_NOT_FOUND , "Cache key could not be found") XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_STREAM , "Cache data is a stream") XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_NOT_STREAM , "Cache data is not a stream") diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index e7a266111ef..ce76ae12258 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -29,6 +29,7 @@ #include "nsViewportFrame.h" #include "nsSVGTextFrame2.h" #include "nsSVGTextPathFrame.h" +#include "StickyScrollContainer.h" #include "nsIRootBox.h" #include "nsIDOMMutationEvent.h" #include "nsContentUtils.h" @@ -326,7 +327,7 @@ RestyleManager::RecomputePosition(nsIFrame* aFrame) aFrame->SchedulePaint(); // For relative positioning, we can simply update the frame rect - if (display->mPosition == NS_STYLE_POSITION_RELATIVE) { + if (display->IsRelativelyPositionedStyle()) { switch (display->mDisplay) { case NS_STYLE_DISPLAY_TABLE_CAPTION: case NS_STYLE_DISPLAY_TABLE_CELL: @@ -345,17 +346,27 @@ RestyleManager::RecomputePosition(nsIFrame* aFrame) } nsIFrame* cb = aFrame->GetContainingBlock(); - const nsSize size = cb->GetContentRectRelativeToSelf().Size(); - nsPoint position = aFrame->GetNormalPosition(); nsMargin newOffsets; // Move the frame - nsHTMLReflowState::ComputeRelativeOffsets( - cb->StyleVisibility()->mDirection, - aFrame, size.width, size.height, newOffsets); - NS_ASSERTION(newOffsets.left == -newOffsets.right && - newOffsets.top == -newOffsets.bottom, - "ComputeRelativeOffsets should return valid results"); + if (display->mPosition == NS_STYLE_POSITION_STICKY) { + StickyScrollContainer::ComputeStickyOffsets(aFrame); + } else { + MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition, + "Unexpected type of positioning"); + const nsSize size = cb->GetContentRectRelativeToSelf().Size(); + + nsHTMLReflowState::ComputeRelativeOffsets( + cb->StyleVisibility()->mDirection, + aFrame, size.width, size.height, newOffsets); + NS_ASSERTION(newOffsets.left == -newOffsets.right && + newOffsets.top == -newOffsets.bottom, + "ComputeRelativeOffsets should return valid results"); + } + + nsPoint position = aFrame->GetNormalPosition(); + + // This handles both relative and sticky positioning. nsHTMLReflowState::ApplyRelativePositioning(aFrame, newOffsets, &position); aFrame->SetPosition(position); diff --git a/layout/base/RestyleTracker.h b/layout/base/RestyleTracker.h index d71fd09d77a..68cadb7464e 100644 --- a/layout/base/RestyleTracker.h +++ b/layout/base/RestyleTracker.h @@ -29,6 +29,10 @@ class OverflowChangedTracker { public: + OverflowChangedTracker() : + mSubtreeRoot(nullptr) + {} + ~OverflowChangedTracker() { NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!"); @@ -67,6 +71,15 @@ public: } } + /** + * Set the subtree root to limit overflow updates. This must be set if and + * only if currently reflowing aSubtreeRoot, to ensure overflow changes will + * still propagate correctly. + */ + void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) { + mSubtreeRoot = aSubtreeRoot; + } + /** * Update the overflow of all added frames, and clear the entry list. * @@ -100,7 +113,7 @@ public: } if (updateParent) { nsIFrame *parent = frame->GetParent(); - if (parent) { + if (parent && parent != mSubtreeRoot) { if (!mEntryList.contains(Entry(parent, entry->mDepth - 1, false))) { mEntryList.insert(new Entry(parent, entry->mDepth - 1, false)); } @@ -165,6 +178,9 @@ private: /* A list of frames to process, sorted by their depth in the frame tree */ SplayTree mEntryList; + + /* Don't update overflow of this frame or its ancestors. */ + const nsIFrame* mSubtreeRoot; }; class RestyleTracker { diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index e947451d604..c9d04094a0a 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -93,6 +93,7 @@ using mozilla::image::ImageOps; using mozilla::image::Orientation; #define FLEXBOX_ENABLED_PREF_NAME "layout.css.flexbox.enabled" +#define STICKY_ENABLED_PREF_NAME "layout.css.sticky.enabled" #ifdef DEBUG // TODO: remove, see bug 598468. @@ -120,6 +121,11 @@ static int32_t sIndexOfInlineFlexInDisplayTable; // This tracks whether those ^^ indices have been initialized static bool sAreFlexKeywordIndicesInitialized = false; +// This is an index into kPositionKTable. It will be initialized +// the first time that StickyEnabledPrefChangeCallback() is invoked. +static int32_t sIndexOfStickyInPositionTable; +static bool sIsStickyKeywordIndexInitialized = false; + typedef nsDataHashtable ContentMap; static ContentMap* sContentMap = nullptr; static ContentMap& GetContentMap() { @@ -170,6 +176,38 @@ FlexboxEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) return 0; } +// When the pref "layout.css.sticky.enabled" changes, this function is invoked +// to let us update kPositionKTable, to selectively disable or restore the +// entry for "sticky" in that table. +static int +StickyEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) +{ + MOZ_ASSERT(strncmp(aPrefName, STICKY_ENABLED_PREF_NAME, + NS_ARRAY_LENGTH(STICKY_ENABLED_PREF_NAME)) == 0, + "We only registered this callback for a single pref, so it " + "should only be called for that pref"); + + bool isStickyEnabled = + Preferences::GetBool(STICKY_ENABLED_PREF_NAME, false); + + if (!sIsStickyKeywordIndexInitialized) { + // First run: find the position of "sticky" in kPositionKTable. + sIndexOfStickyInPositionTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_sticky, + nsCSSProps::kPositionKTable); + MOZ_ASSERT(sIndexOfStickyInPositionTable >= 0, + "Couldn't find sticky in kPositionKTable"); + sIsStickyKeywordIndexInitialized = true; + } + + // OK -- now, stomp on or restore the "sticky" entry in kPositionKTable, + // depending on whether the sticky pref is enabled vs. disabled. + nsCSSProps::kPositionKTable[sIndexOfStickyInPositionTable] = + isStickyEnabled ? eCSSKeyword_sticky : eCSSKeyword_UNKNOWN; + + return 0; +} + template static AnimationsOrTransitions* HasAnimationOrTransition(nsIContent* aContent, @@ -5002,6 +5040,9 @@ nsLayoutUtils::Initialize() Preferences::RegisterCallback(FlexboxEnabledPrefChangeCallback, FLEXBOX_ENABLED_PREF_NAME); FlexboxEnabledPrefChangeCallback(FLEXBOX_ENABLED_PREF_NAME, nullptr); + Preferences::RegisterCallback(StickyEnabledPrefChangeCallback, + STICKY_ENABLED_PREF_NAME); + StickyEnabledPrefChangeCallback(STICKY_ENABLED_PREF_NAME, nullptr); } /* static */ @@ -5015,6 +5056,8 @@ nsLayoutUtils::Shutdown() Preferences::UnregisterCallback(FlexboxEnabledPrefChangeCallback, FLEXBOX_ENABLED_PREF_NAME); + Preferences::UnregisterCallback(StickyEnabledPrefChangeCallback, + STICKY_ENABLED_PREF_NAME); } /* static */ diff --git a/layout/generic/StickyScrollContainer.cpp b/layout/generic/StickyScrollContainer.cpp new file mode 100644 index 00000000000..bfe911a7489 --- /dev/null +++ b/layout/generic/StickyScrollContainer.cpp @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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/. */ + +/** + * compute sticky positioning, both during reflow and when the scrolling + * container scrolls + */ + +#include "StickyScrollContainer.h" +#include "nsIFrame.h" +#include "nsIScrollableFrame.h" +#include "nsLayoutUtils.h" +#include "RestyleTracker.h" + +using namespace mozilla::css; + +namespace mozilla { + +void DestroyStickyScrollContainer(void* aPropertyValue) +{ + delete static_cast(aPropertyValue); +} + +NS_DECLARE_FRAME_PROPERTY(StickyScrollContainerProperty, + DestroyStickyScrollContainer) + +StickyScrollContainer::StickyScrollContainer(nsIScrollableFrame* aScrollFrame) + : mScrollFrame(aScrollFrame) + , mScrollPosition() +{ + mScrollFrame->AddScrollPositionListener(this); +} + +StickyScrollContainer::~StickyScrollContainer() +{ + mScrollFrame->RemoveScrollPositionListener(this); +} + +// static +StickyScrollContainer* +StickyScrollContainer::StickyScrollContainerForFrame(nsIFrame* aFrame) +{ + nsIScrollableFrame* scrollFrame = + nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(), + nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + NS_ASSERTION(scrollFrame, "Need a scrolling container"); + FrameProperties props = static_cast(do_QueryFrame(scrollFrame))-> + Properties(); + StickyScrollContainer* s = static_cast + (props.Get(StickyScrollContainerProperty())); + if (!s) { + s = new StickyScrollContainer(scrollFrame); + props.Set(StickyScrollContainerProperty(), s); + } + return s; +} + +// static +StickyScrollContainer* +StickyScrollContainer::GetStickyScrollContainerForScrollFrame(nsIFrame* aFrame) +{ + FrameProperties props = aFrame->Properties(); + return static_cast + (props.Get(StickyScrollContainerProperty())); +} + +static nscoord +ComputeStickySideOffset(Side aSide, const nsStyleSides& aOffset, + nscoord aPercentBasis) +{ + if (eStyleUnit_Auto == aOffset.GetUnit(aSide)) { + return NS_AUTOOFFSET; + } else { + return nsLayoutUtils::ComputeCBDependentValue(aPercentBasis, + aOffset.Get(aSide)); + } +} + +// static +void +StickyScrollContainer::ComputeStickyOffsets(nsIFrame* aFrame) +{ + nsIScrollableFrame* scrollableFrame = + nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(), + nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + + if (!scrollableFrame) { + // Not sure how this would happen, but bail if it does. + NS_ERROR("Couldn't find a scrollable frame"); + return; + } + + nsSize scrollContainerSize = scrollableFrame->GetScrolledFrame()-> + GetContentRectRelativeToSelf().Size(); + + nsMargin computedOffsets; + const nsStylePosition* position = aFrame->StylePosition(); + + computedOffsets.left = ComputeStickySideOffset(eSideLeft, position->mOffset, + scrollContainerSize.width); + computedOffsets.right = ComputeStickySideOffset(eSideRight, position->mOffset, + scrollContainerSize.width); + computedOffsets.top = ComputeStickySideOffset(eSideTop, position->mOffset, + scrollContainerSize.height); + computedOffsets.bottom = ComputeStickySideOffset(eSideBottom, position->mOffset, + scrollContainerSize.height); + + // Store the offset + FrameProperties props = aFrame->Properties(); + nsMargin* offsets = static_cast + (props.Get(nsIFrame::ComputedStickyOffsetProperty())); + if (offsets) { + *offsets = computedOffsets; + } else { + props.Set(nsIFrame::ComputedStickyOffsetProperty(), + new nsMargin(computedOffsets)); + } +} + +void +StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick, + nsRect* aContain) const +{ + aStick->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX); + aContain->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX); + + const nsMargin* computedOffsets = static_cast( + aFrame->Properties().Get(nsIFrame::ComputedStickyOffsetProperty())); + if (!computedOffsets) { + // We haven't reflowed the scroll frame yet, so offsets haven't been + // computed. Bail. + return; + } + + nsIFrame* scrolledFrame = mScrollFrame->GetScrolledFrame(); + nsIFrame* cbFrame = aFrame->GetContainingBlock(); + NS_ASSERTION(cbFrame == scrolledFrame || + nsLayoutUtils::IsProperAncestorFrame(scrolledFrame, cbFrame), + "Scroll frame should be an ancestor of the containing block"); + + nsRect rect = aFrame->GetRect(); + nsMargin margin = aFrame->GetUsedMargin(); + + // Containing block limits + if (cbFrame != scrolledFrame) { + nsMargin cbBorderPadding = cbFrame->GetUsedBorderAndPadding(); + aContain->SetRect(nsPoint(cbBorderPadding.left, cbBorderPadding.top) - + aFrame->GetParent()->GetOffsetTo(cbFrame), + cbFrame->GetContentRectRelativeToSelf().Size() - + rect.Size()); + aContain->Deflate(margin); + } + + nsMargin sfPadding = scrolledFrame->GetUsedPadding(); + nsPoint sfOffset = aFrame->GetParent()->GetOffsetTo(scrolledFrame); + + // Top + if (computedOffsets->top != NS_AUTOOFFSET) { + aStick->SetTopEdge(mScrollPosition.y + sfPadding.top + + computedOffsets->top - sfOffset.y); + } + + nsSize sfSize = scrolledFrame->GetContentRectRelativeToSelf().Size(); + + // Bottom + if (computedOffsets->bottom != NS_AUTOOFFSET && + (computedOffsets->top == NS_AUTOOFFSET || + rect.height <= sfSize.height - computedOffsets->TopBottom())) { + aStick->SetBottomEdge(mScrollPosition.y + sfPadding.top + sfSize.height - + computedOffsets->bottom - rect.height - sfOffset.y); + } + + uint8_t direction = cbFrame->StyleVisibility()->mDirection; + + // Left + if (computedOffsets->left != NS_AUTOOFFSET && + (computedOffsets->right == NS_AUTOOFFSET || + direction == NS_STYLE_DIRECTION_LTR || + rect.width <= sfSize.width - computedOffsets->LeftRight())) { + aStick->SetLeftEdge(mScrollPosition.x + sfPadding.left + + computedOffsets->left - sfOffset.x); + } + + // Right + if (computedOffsets->right != NS_AUTOOFFSET && + (computedOffsets->left == NS_AUTOOFFSET || + direction == NS_STYLE_DIRECTION_RTL || + rect.width <= sfSize.width - computedOffsets->LeftRight())) { + aStick->SetRightEdge(mScrollPosition.x + sfPadding.left + sfSize.width - + computedOffsets->right - rect.width - sfOffset.x); + } +} + +nsPoint +StickyScrollContainer::ComputePosition(nsIFrame* aFrame) const +{ + nsRect stick; + nsRect contain; + ComputeStickyLimits(aFrame, &stick, &contain); + + nsPoint position = aFrame->GetNormalPosition(); + + // For each sticky direction (top, bottom, left, right), move the frame along + // the appropriate axis, based on the scroll position, but limit this to keep + // the element's margin box within the containing block. + position.y = std::max(position.y, std::min(stick.y, contain.YMost())); + position.y = std::min(position.y, std::max(stick.YMost(), contain.y)); + position.x = std::max(position.x, std::min(stick.x, contain.XMost())); + position.x = std::min(position.x, std::max(stick.XMost(), contain.x)); + + return position; +} + +void +StickyScrollContainer::UpdatePositions(nsPoint aScrollPosition, + nsIFrame* aSubtreeRoot) +{ + NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == do_QueryFrame(mScrollFrame), + "If reflowing, should be reflowing the scroll frame"); + mScrollPosition = aScrollPosition; + + OverflowChangedTracker oct; + oct.SetSubtreeRoot(aSubtreeRoot); + for (nsTArray::size_type i = 0; i < mFrames.Length(); i++) { + nsIFrame* f = mFrames[i]; + if (aSubtreeRoot) { + // Reflowing the scroll frame, so recompute offsets. + ComputeStickyOffsets(f); + } + f->SetPosition(ComputePosition(f)); + oct.AddFrame(f); + } + oct.Flush(); +} + +void +StickyScrollContainer::ScrollPositionWillChange(nscoord aX, nscoord aY) +{ +} + +void +StickyScrollContainer::ScrollPositionDidChange(nscoord aX, nscoord aY) +{ + UpdatePositions(nsPoint(aX, aY), nullptr); +} + +} // namespace mozilla diff --git a/layout/generic/StickyScrollContainer.h b/layout/generic/StickyScrollContainer.h new file mode 100644 index 00000000000..fefe243f5b6 --- /dev/null +++ b/layout/generic/StickyScrollContainer.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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/. */ + +/** + * compute sticky positioning, both during reflow and when the scrolling + * container scrolls + */ + +#ifndef StickyScrollContainer_h +#define StickyScrollContainer_h + +#include "nsPoint.h" +#include "nsTArray.h" +#include "nsIScrollPositionListener.h" + +class nsRect; +class nsIFrame; +class nsIScrollableFrame; + +namespace mozilla { + +class StickyScrollContainer MOZ_FINAL : public nsIScrollPositionListener +{ +public: + /** + * Find the StickyScrollContainer associated with the scroll container of + * the given frame, creating it if necessary. + */ + static StickyScrollContainer* StickyScrollContainerForFrame(nsIFrame* aFrame); + + /** + * Find the StickyScrollContainer associated with the given scroll frame, + * if it exists. + */ + static StickyScrollContainer* GetStickyScrollContainerForScrollFrame(nsIFrame* aScrollFrame); + + void AddFrame(nsIFrame* aFrame) { + mFrames.AppendElement(aFrame); + } + void RemoveFrame(nsIFrame* aFrame) { + mFrames.RemoveElement(aFrame); + } + + // Compute the offsets for a sticky position element + static void ComputeStickyOffsets(nsIFrame* aFrame); + + /** + * Compute the position of a sticky positioned frame, based on information + * stored in its properties along with our scroll frame and scroll position. + */ + nsPoint ComputePosition(nsIFrame* aFrame) const; + + /** + * Compute and set the position of all sticky frames, given the current + * scroll position of the scroll frame. If not in reflow, aSubtreeRoot should + * be null; otherwise, overflow-area updates will be limited to not affect + * aSubtreeRoot or its ancestors. + */ + void UpdatePositions(nsPoint aScrollPosition, nsIFrame* aSubtreeRoot); + + // nsIScrollPositionListener + virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) MOZ_OVERRIDE; + virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) MOZ_OVERRIDE; + +private: + StickyScrollContainer(nsIScrollableFrame* aScrollFrame); + ~StickyScrollContainer(); + + /** + * Compute two rectangles that determine sticky positioning: |aStick|, based + * on the scroll container, and |aContain|, based on the containing block. + * Sticky positioning keeps the frame position (its upper-left corner) always + * within |aContain| and secondarily within |aStick|. + */ + void ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick, + nsRect* aContain) const; + + friend void DestroyStickyScrollContainer(void* aPropertyValue); + + nsIScrollableFrame* const mScrollFrame; + nsTArray mFrames; + nsPoint mScrollPosition; +}; + +} // namespace mozilla + +#endif /* StickyScrollContainer_h */ diff --git a/layout/generic/moz.build b/layout/generic/moz.build index 509da305fbe..8b6a82fb4aa 100644 --- a/layout/generic/moz.build +++ b/layout/generic/moz.build @@ -45,6 +45,7 @@ EXPORTS.mozilla.layout += [ CPP_SOURCES += [ 'FrameChildList.cpp', 'ScrollbarActivity.cpp', + 'StickyScrollContainer.cpp', 'TextOverflow.cpp', 'nsAbsoluteContainingBlock.cpp', 'nsBRFrame.cpp', diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 3d5465af0a0..a2e5ea6a44b 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -73,6 +73,7 @@ #include "gfxContext.h" #include "nsRenderingContext.h" #include "nsAbsoluteContainingBlock.h" +#include "StickyScrollContainer.h" #include "nsFontInflationData.h" #include "gfxASurface.h" #include "nsRegion.h" @@ -510,6 +511,9 @@ nsFrame::Init(nsIContent* aContent, // property, so we can set this bit here and then ignore it. mState |= NS_FRAME_MAY_BE_TRANSFORMED; } + if (disp->mPosition == NS_STYLE_POSITION_STICKY) { + StickyScrollContainer::StickyScrollContainerForFrame(this)->AddFrame(this); + } if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) || !GetParent() #ifdef DEBUG @@ -588,6 +592,11 @@ nsFrame::DestroyFrom(nsIFrame* aDestructRoot) nsSVGEffects::InvalidateDirectRenderingObservers(this); + if (StyleDisplay()->mPosition == NS_STYLE_POSITION_STICKY) { + StickyScrollContainer::StickyScrollContainerForFrame(this)-> + RemoveFrame(this); + } + // Get the view pointer now before the frame properties disappear // when we call NotifyDestroyingFrame() nsView* view = GetView(); @@ -2089,7 +2098,8 @@ nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder, bool isPositioned = disp->IsPositioned(child); bool isStackingContext = - (isPositioned && pos->mZIndex.GetUnit() == eStyleUnit_Integer) || + (isPositioned && (disp->mPosition == NS_STYLE_POSITION_STICKY || + pos->mZIndex.GetUnit() == eStyleUnit_Integer)) || isVisuallyAtomic || (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT); if (isVisuallyAtomic || isPositioned || (!isSVG && disp->IsFloating(child)) || diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 068b7c919ba..85ddcd58c81 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -48,6 +48,7 @@ #include "nsThemeConstants.h" #include "nsSVGIntegrationUtils.h" #include "nsIScrollPositionListener.h" +#include "StickyScrollContainer.h" #include #include // for std::abs(int/long) #include // for std::abs(float/double) @@ -827,6 +828,7 @@ nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext, state.mContentsOverflowAreas + mInner.mScrolledFrame->GetPosition()); } + mInner.UpdateSticky(); FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus); if (!InInitialReflow() && !mInner.mHadNonInitialReflow) { @@ -3544,6 +3546,8 @@ nsXULScrollFrame::Layout(nsBoxLayoutState& aState) mInner.mHadNonInitialReflow = true; } + mInner.UpdateSticky(); + // Set up overflow areas for block frames for the benefit of // text-overflow. nsIFrame* f = mInner.mScrolledFrame->GetContentInsertionFrame(); @@ -3713,6 +3717,17 @@ nsGfxScrollFrameInner::UpdateOverflow() return mOuter->nsContainerFrame::UpdateOverflow(); } +void +nsGfxScrollFrameInner::UpdateSticky() +{ + StickyScrollContainer* ssc = StickyScrollContainer:: + GetStickyScrollContainerForScrollFrame(mOuter); + if (ssc) { + nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter); + ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter); + } +} + void nsGfxScrollFrameInner::AdjustScrollbarRectForResizer( nsIFrame* aFrame, nsPresContext* aPresContext, diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h index 7e9b43ebfe0..b04496aebf7 100644 --- a/layout/generic/nsGfxScrollFrame.h +++ b/layout/generic/nsGfxScrollFrame.h @@ -276,6 +276,8 @@ public: bool UpdateOverflow(); + void UpdateSticky(); + // adjust the scrollbar rectangle aRect to account for any visible resizer. // aHasResizer specifies if there is a content resizer, however this method // will also check if a widget resizer is present as well. diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index 9ede7ffaae3..d8bbfa82013 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -23,6 +23,7 @@ #include "nsLayoutUtils.h" #include "mozilla/Preferences.h" #include "nsFontInflationData.h" +#include "StickyScrollContainer.h" #include #ifdef DEBUG @@ -32,6 +33,7 @@ #endif using namespace mozilla; +using namespace mozilla::css; using namespace mozilla::layout; enum eNormalLineHeightControl { @@ -842,6 +844,9 @@ nsHTMLReflowState::ApplyRelativePositioning(nsIFrame* aFrame, const nsStyleDisplay* display = aFrame->StyleDisplay(); if (NS_STYLE_POSITION_RELATIVE == display->mPosition) { *aPosition += nsPoint(aComputedOffsets.left, aComputedOffsets.top); + } else if (NS_STYLE_POSITION_STICKY == display->mPosition) { + *aPosition = StickyScrollContainer::StickyScrollContainerForFrame(aFrame)-> + ComputePosition(aFrame); } } @@ -1940,8 +1945,11 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext, // Compute our offsets if the element is relatively positioned. We need // the correct containing block width and height here, which is why we need - // to do it after all the quirks-n-such above. - if (mStyleDisplay->IsRelativelyPositioned(frame)) { + // to do it after all the quirks-n-such above. (If the element is sticky + // positioned, we need to wait until the scroll container knows its size, + // so we compute offsets from StickyScrollContainer::UpdatePositions.) + if (mStyleDisplay->IsRelativelyPositioned(frame) && + NS_STYLE_POSITION_RELATIVE == mStyleDisplay->mPosition) { uint8_t direction = NS_STYLE_DIRECTION_LTR; if (cbrs && NS_STYLE_DIRECTION_RTL == cbrs->mStyleVisibility->mDirection) { direction = NS_STYLE_DIRECTION_RTL; diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 56df0108238..ae854e69e81 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -927,6 +927,7 @@ public: NS_DECLARE_FRAME_PROPERTY(IBSplitSpecialPrevSibling, nullptr) NS_DECLARE_FRAME_PROPERTY(NormalPositionProperty, DestroyPoint) + NS_DECLARE_FRAME_PROPERTY(ComputedStickyOffsetProperty, DestroyMargin) NS_DECLARE_FRAME_PROPERTY(OutlineInnerRectProperty, DestroyRect) NS_DECLARE_FRAME_PROPERTY(PreEffectsBBoxProperty, DestroyRect) diff --git a/layout/reftests/position-sticky/bottom-1-ref.html b/layout/reftests/position-sticky/bottom-1-ref.html new file mode 100644 index 00000000000..ac107c0d037 --- /dev/null +++ b/layout/reftests/position-sticky/bottom-1-ref.html @@ -0,0 +1,25 @@ + + + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-1.html b/layout/reftests/position-sticky/bottom-1.html new file mode 100644 index 00000000000..183e012f02a --- /dev/null +++ b/layout/reftests/position-sticky/bottom-1.html @@ -0,0 +1,29 @@ + + + + + CSS Test: Sticky Positioning - bottom, normal position + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-2-ref.html b/layout/reftests/position-sticky/bottom-2-ref.html new file mode 100644 index 00000000000..368dc22d4c8 --- /dev/null +++ b/layout/reftests/position-sticky/bottom-2-ref.html @@ -0,0 +1,25 @@ + + + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-2a.html b/layout/reftests/position-sticky/bottom-2a.html new file mode 100644 index 00000000000..5a52cb9048c --- /dev/null +++ b/layout/reftests/position-sticky/bottom-2a.html @@ -0,0 +1,29 @@ + + + + + CSS Test: Sticky Positioning - bottom, normal position + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-2b.html b/layout/reftests/position-sticky/bottom-2b.html new file mode 100644 index 00000000000..62ba2499b99 --- /dev/null +++ b/layout/reftests/position-sticky/bottom-2b.html @@ -0,0 +1,29 @@ + + + + + CSS Test: Sticky Positioning - bottom, stuck + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-2c.html b/layout/reftests/position-sticky/bottom-2c.html new file mode 100644 index 00000000000..2408b7d7e0e --- /dev/null +++ b/layout/reftests/position-sticky/bottom-2c.html @@ -0,0 +1,30 @@ + + + + + + CSS Test: Sticky Positioning - bottom, stuck + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-3-ref.html b/layout/reftests/position-sticky/bottom-3-ref.html new file mode 100644 index 00000000000..c40d5c9659f --- /dev/null +++ b/layout/reftests/position-sticky/bottom-3-ref.html @@ -0,0 +1,32 @@ + + + + + + + + +
+
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-3.html b/layout/reftests/position-sticky/bottom-3.html new file mode 100644 index 00000000000..996b0226839 --- /dev/null +++ b/layout/reftests/position-sticky/bottom-3.html @@ -0,0 +1,37 @@ + + + + + + CSS Test: Sticky Positioning - bottom, stuck + + + + + + +
+
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-4-ref.html b/layout/reftests/position-sticky/bottom-4-ref.html new file mode 100644 index 00000000000..822565bfd1c --- /dev/null +++ b/layout/reftests/position-sticky/bottom-4-ref.html @@ -0,0 +1,34 @@ + + + + + + + + +
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/bottom-4.html b/layout/reftests/position-sticky/bottom-4.html new file mode 100644 index 00000000000..5a06b4ec9aa --- /dev/null +++ b/layout/reftests/position-sticky/bottom-4.html @@ -0,0 +1,41 @@ + + + + + CSS Test: Sticky Positioning - bottom, contained + + + + + + +
+
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/containing-block-1-ref.html b/layout/reftests/position-sticky/containing-block-1-ref.html new file mode 100644 index 00000000000..ccb25d81090 --- /dev/null +++ b/layout/reftests/position-sticky/containing-block-1-ref.html @@ -0,0 +1,32 @@ + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/containing-block-1.html b/layout/reftests/position-sticky/containing-block-1.html new file mode 100644 index 00000000000..cb09717e5af --- /dev/null +++ b/layout/reftests/position-sticky/containing-block-1.html @@ -0,0 +1,35 @@ + + + + + CSS Test: Sticky Positioning - absolute containing block + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/initial-1-ref.html b/layout/reftests/position-sticky/initial-1-ref.html new file mode 100644 index 00000000000..70b0b6eb039 --- /dev/null +++ b/layout/reftests/position-sticky/initial-1-ref.html @@ -0,0 +1,27 @@ + + + + + + + +
+ + diff --git a/layout/reftests/position-sticky/initial-1.html b/layout/reftests/position-sticky/initial-1.html new file mode 100644 index 00000000000..6e0bb670577 --- /dev/null +++ b/layout/reftests/position-sticky/initial-1.html @@ -0,0 +1,30 @@ + + + + + CSS Test: Sticky Positioning - initial reflow + + + + + +
+ + diff --git a/layout/reftests/position-sticky/initial-scroll-1-ref.html b/layout/reftests/position-sticky/initial-scroll-1-ref.html new file mode 100644 index 00000000000..bb021dbe7af --- /dev/null +++ b/layout/reftests/position-sticky/initial-scroll-1-ref.html @@ -0,0 +1,35 @@ + + + + + + + +
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/initial-scroll-1.html b/layout/reftests/position-sticky/initial-scroll-1.html new file mode 100644 index 00000000000..343c6295ee2 --- /dev/null +++ b/layout/reftests/position-sticky/initial-scroll-1.html @@ -0,0 +1,38 @@ + + + + + CSS Test: Sticky Positioning - initial scroll position + + + + + +
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/left-1-ref.html b/layout/reftests/position-sticky/left-1-ref.html new file mode 100644 index 00000000000..ebcf46f4fb6 --- /dev/null +++ b/layout/reftests/position-sticky/left-1-ref.html @@ -0,0 +1,42 @@ + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/left-1.html b/layout/reftests/position-sticky/left-1.html new file mode 100644 index 00000000000..779f9309775 --- /dev/null +++ b/layout/reftests/position-sticky/left-1.html @@ -0,0 +1,51 @@ + + + + + CSS Test: Sticky Positioning - left, normal position + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/left-2-ref.html b/layout/reftests/position-sticky/left-2-ref.html new file mode 100644 index 00000000000..ca5c35a6884 --- /dev/null +++ b/layout/reftests/position-sticky/left-2-ref.html @@ -0,0 +1,37 @@ + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/left-2.html b/layout/reftests/position-sticky/left-2.html new file mode 100644 index 00000000000..b3c3e4ee373 --- /dev/null +++ b/layout/reftests/position-sticky/left-2.html @@ -0,0 +1,51 @@ + + + + + CSS Test: Sticky Positioning - left, stuck + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/left-3-ref.html b/layout/reftests/position-sticky/left-3-ref.html new file mode 100644 index 00000000000..1ae2eaf4112 --- /dev/null +++ b/layout/reftests/position-sticky/left-3-ref.html @@ -0,0 +1,39 @@ + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/left-3.html b/layout/reftests/position-sticky/left-3.html new file mode 100644 index 00000000000..ef9e2b303d8 --- /dev/null +++ b/layout/reftests/position-sticky/left-3.html @@ -0,0 +1,51 @@ + + + + + CSS Test: Sticky Positioning - left, contained + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/left-right-1-ref.html b/layout/reftests/position-sticky/left-right-1-ref.html new file mode 100644 index 00000000000..89b7a060811 --- /dev/null +++ b/layout/reftests/position-sticky/left-right-1-ref.html @@ -0,0 +1,42 @@ + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/left-right-1.html b/layout/reftests/position-sticky/left-right-1.html new file mode 100644 index 00000000000..ca7a77ff755 --- /dev/null +++ b/layout/reftests/position-sticky/left-right-1.html @@ -0,0 +1,46 @@ + + + + + CSS Test: Sticky Positioning - left+right, at left + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/left-right-2-ref.html b/layout/reftests/position-sticky/left-right-2-ref.html new file mode 100644 index 00000000000..26706833913 --- /dev/null +++ b/layout/reftests/position-sticky/left-right-2-ref.html @@ -0,0 +1,33 @@ + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/left-right-2.html b/layout/reftests/position-sticky/left-right-2.html new file mode 100644 index 00000000000..7d9068f2a05 --- /dev/null +++ b/layout/reftests/position-sticky/left-right-2.html @@ -0,0 +1,50 @@ + + + + + CSS Test: Sticky Positioning - left+right, at middle + + + + + + +
+
+
+ + + diff --git a/layout/reftests/position-sticky/left-right-3-ref.html b/layout/reftests/position-sticky/left-right-3-ref.html new file mode 100644 index 00000000000..9648f4b2841 --- /dev/null +++ b/layout/reftests/position-sticky/left-right-3-ref.html @@ -0,0 +1,34 @@ + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/left-right-3.html b/layout/reftests/position-sticky/left-right-3.html new file mode 100644 index 00000000000..e62978220ba --- /dev/null +++ b/layout/reftests/position-sticky/left-right-3.html @@ -0,0 +1,50 @@ + + + + + CSS Test: Sticky Positioning - left+right, at right + + + + + + +
+
+
+ + + diff --git a/layout/reftests/position-sticky/margin-1-ref.html b/layout/reftests/position-sticky/margin-1-ref.html new file mode 100644 index 00000000000..f577b077a5f --- /dev/null +++ b/layout/reftests/position-sticky/margin-1-ref.html @@ -0,0 +1,31 @@ + + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/margin-1.html b/layout/reftests/position-sticky/margin-1.html new file mode 100644 index 00000000000..7a084d8048e --- /dev/null +++ b/layout/reftests/position-sticky/margin-1.html @@ -0,0 +1,41 @@ + + + + + CSS Test: Sticky Positioning - element margin + + + + + + + +
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/overconstrained-1-ref.html b/layout/reftests/position-sticky/overconstrained-1-ref.html new file mode 100644 index 00000000000..f458c239d93 --- /dev/null +++ b/layout/reftests/position-sticky/overconstrained-1-ref.html @@ -0,0 +1,25 @@ + + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/overconstrained-1.html b/layout/reftests/position-sticky/overconstrained-1.html new file mode 100644 index 00000000000..95d46b2412f --- /dev/null +++ b/layout/reftests/position-sticky/overconstrained-1.html @@ -0,0 +1,31 @@ + + + + + CSS Test: Sticky Positioning - top+bottom, overconstrained + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/overconstrained-2-ref.html b/layout/reftests/position-sticky/overconstrained-2-ref.html new file mode 100644 index 00000000000..8593fa3f5a0 --- /dev/null +++ b/layout/reftests/position-sticky/overconstrained-2-ref.html @@ -0,0 +1,40 @@ + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/overconstrained-2.html b/layout/reftests/position-sticky/overconstrained-2.html new file mode 100644 index 00000000000..f399c912e9a --- /dev/null +++ b/layout/reftests/position-sticky/overconstrained-2.html @@ -0,0 +1,46 @@ + + + + + CSS Test: Sticky Positioning - left+right, overconstrained + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/overconstrained-3-ref.html b/layout/reftests/position-sticky/overconstrained-3-ref.html new file mode 100644 index 00000000000..387c28eda80 --- /dev/null +++ b/layout/reftests/position-sticky/overconstrained-3-ref.html @@ -0,0 +1,28 @@ + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/overconstrained-3.html b/layout/reftests/position-sticky/overconstrained-3.html new file mode 100644 index 00000000000..852d356835f --- /dev/null +++ b/layout/reftests/position-sticky/overconstrained-3.html @@ -0,0 +1,51 @@ + + + + + CSS Test: Sticky Positioning - left+right, overconstrained + + + + + + +
+
+
+ + + diff --git a/layout/reftests/position-sticky/overcontain-1-ref.html b/layout/reftests/position-sticky/overcontain-1-ref.html new file mode 100644 index 00000000000..fd9c2fc551f --- /dev/null +++ b/layout/reftests/position-sticky/overcontain-1-ref.html @@ -0,0 +1,20 @@ + + + + + + + +
+ + diff --git a/layout/reftests/position-sticky/overcontain-1.html b/layout/reftests/position-sticky/overcontain-1.html new file mode 100644 index 00000000000..25f31dc5937 --- /dev/null +++ b/layout/reftests/position-sticky/overcontain-1.html @@ -0,0 +1,25 @@ + + + + + CSS Test: Sticky Positioning - containment and normal position + + + + + +
+ + diff --git a/layout/reftests/position-sticky/padding-1-ref.html b/layout/reftests/position-sticky/padding-1-ref.html new file mode 100644 index 00000000000..e23b7c9d397 --- /dev/null +++ b/layout/reftests/position-sticky/padding-1-ref.html @@ -0,0 +1,40 @@ + + + + + + + +
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/padding-1.html b/layout/reftests/position-sticky/padding-1.html new file mode 100644 index 00000000000..1a154a2e53f --- /dev/null +++ b/layout/reftests/position-sticky/padding-1.html @@ -0,0 +1,37 @@ + + + + + CSS Test: Sticky Positioning - percentage offsets + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/padding-2-ref.html b/layout/reftests/position-sticky/padding-2-ref.html new file mode 100644 index 00000000000..643c507307e --- /dev/null +++ b/layout/reftests/position-sticky/padding-2-ref.html @@ -0,0 +1,29 @@ + + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/padding-2.html b/layout/reftests/position-sticky/padding-2.html new file mode 100644 index 00000000000..03080bf1012 --- /dev/null +++ b/layout/reftests/position-sticky/padding-2.html @@ -0,0 +1,41 @@ + + + + + CSS Test: Sticky Positioning - offsets reference + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/padding-3-ref.html b/layout/reftests/position-sticky/padding-3-ref.html new file mode 100644 index 00000000000..153560bb5a5 --- /dev/null +++ b/layout/reftests/position-sticky/padding-3-ref.html @@ -0,0 +1,40 @@ + + + + + + + +
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/padding-3.html b/layout/reftests/position-sticky/padding-3.html new file mode 100644 index 00000000000..fc84983d690 --- /dev/null +++ b/layout/reftests/position-sticky/padding-3.html @@ -0,0 +1,39 @@ + + + + + CSS Test: Sticky Positioning - offsets reference, bottom/right + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/pref-1-disabled-ref.html b/layout/reftests/position-sticky/pref-1-disabled-ref.html new file mode 100644 index 00000000000..4485d7cee85 --- /dev/null +++ b/layout/reftests/position-sticky/pref-1-disabled-ref.html @@ -0,0 +1,20 @@ + + + + + + + + +

+ "position: sticky" is: +

disabled
+

+ + diff --git a/layout/reftests/position-sticky/pref-1-enabled-ref.html b/layout/reftests/position-sticky/pref-1-enabled-ref.html new file mode 100644 index 00000000000..c8dd55ed891 --- /dev/null +++ b/layout/reftests/position-sticky/pref-1-enabled-ref.html @@ -0,0 +1,20 @@ + + + + + + + + +

+ "position: sticky" is: +

enabled
+

+ + diff --git a/layout/reftests/position-sticky/pref-1.html b/layout/reftests/position-sticky/pref-1.html new file mode 100644 index 00000000000..d40042a3922 --- /dev/null +++ b/layout/reftests/position-sticky/pref-1.html @@ -0,0 +1,34 @@ + + + + + CSS Test: Sticky Positioning - support test + + + + + + +

+ "position: sticky" is: + +

enabled
+
disabled
+

+ + diff --git a/layout/reftests/position-sticky/reftest.list b/layout/reftests/position-sticky/reftest.list new file mode 100644 index 00000000000..098c9a667f9 --- /dev/null +++ b/layout/reftests/position-sticky/reftest.list @@ -0,0 +1,43 @@ +test-pref(layout.css.sticky.enabled,false) == pref-1.html pref-1-disabled-ref.html +test-pref(layout.css.sticky.enabled,true) == pref-1.html pref-1-enabled-ref.html +default-preferences pref(layout.css.sticky.enabled,true) + +== top-1.html top-1-ref.html +fuzzy-if(Android,4,914) == top-2.html top-2-ref.html +fuzzy-if(Android,4,2729) == top-3.html top-3-ref.html +== top-4.html top-4-ref.html +== top-5.html top-5-ref.html +== top-6.html top-6-ref.html +== bottom-1.html bottom-1-ref.html +== bottom-2a.html bottom-2-ref.html +== bottom-2b.html bottom-2-ref.html +== bottom-2c.html bottom-2-ref.html +== bottom-3.html bottom-3-ref.html +== bottom-4.html bottom-4-ref.html +fuzzy-if(Android,2,4) == left-1.html left-1-ref.html +fuzzy-if(Android,2,4) == left-2.html left-2-ref.html +== left-3.html left-3-ref.html +== right-1.html right-1-ref.html +fuzzy-if(Android,2,4) == right-2.html right-2-ref.html +fuzzy-if(Android,2,4) == right-3.html right-3-ref.html +== margin-1.html margin-1-ref.html +== padding-1.html padding-1-ref.html +== padding-2.html padding-2-ref.html +== padding-3.html padding-3-ref.html +== overcontain-1.html overcontain-1-ref.html +== initial-1.html initial-1-ref.html +== initial-scroll-1.html initial-scroll-1-ref.html +== scrollframe-reflow-1.html scrollframe-reflow-1-ref.html +== scrollframe-reflow-2.html scrollframe-reflow-2-ref.html +== scrollframe-auto-1.html scrollframe-auto-1-ref.html +== stacking-context-1.html stacking-context-1-ref.html +== top-bottom-1.html top-bottom-1-ref.html +== top-bottom-2.html top-bottom-2-ref.html +== top-bottom-3.html top-bottom-3-ref.html +== left-right-1.html left-right-1-ref.html +== left-right-2.html left-right-2-ref.html +== left-right-3.html left-right-3-ref.html +== containing-block-1.html containing-block-1-ref.html +== overconstrained-1.html overconstrained-1-ref.html +== overconstrained-2.html overconstrained-2-ref.html +== overconstrained-3.html overconstrained-3-ref.html diff --git a/layout/reftests/position-sticky/right-1-ref.html b/layout/reftests/position-sticky/right-1-ref.html new file mode 100644 index 00000000000..dc2457e871f --- /dev/null +++ b/layout/reftests/position-sticky/right-1-ref.html @@ -0,0 +1,40 @@ + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/right-1.html b/layout/reftests/position-sticky/right-1.html new file mode 100644 index 00000000000..8e0d96a48f5 --- /dev/null +++ b/layout/reftests/position-sticky/right-1.html @@ -0,0 +1,54 @@ + + + + + CSS Test: Sticky Positioning - right, stuck + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/right-2-ref.html b/layout/reftests/position-sticky/right-2-ref.html new file mode 100644 index 00000000000..6b3549293cf --- /dev/null +++ b/layout/reftests/position-sticky/right-2-ref.html @@ -0,0 +1,41 @@ + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/right-2.html b/layout/reftests/position-sticky/right-2.html new file mode 100644 index 00000000000..a8d7a14fd62 --- /dev/null +++ b/layout/reftests/position-sticky/right-2.html @@ -0,0 +1,54 @@ + + + + + CSS Test: Sticky Positioning - right, stuck + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/right-3-ref.html b/layout/reftests/position-sticky/right-3-ref.html new file mode 100644 index 00000000000..a2462b40f23 --- /dev/null +++ b/layout/reftests/position-sticky/right-3-ref.html @@ -0,0 +1,41 @@ + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/right-3.html b/layout/reftests/position-sticky/right-3.html new file mode 100644 index 00000000000..25b64314256 --- /dev/null +++ b/layout/reftests/position-sticky/right-3.html @@ -0,0 +1,54 @@ + + + + + CSS Test: Sticky Positioning - right, contained + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/scrollframe-auto-1-ref.html b/layout/reftests/position-sticky/scrollframe-auto-1-ref.html new file mode 100644 index 00000000000..f35afdf7feb --- /dev/null +++ b/layout/reftests/position-sticky/scrollframe-auto-1-ref.html @@ -0,0 +1,34 @@ + + + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/scrollframe-auto-1.html b/layout/reftests/position-sticky/scrollframe-auto-1.html new file mode 100644 index 00000000000..4132154ceb2 --- /dev/null +++ b/layout/reftests/position-sticky/scrollframe-auto-1.html @@ -0,0 +1,36 @@ + + + + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/scrollframe-reflow-1-ref.html b/layout/reftests/position-sticky/scrollframe-reflow-1-ref.html new file mode 100644 index 00000000000..b3fa4f230d4 --- /dev/null +++ b/layout/reftests/position-sticky/scrollframe-reflow-1-ref.html @@ -0,0 +1,25 @@ + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/scrollframe-reflow-1.html b/layout/reftests/position-sticky/scrollframe-reflow-1.html new file mode 100644 index 00000000000..3feb1eca442 --- /dev/null +++ b/layout/reftests/position-sticky/scrollframe-reflow-1.html @@ -0,0 +1,36 @@ + + + + + + + + + + +
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/scrollframe-reflow-2-ref.html b/layout/reftests/position-sticky/scrollframe-reflow-2-ref.html new file mode 100644 index 00000000000..e5b029687c4 --- /dev/null +++ b/layout/reftests/position-sticky/scrollframe-reflow-2-ref.html @@ -0,0 +1,25 @@ + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/scrollframe-reflow-2.html b/layout/reftests/position-sticky/scrollframe-reflow-2.html new file mode 100644 index 00000000000..b48fe003790 --- /dev/null +++ b/layout/reftests/position-sticky/scrollframe-reflow-2.html @@ -0,0 +1,36 @@ + + + + + + + + + + +
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/stacking-context-1-ref.html b/layout/reftests/position-sticky/stacking-context-1-ref.html new file mode 100644 index 00000000000..7d7db098fe5 --- /dev/null +++ b/layout/reftests/position-sticky/stacking-context-1-ref.html @@ -0,0 +1,39 @@ + + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/stacking-context-1.html b/layout/reftests/position-sticky/stacking-context-1.html new file mode 100644 index 00000000000..103f32a1247 --- /dev/null +++ b/layout/reftests/position-sticky/stacking-context-1.html @@ -0,0 +1,45 @@ + + + + + CSS Test: Sticky Positioning - stacking context + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-1-ref.html b/layout/reftests/position-sticky/top-1-ref.html new file mode 100644 index 00000000000..edb94fdbe81 --- /dev/null +++ b/layout/reftests/position-sticky/top-1-ref.html @@ -0,0 +1,34 @@ + + + + + + + + +
+
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-1.html b/layout/reftests/position-sticky/top-1.html new file mode 100644 index 00000000000..a7bdb58e369 --- /dev/null +++ b/layout/reftests/position-sticky/top-1.html @@ -0,0 +1,41 @@ + + + + + CSS Test: Sticky Positioning - top, normal position + + + + + + +
+
+
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-2-ref.html b/layout/reftests/position-sticky/top-2-ref.html new file mode 100644 index 00000000000..93788f3fe28 --- /dev/null +++ b/layout/reftests/position-sticky/top-2-ref.html @@ -0,0 +1,34 @@ + + + + + + + + +
+
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-2.html b/layout/reftests/position-sticky/top-2.html new file mode 100644 index 00000000000..d3d14f4ed95 --- /dev/null +++ b/layout/reftests/position-sticky/top-2.html @@ -0,0 +1,45 @@ + + + + + CSS Test: Sticky Positioning - top, normal position + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/top-3-ref.html b/layout/reftests/position-sticky/top-3-ref.html new file mode 100644 index 00000000000..8e72c080399 --- /dev/null +++ b/layout/reftests/position-sticky/top-3-ref.html @@ -0,0 +1,38 @@ + + + + + + + + +
+
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-3.html b/layout/reftests/position-sticky/top-3.html new file mode 100644 index 00000000000..a44baae2e60 --- /dev/null +++ b/layout/reftests/position-sticky/top-3.html @@ -0,0 +1,45 @@ + + + + + CSS Test: Sticky Positioning - top, stuck + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/top-4-ref.html b/layout/reftests/position-sticky/top-4-ref.html new file mode 100644 index 00000000000..8eaed81980a --- /dev/null +++ b/layout/reftests/position-sticky/top-4-ref.html @@ -0,0 +1,30 @@ + + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-4.html b/layout/reftests/position-sticky/top-4.html new file mode 100644 index 00000000000..71f7ff01c10 --- /dev/null +++ b/layout/reftests/position-sticky/top-4.html @@ -0,0 +1,45 @@ + + + + + CSS Test: Sticky Positioning - top, stuck + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/top-5-ref.html b/layout/reftests/position-sticky/top-5-ref.html new file mode 100644 index 00000000000..70d821344ef --- /dev/null +++ b/layout/reftests/position-sticky/top-5-ref.html @@ -0,0 +1,28 @@ + + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-5.html b/layout/reftests/position-sticky/top-5.html new file mode 100644 index 00000000000..27958069569 --- /dev/null +++ b/layout/reftests/position-sticky/top-5.html @@ -0,0 +1,45 @@ + + + + + CSS Test: Sticky Positioning - top, contained + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/top-6-ref.html b/layout/reftests/position-sticky/top-6-ref.html new file mode 100644 index 00000000000..9e96ee8bec0 --- /dev/null +++ b/layout/reftests/position-sticky/top-6-ref.html @@ -0,0 +1,32 @@ + + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-6.html b/layout/reftests/position-sticky/top-6.html new file mode 100644 index 00000000000..1e2909561e7 --- /dev/null +++ b/layout/reftests/position-sticky/top-6.html @@ -0,0 +1,49 @@ + + + + + CSS Test: Sticky Positioning - top, contained + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/top-bottom-1-ref.html b/layout/reftests/position-sticky/top-bottom-1-ref.html new file mode 100644 index 00000000000..3d1d6a35b5c --- /dev/null +++ b/layout/reftests/position-sticky/top-bottom-1-ref.html @@ -0,0 +1,27 @@ + + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-bottom-1.html b/layout/reftests/position-sticky/top-bottom-1.html new file mode 100644 index 00000000000..b73d40e8251 --- /dev/null +++ b/layout/reftests/position-sticky/top-bottom-1.html @@ -0,0 +1,31 @@ + + + + + CSS Test: Sticky Positioning - top+bottom, at top + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-bottom-2-ref.html b/layout/reftests/position-sticky/top-bottom-2-ref.html new file mode 100644 index 00000000000..3e440bc8fe7 --- /dev/null +++ b/layout/reftests/position-sticky/top-bottom-2-ref.html @@ -0,0 +1,23 @@ + + + + + + + + +
+
+
+
+ + diff --git a/layout/reftests/position-sticky/top-bottom-2.html b/layout/reftests/position-sticky/top-bottom-2.html new file mode 100644 index 00000000000..31443f5dfff --- /dev/null +++ b/layout/reftests/position-sticky/top-bottom-2.html @@ -0,0 +1,35 @@ + + + + + CSS Test: Sticky Positioning - top+bottom, at middle + + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/position-sticky/top-bottom-3-ref.html b/layout/reftests/position-sticky/top-bottom-3-ref.html new file mode 100644 index 00000000000..d1a8e2429c6 --- /dev/null +++ b/layout/reftests/position-sticky/top-bottom-3-ref.html @@ -0,0 +1,25 @@ + + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/top-bottom-3.html b/layout/reftests/position-sticky/top-bottom-3.html new file mode 100644 index 00000000000..b42f08919bb --- /dev/null +++ b/layout/reftests/position-sticky/top-bottom-3.html @@ -0,0 +1,35 @@ + + + + + CSS Test: Sticky Positioning - top+bottom, at bottom + + + + + + + +
+
+
+
+
+ + + diff --git a/layout/reftests/reftest.list b/layout/reftests/reftest.list index 6dd7f436f9e..2f78d3b0a04 100644 --- a/layout/reftests/reftest.list +++ b/layout/reftests/reftest.list @@ -250,6 +250,9 @@ skip-if(Android&&AndroidVersion>=15) include scrolling/reftest.list # selection include selection/reftest.list +# sticky positioning +include position-sticky/reftest.list + # svg/ include svg/reftest.list diff --git a/layout/reftests/svg/text/ignore-position-ref.svg b/layout/reftests/svg/text/ignore-position-ref.svg index 74a2a5fafa8..3d265f2ab78 100644 --- a/layout/reftests/svg/text/ignore-position-ref.svg +++ b/layout/reftests/svg/text/ignore-position-ref.svg @@ -7,5 +7,6 @@ hello there everyone hello there everyone hello there everyone + hello there everyone diff --git a/layout/reftests/svg/text/ignore-position.svg b/layout/reftests/svg/text/ignore-position.svg index 5493313e0ff..ec0878264f5 100644 --- a/layout/reftests/svg/text/ignore-position.svg +++ b/layout/reftests/svg/text/ignore-position.svg @@ -7,5 +7,6 @@ hello there everyone hello there everyone hello there everyone + hello there everyone diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index b4d6f68223f..7e435f60b95 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -485,6 +485,7 @@ CSS_KEY(static, static) CSS_KEY(status-bar, status_bar) CSS_KEY(step-end, step_end) CSS_KEY(step-start, step_start) +CSS_KEY(sticky, sticky) CSS_KEY(stretch, stretch) CSS_KEY(stretch-to-fit, stretch_to_fit) CSS_KEY(stroke, stroke) diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index e1ec600f29d..2ea8ac065ce 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1372,11 +1372,15 @@ const int32_t nsCSSProps::kPointerEventsKTable[] = { eCSSKeyword_UNKNOWN, -1 }; -const int32_t nsCSSProps::kPositionKTable[] = { +int32_t nsCSSProps::kPositionKTable[] = { eCSSKeyword_static, NS_STYLE_POSITION_STATIC, eCSSKeyword_relative, NS_STYLE_POSITION_RELATIVE, eCSSKeyword_absolute, NS_STYLE_POSITION_ABSOLUTE, eCSSKeyword_fixed, NS_STYLE_POSITION_FIXED, + // NOTE: This currently needs to be the last entry in the table, + // because the "layout.css.sticky.enabled" pref that disables + // this will disable all the entries after it, too. + eCSSKeyword_sticky, NS_STYLE_POSITION_STICKY, eCSSKeyword_UNKNOWN,-1 }; diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index 51298458d69..bd62e61cf5a 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -518,7 +518,9 @@ public: static const int32_t kPageSizeKTable[]; static const int32_t kPitchKTable[]; static const int32_t kPointerEventsKTable[]; - static const int32_t kPositionKTable[]; + // Not const because we modify its entries when the pref + // "layout.css.sticky.enabled" changes: + static int32_t kPositionKTable[]; static const int32_t kRadialGradientShapeKTable[]; static const int32_t kRadialGradientSizeKTable[]; static const int32_t kRadialGradientLegacySizeKTable[]; diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 7f22a4cc671..751e6abe9b6 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -3708,6 +3708,8 @@ nsComputedDOMStyle::GetOffsetWidthFor(mozilla::css::Side aSide) return GetStaticOffset(aSide); case NS_STYLE_POSITION_RELATIVE: return GetRelativeOffset(aSide); + case NS_STYLE_POSITION_STICKY: + return GetStickyOffset(aSide); case NS_STYLE_POSITION_ABSOLUTE: case NS_STYLE_POSITION_FIXED: return GetAbsoluteOffset(aSide); @@ -3808,6 +3810,36 @@ nsComputedDOMStyle::GetRelativeOffset(mozilla::css::Side aSide) return val; } +CSSValue* +nsComputedDOMStyle::GetStickyOffset(mozilla::css::Side aSide) +{ + nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue; + + const nsStylePosition* positionData = StylePosition(); + nsStyleCoord coord = positionData->mOffset.Get(aSide); + + NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord || + coord.GetUnit() == eStyleUnit_Percent || + coord.GetUnit() == eStyleUnit_Auto || + coord.IsCalcUnit(), + "Unexpected unit"); + + if (coord.GetUnit() == eStyleUnit_Auto) { + val->SetIdent(eCSSKeyword_auto); + return val; + } + PercentageBaseGetter baseGetter; + if (aSide == NS_SIDE_LEFT || aSide == NS_SIDE_RIGHT) { + baseGetter = &nsComputedDOMStyle::GetScrollFrameContentWidth; + } else { + baseGetter = &nsComputedDOMStyle::GetScrollFrameContentHeight; + } + + val->SetAppUnits(StyleCoordToNSCoord(coord, baseGetter, 0, false)); + return val; +} + + CSSValue* nsComputedDOMStyle::GetStaticOffset(mozilla::css::Side aSide) @@ -4126,6 +4158,50 @@ nsComputedDOMStyle::GetCBContentHeight(nscoord& aHeight) return true; } +bool +nsComputedDOMStyle::GetScrollFrameContentWidth(nscoord& aWidth) +{ + if (!mOuterFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + nsIScrollableFrame* scrollableFrame = + nsLayoutUtils::GetNearestScrollableFrame(mOuterFrame->GetParent(), + nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + + if (!scrollableFrame) { + return false; + } + aWidth = + scrollableFrame->GetScrolledFrame()->GetContentRectRelativeToSelf().width; + return true; +} + +bool +nsComputedDOMStyle::GetScrollFrameContentHeight(nscoord& aHeight) +{ + if (!mOuterFrame) { + return false; + } + + AssertFlushedPendingReflows(); + + nsIScrollableFrame* scrollableFrame = + nsLayoutUtils::GetNearestScrollableFrame(mOuterFrame->GetParent(), + nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + + if (!scrollableFrame) { + return false; + } + aHeight = + scrollableFrame->GetScrolledFrame()->GetContentRectRelativeToSelf().height; + return true; +} + bool nsComputedDOMStyle::GetFrameBorderRectWidth(nscoord& aWidth) { diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index ae3040561ea..dfd6e41253d 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -125,6 +125,8 @@ private: mozilla::dom::CSSValue* GetRelativeOffset(mozilla::css::Side aSide); + mozilla::dom::CSSValue* GetStickyOffset(mozilla::css::Side aSide); + mozilla::dom::CSSValue* GetStaticOffset(mozilla::css::Side aSide); mozilla::dom::CSSValue* GetPaddingWidthFor(mozilla::css::Side aSide); @@ -491,6 +493,8 @@ private: bool GetCBContentWidth(nscoord& aWidth); bool GetCBContentHeight(nscoord& aWidth); + bool GetScrollFrameContentWidth(nscoord& aWidth); + bool GetScrollFrameContentHeight(nscoord& aHeight); bool GetFrameBoundsWidthForTransform(nscoord &aWidth); bool GetFrameBoundsHeightForTransform(nscoord &aHeight); bool GetFrameBorderRectWidth(nscoord& aWidth); diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index a4dfbfb9a04..7818a11f529 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -531,6 +531,7 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { #define NS_STYLE_POSITION_RELATIVE 1 #define NS_STYLE_POSITION_ABSOLUTE 2 #define NS_STYLE_POSITION_FIXED 3 +#define NS_STYLE_POSITION_STICKY 4 // See nsStylePosition.mClip #define NS_STYLE_CLIP_AUTO 0x00 diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index d905bf8628a..b816d98be2f 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1788,7 +1788,8 @@ struct nsStyleDisplay { } bool IsRelativelyPositionedStyle() const { - return mPosition == NS_STYLE_POSITION_RELATIVE; + return NS_STYLE_POSITION_RELATIVE == mPosition || + NS_STYLE_POSITION_STICKY == mPosition; } bool IsScrollableOverflow() const { diff --git a/layout/style/test/Makefile.in b/layout/style/test/Makefile.in index 2e898bcaeb0..ebc9aab2cd5 100644 --- a/layout/style/test/Makefile.in +++ b/layout/style/test/Makefile.in @@ -137,6 +137,8 @@ MOCHITEST_FILES = test_acid3_test46.html \ test_parser_diagnostics_unprintables.html \ test_pixel_lengths.html \ test_pointer-events.html \ + file_position_sticky.html \ + test_position_sticky.html \ test_property_database.html \ test_priority_preservation.html \ test_property_syntax_errors.html \ diff --git a/layout/style/test/file_position_sticky.html b/layout/style/test/file_position_sticky.html new file mode 100644 index 00000000000..20e1550cb71 --- /dev/null +++ b/layout/style/test/file_position_sticky.html @@ -0,0 +1,88 @@ + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+ + diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index e0f692c7e1f..1d0d2a6eb33 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4740,6 +4740,10 @@ if (SpecialPowers.getBoolPref("layout.css.osx-font-smoothing.enabled")) { }; } +if (SpecialPowers.getBoolPref("layout.css.sticky.enabled")) { + gCSSProperties["position"].other_values.push("sticky"); +} + if (SpecialPowers.getBoolPref("layout.css.mix-blend-mode.enabled")) { gCSSProperties["mix-blend-mode"] = { domProp: "mixBlendMode", diff --git a/layout/style/test/test_position_sticky.html b/layout/style/test/test_position_sticky.html new file mode 100644 index 00000000000..0164f2219a0 --- /dev/null +++ b/layout/style/test/test_position_sticky.html @@ -0,0 +1,42 @@ + + + + + + Test for Bug 886646 + + + + +Mozilla Bug 886646 +
+ +
+
+
+
+ + diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 6dcf414e61e..25e1cc31577 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -1795,6 +1795,9 @@ pref("layout.css.filters.enabled", false); // Is support for CSS Flexbox enabled? pref("layout.css.flexbox.enabled", true); +// Is support for CSS sticky positioning enabled? +pref("layout.css.sticky.enabled", false); + // Is support for the CSS4 image-orientation property enabled? pref("layout.css.image-orientation.enabled", true); diff --git a/netwerk/base/public/nsIServerSocket.idl b/netwerk/base/public/nsIServerSocket.idl index 072eaceafbe..74e5c80988a 100644 --- a/netwerk/base/public/nsIServerSocket.idl +++ b/netwerk/base/public/nsIServerSocket.idl @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" +#include "nsIFile.idl" interface nsIServerSocketListener; interface nsISocketTransport; @@ -18,7 +19,7 @@ typedef unsigned long nsServerSocketFlag; * * An interface to a server socket that can accept incoming connections. */ -[scriptable, uuid(0df6a0e2-a6b1-4d4c-b30d-f2cb093444e3)] +[scriptable, uuid(7a9c39cb-a13f-4eef-9bdf-a74301628742)] interface nsIServerSocket : nsISupports { /** @@ -94,6 +95,61 @@ interface nsIServerSocket : nsISupports */ [noscript] void initWithAddress([const] in PRNetAddrPtr aAddr, in long aBackLog); + /** + * initWithFilename + * + * This method initializes a Unix domain or "local" server socket. Such + * a socket has a name in the filesystem, like an ordinary file. To + * connect, a client supplies the socket's filename, and the usual + * permission checks on socket apply. + * + * This makes Unix domain sockets useful for communication between the + * programs being run by a specific user on a single machine: the + * operating system takes care of authentication, and the user's home + * directory or profile directory provide natural per-user rendezvous + * points. + * + * Since Unix domain sockets are always local to the machine, they are + * not affected by the nsIIOService's 'offline' flag. + * + * The system-level socket API may impose restrictions on the length of + * the filename that are stricter than those of the underlying + * filesystem. If the file name is too long, this returns + * NS_ERROR_FILE_NAME_TOO_LONG. + * + * All components of the path prefix of |aPath| must name directories; + * otherwise, this returns NS_ERROR_FILE_NOT_DIRECTORY. + * + * This call requires execute permission on all directories containing + * the one in which the socket is to be created, and write and execute + * permission on the directory itself. Otherwise, this returns + * NS_ERROR_CONNECTION_REFUSED. + * + * This call creates the socket's directory entry. There must not be + * any existing entry with the given name. If there is, this returns + * NS_ERROR_SOCKET_ADDRESS_IN_USE. + * + * On systems that don't support Unix domain sockets at all, this + * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED. + * + * @param aPath nsIFile + * The file name at which the socket should be created. + * + * @param aPermissions unsigned long + * Unix-style permission bits to be applied to the new socket. + * + * Note about permissions: Linux's unix(7) man page claims that some + * BSD-derived systems ignore permissions on UNIX-domain sockets; + * NetBSD's bind(2) man page agrees, but says it does check now (dated + * 2005). POSIX has required 'connect' to fail if write permission on + * the socket itself is not granted since 2003 (Issue 6). NetBSD says + * that the permissions on the containing directory (execute) have + * always applied, so creating sockets in appropriately protected + * directories should be secure on both old and new systems. + */ + void initWithFilename(in nsIFile aPath, in unsigned long aPermissions, + in long aBacklog); + /** * close * diff --git a/netwerk/base/public/nsISocketTransport.idl b/netwerk/base/public/nsISocketTransport.idl index 0c8c585aadd..e7598002fe3 100644 --- a/netwerk/base/public/nsISocketTransport.idl +++ b/netwerk/base/public/nsISocketTransport.idl @@ -27,34 +27,37 @@ native NetAddr(mozilla::net::NetAddr); interface nsISocketTransport : nsITransport { /** - * Get the host for the underlying socket connection. + * Get the peer's host for the underlying socket connection. + * For Unix domain sockets, this is a pathname, or the empty string for + * unnamed and abstract socket addresses. */ readonly attribute AUTF8String host; /** * Get the port for the underlying socket connection. + * For Unix domain sockets, this is zero. */ readonly attribute long port; - /** + /** * Returns the IP address of the socket connection peer. This * attribute is defined only once a connection has been established. */ [noscript] NetAddr getPeerAddr(); - /** + /** * Returns the IP address of the initiating end. This attribute * is defined only once a connection has been established. */ [noscript] NetAddr getSelfAddr(); - /** + /** * Returns a scriptable version of getPeerAddr. This attribute is defined * only once a connection has been established. */ nsINetAddr getScriptablePeerAddr(); - /** + /** * Returns a scriptable version of getSelfAddr. This attribute is defined * only once a connection has been established. */ diff --git a/netwerk/base/public/nsISocketTransportService.idl b/netwerk/base/public/nsISocketTransportService.idl index 7ac110bd5be..cb328426b77 100644 --- a/netwerk/base/public/nsISocketTransportService.idl +++ b/netwerk/base/public/nsISocketTransportService.idl @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" +#include "nsIFile.idl" interface nsISocketTransport; interface nsIProxyInfo; @@ -17,7 +18,7 @@ struct PRFileDesc; [ptr] native PRFileDescPtr(PRFileDesc); [ptr] native nsASocketHandlerPtr(nsASocketHandler); -[scriptable, uuid(185B3A5D-8729-436D-9693-7BDCCB9C2216)] +[scriptable, uuid(ad56b25f-e6bb-4db3-9f7b-5b7db33fd2b1)] interface nsISocketTransportService : nsISupports { /** @@ -36,7 +37,7 @@ interface nsISocketTransportService : nsISupports * specifies the transport-layer proxy type to use. null if no * proxy. used for communicating information about proxies like * SOCKS (which are transparent to upper protocols). - * + * * @see nsIProxiedProtocolHandler * @see nsIProtocolProxyService::GetProxyInfo * @@ -45,10 +46,44 @@ interface nsISocketTransportService : nsISupports nsISocketTransport createTransport([array, size_is(aTypeCount)] in string aSocketTypes, in unsigned long aTypeCount, - in AUTF8String aHost, + in AUTF8String aHost, in long aPort, in nsIProxyInfo aProxyInfo); + /** + * Create a transport built on a Unix domain socket, connecting to the + * given filename. + * + * Since Unix domain sockets are always local to the machine, they are + * not affected by the nsIIOService's 'offline' flag. + * + * On systems that don't support Unix domain sockets at all, this + * returns NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED. + * + * The system-level socket API may impose restrictions on the length of + * the filename that are stricter than those of the underlying + * filesystem. If the file name is too long, this returns + * NS_ERROR_FILE_NAME_TOO_LONG. + * + * The |aPath| parameter must specify an existing directory entry. + * Otherwise, this returns NS_ERROR_FILE_NOT_FOUND. + * + * The program must have search permission on all components of the + * path prefix of |aPath|, and read and write permission on |aPath| + * itself. Without such permission, this returns + * NS_ERROR_CONNECTION_REFUSED. + * + * The |aPath| parameter must refer to a unix-domain socket. Otherwise, + * this returns NS_ERROR_CONNECTION_REFUSED. (POSIX specifies + * ECONNREFUSED when "the target address was not listening for + * connections", and this is what Linux returns.) + * + * @param aPath + * The file name of the Unix domain socket to which we should + * connect. + */ + nsISocketTransport createUnixDomainTransport(in nsIFile aPath); + /** * Adds a new socket to the list of controlled sockets. * diff --git a/netwerk/base/public/nsITransport.idl b/netwerk/base/public/nsITransport.idl index 6fbec334a7e..a7bf364d162 100644 --- a/netwerk/base/public/nsITransport.idl +++ b/netwerk/base/public/nsITransport.idl @@ -87,7 +87,7 @@ interface nsITransport : nsISupports * * OPEN_UNBUFFERED * If specified, the resulting stream may not support WriteSegments and - * WriteFrom. WriteSegments and WriteFrom are only gauranteed to be + * WriteFrom. WriteSegments and WriteFrom are only guaranteed to be * implemented when this flag is NOT specified. * * @param aFlags diff --git a/netwerk/base/src/nsServerSocket.cpp b/netwerk/base/src/nsServerSocket.cpp index cdef53f4f8b..95a7c435069 100644 --- a/netwerk/base/src/nsServerSocket.cpp +++ b/netwerk/base/src/nsServerSocket.cpp @@ -173,6 +173,13 @@ nsServerSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags) PRNetAddr prClientAddr; NetAddr clientAddr; + // NSPR doesn't tell us the peer address's length (as provided by the + // 'accept' system call), so we can't distinguish between named, + // unnamed, and abstract peer addresses. Clear prClientAddr first, so + // that the path will at least be reliably empty for unnamed and + // abstract addresses, and not garbage when the peer is unnamed. + memset(&prClientAddr, 0, sizeof(prClientAddr)); + clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT); PRNetAddrToNetAddr(&prClientAddr, &clientAddr); if (!clientFD) @@ -230,6 +237,15 @@ nsServerSocket::OnSocketDetached(PRFileDesc *fd) void nsServerSocket::IsLocal(bool *aIsLocal) { +#if defined(XP_UNIX) || defined(XP_OS2) + // Unix-domain sockets are always local. + if (mAddr.raw.family == PR_AF_LOCAL) + { + *aIsLocal = true; + return; + } +#endif + // If bound to loopback, this server socket only accepts local connections. *aIsLocal = PR_IsNetAddrType(&mAddr, PR_IpAddrLoopback); } @@ -257,6 +273,35 @@ nsServerSocket::Init(int32_t aPort, bool aLoopbackOnly, int32_t aBackLog) return InitSpecialConnection(aPort, aLoopbackOnly ? LoopbackOnly : 0, aBackLog); } +NS_IMETHODIMP +nsServerSocket::InitWithFilename(nsIFile *aPath, uint32_t aPermissions, int32_t aBacklog) +{ +#if defined(XP_UNIX) || defined(XP_OS2) + nsresult rv; + + nsAutoCString path; + rv = aPath->GetNativePath(path); + if (NS_FAILED(rv)) + return rv; + + // Create a Unix domain PRNetAddr referring to the given path. + PRNetAddr addr; + if (path.Length() > sizeof(addr.local.path) - 1) + return NS_ERROR_FILE_NAME_TOO_LONG; + addr.local.family = PR_AF_LOCAL; + memcpy(addr.local.path, path.get(), path.Length()); + addr.local.path[path.Length()] = '\0'; + + rv = InitWithAddress(&addr, aBacklog); + if (NS_FAILED(rv)) + return rv; + + return aPath->SetPermissions(aPermissions); +#else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +#endif +} + NS_IMETHODIMP nsServerSocket::InitSpecialConnection(int32_t aPort, nsServerSocketFlag aFlags, int32_t aBackLog) @@ -289,7 +334,7 @@ nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog) if (!mFD) { NS_WARNING("unable to create server socket"); - return NS_ERROR_FAILURE; + return ErrorAccordingToNSPR(PR_GetError()); } PRSocketOptionData opt; @@ -330,8 +375,9 @@ nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog) return NS_OK; fail: + nsresult rv = ErrorAccordingToNSPR(PR_GetError()); Close(); - return NS_ERROR_FAILURE; + return rv; } NS_IMETHODIMP @@ -468,8 +514,11 @@ nsServerSocket::GetPort(int32_t *aResult) uint16_t port; if (mAddr.raw.family == PR_AF_INET) port = mAddr.inet.port; - else + else if (mAddr.raw.family == PR_AF_INET6) port = mAddr.ipv6.port; + else + return NS_ERROR_FAILURE; + *aResult = (int32_t) PR_ntohs(port); return NS_OK; } diff --git a/netwerk/base/src/nsSocketTransport2.cpp b/netwerk/base/src/nsSocketTransport2.cpp index 14313eed8ab..6c00641561b 100644 --- a/netwerk/base/src/nsSocketTransport2.cpp +++ b/netwerk/base/src/nsSocketTransport2.cpp @@ -155,23 +155,88 @@ ErrorAccordingToNSPR(PRErrorCode errorCode) rv = NS_ERROR_NET_INTERRUPT; break; case PR_CONNECT_REFUSED_ERROR: - case PR_NETWORK_UNREACHABLE_ERROR: // XXX need new nsresult for this! - case PR_HOST_UNREACHABLE_ERROR: // XXX and this! + // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We + // could get better diagnostics by adding distinct XPCOM error codes for + // each of these, but there are a lot of places in Gecko that check + // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to + // be checked. + case PR_NETWORK_UNREACHABLE_ERROR: + case PR_HOST_UNREACHABLE_ERROR: case PR_ADDRESS_NOT_AVAILABLE_ERROR: // Treat EACCES as a soft error since (at least on Linux) connect() returns // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784. - case PR_ADDRESS_NOT_SUPPORTED_ERROR: case PR_NO_ACCESS_RIGHTS_ERROR: rv = NS_ERROR_CONNECTION_REFUSED; break; + case PR_ADDRESS_NOT_SUPPORTED_ERROR: + rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; + break; case PR_IO_TIMEOUT_ERROR: case PR_CONNECT_TIMEOUT_ERROR: rv = NS_ERROR_NET_TIMEOUT; break; + case PR_OUT_OF_MEMORY_ERROR: + // These really indicate that the descriptor table filled up, or that the + // kernel ran out of network buffers - but nobody really cares which part of + // the system ran out of memory. + case PR_PROC_DESC_TABLE_FULL_ERROR: + case PR_SYS_DESC_TABLE_FULL_ERROR: + case PR_INSUFFICIENT_RESOURCES_ERROR: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + case PR_ADDRESS_IN_USE_ERROR: + rv = NS_ERROR_SOCKET_ADDRESS_IN_USE; + break; + // These filename-related errors can arise when using Unix-domain sockets. + case PR_FILE_NOT_FOUND_ERROR: + rv = NS_ERROR_FILE_NOT_FOUND; + break; + case PR_IS_DIRECTORY_ERROR: + rv = NS_ERROR_FILE_IS_DIRECTORY; + break; + case PR_LOOP_ERROR: + rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + break; + case PR_NAME_TOO_LONG_ERROR: + rv = NS_ERROR_FILE_NAME_TOO_LONG; + break; + case PR_NO_DEVICE_SPACE_ERROR: + rv = NS_ERROR_FILE_NO_DEVICE_SPACE; + break; + case PR_NOT_DIRECTORY_ERROR: + rv = NS_ERROR_FILE_NOT_DIRECTORY; + break; + case PR_READ_ONLY_FILESYSTEM_ERROR: + rv = NS_ERROR_FILE_READ_ONLY; + break; default: if (IsNSSErrorCode(errorCode)) rv = GetXPCOMFromNSSError(errorCode); break; + + // NSPR's socket code can return these, but they're not worth breaking out + // into their own error codes, distinct from NS_ERROR_FAILURE: + // + // PR_BAD_DESCRIPTOR_ERROR + // PR_INVALID_ARGUMENT_ERROR + // PR_NOT_SOCKET_ERROR + // PR_NOT_TCP_SOCKET_ERROR + // These would indicate a bug internal to the component. + // + // PR_PROTOCOL_NOT_SUPPORTED_ERROR + // This means that we can't use the given "protocol" (like + // IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As + // above, this indicates an internal bug. + // + // PR_IS_CONNECTED_ERROR + // This indicates that we've applied a system call like 'bind' or + // 'connect' to a socket that is already connected. The socket + // components manage each file descriptor's state, and in some cases + // handle this error result internally. We shouldn't be returning + // this to our callers. + // + // PR_IO_ERROR + // This is so vague that NS_ERROR_FAILURE is just as good. } SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%x]\n", errorCode, rv)); return rv; @@ -798,20 +863,46 @@ nsSocketTransport::Init(const char **types, uint32_t typeCount, return NS_OK; } +nsresult +nsSocketTransport::InitWithFilename(const char *filename) +{ +#if defined(XP_UNIX) || defined(XP_OS2) + size_t filenameLength = strlen(filename); + + if (filenameLength > sizeof(mNetAddr.local.path) - 1) + return NS_ERROR_FILE_NAME_TOO_LONG; + + mHost.Assign(filename); + mPort = 0; + mTypeCount = 0; + + mNetAddr.local.family = AF_LOCAL; + memcpy(mNetAddr.local.path, filename, filenameLength); + mNetAddr.local.path[filenameLength] = '\0'; + mNetAddrIsSet = true; + + return NS_OK; +#else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +#endif +} + nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr) { NS_ASSERTION(!mFD, "already initialized"); - char buf[kIPv6CStrBufSize]; + char buf[kNetAddrMaxCStrBufSize]; NetAddrToString(addr, buf, sizeof(buf)); mHost.Assign(buf); uint16_t port; if (addr->raw.family == AF_INET) port = addr->inet.port; - else + else if (addr->raw.family == AF_INET6) port = addr->inet6.port; + else + port = 0; mPort = ntohs(port); memcpy(&mNetAddr, addr, sizeof(NetAddr)); @@ -886,6 +977,10 @@ nsSocketTransport::ResolveHost() if (!mProxyHost.IsEmpty()) { if (!mProxyTransparent || mProxyTransparentResolvesHost) { +#if defined(XP_UNIX) || defined(XP_OS2) + NS_ABORT_IF_FALSE(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL, + "Unix domain sockets can't be used with proxies"); +#endif // When not resolving mHost locally, we still want to ensure that // it only contains valid characters. See bug 304904 for details. if (!net_IsValidHostName(mHost)) @@ -949,6 +1044,11 @@ nsSocketTransport::BuildSocket(PRFileDesc *&fd, bool &proxyTransparent, bool &us rv = fd ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } else { +#if defined(XP_UNIX) || defined(XP_OS2) + NS_ABORT_IF_FALSE(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL, + "Unix domain sockets can't be used with socket types"); +#endif + fd = nullptr; nsCOMPtr spserv = @@ -1052,9 +1152,13 @@ nsSocketTransport::InitiateSocket() nsresult rv; - if (gIOService->IsOffline() && - !IsLoopBackAddress(&mNetAddr)) - return NS_ERROR_OFFLINE; + if (gIOService->IsOffline()) { + bool isLocal; + + IsLocal(&isLocal); + if (!isLocal) + return NS_ERROR_OFFLINE; + } // // find out if it is going to be ok to attach another socket to the STS. @@ -1159,14 +1263,14 @@ nsSocketTransport::InitiateSocket() #if defined(PR_LOGGING) if (SOCKET_LOG_ENABLED()) { - char buf[kIPv6CStrBufSize]; + char buf[kNetAddrMaxCStrBufSize]; NetAddrToString(&mNetAddr, buf, sizeof(buf)); SOCKET_LOG((" trying address: %s\n", buf)); } #endif - // - // Initiate the connect() to the host... + // + // Initiate the connect() to the host... // PRNetAddr prAddr; NetAddrToPRNetAddr(&mNetAddr, &prAddr); @@ -1247,6 +1351,13 @@ nsSocketTransport::RecoverFromError() SOCKET_LOG(("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%x]\n", this, mState, mCondition)); +#if defined(XP_UNIX) || defined(XP_OS2) + // Unix domain connections don't have multiple addresses to try, + // so the recovery techniques here don't apply. + if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) + return false; +#endif + // can only recover from errors in these states if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) return false; @@ -1451,6 +1562,7 @@ void nsSocketTransport::ReleaseFD_Locked(PRFileDesc *fd) { NS_ASSERTION(mFD == fd, "wrong fd"); + SOCKET_LOG(("JIMB: ReleaseFD_Locked: mFDref = %d\n", mFDref)); if (--mFDref == 0) { if (PR_GetCurrentThread() == gSocketThread) { @@ -1491,9 +1603,18 @@ nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status, nsISupports *pa // ensure that we have created a socket, attached it, and have a // connection. // - if (mState == STATE_CLOSED) - mCondition = ResolveHost(); - else + if (mState == STATE_CLOSED) { + // Unix domain sockets are ready to connect; mNetAddr is all we + // need. Internet address families require a DNS lookup (or possibly + // several) before we can connect. +#if defined(XP_UNIX) || defined(XP_OS2) + if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) + mCondition = InitiateSocket(); + else +#endif + mCondition = ResolveHost(); + + } else SOCKET_LOG((" ignoring redundant event\n")); break; @@ -1724,6 +1845,16 @@ nsSocketTransport::IsLocal(bool *aIsLocal) { { MutexAutoLock lock(mLock); + +#if defined(XP_UNIX) || defined(XP_OS2) + // Unix-domain sockets are always local. + if (mNetAddr.raw.family == PR_AF_LOCAL) + { + *aIsLocal = true; + return; + } +#endif + *aIsLocal = IsLoopBackAddress(&mNetAddr); } } @@ -1985,6 +2116,16 @@ nsSocketTransport::GetSelfAddr(NetAddr *addr) } PRNetAddr prAddr; + + // NSPR doesn't tell us the socket address's length (as provided by + // the 'getsockname' system call), so we can't distinguish between + // named, unnamed, and abstract Unix domain socket names. (Server + // sockets are never unnamed, obviously, but client sockets can use + // any kind of address.) Clear prAddr first, so that the path for + // unnamed and abstract addresses will at least be reliably empty, + // and not garbage for unnamed sockets. + memset(&prAddr, 0, sizeof(prAddr)); + nsresult rv = (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; PRNetAddrToNetAddr(&prAddr, addr); diff --git a/netwerk/base/src/nsSocketTransport2.h b/netwerk/base/src/nsSocketTransport2.h index 9943ae2c49c..96d9ec5c35e 100644 --- a/netwerk/base/src/nsSocketTransport2.h +++ b/netwerk/base/src/nsSocketTransport2.h @@ -129,8 +129,13 @@ public: nsresult InitWithConnectedSocket(PRFileDesc *socketFD, const mozilla::net::NetAddr *addr); + // This method instructs the socket transport to open a socket + // connected to the given Unix domain address. We can only create + // unlayered, simple, stream sockets. + nsresult InitWithFilename(const char *filename); + // nsASocketHandler methods: - void OnSocketReady(PRFileDesc *, int16_t outFlags); + void OnSocketReady(PRFileDesc *, int16_t outFlags); void OnSocketDetached(PRFileDesc *); void IsLocal(bool *aIsLocal); diff --git a/netwerk/base/src/nsSocketTransportService2.cpp b/netwerk/base/src/nsSocketTransportService2.cpp index d2eff6f4d6a..4caa6aa1c24 100644 --- a/netwerk/base/src/nsSocketTransportService2.cpp +++ b/netwerk/base/src/nsSocketTransportService2.cpp @@ -573,6 +573,31 @@ nsSocketTransportService::CreateTransport(const char **types, return NS_OK; } +NS_IMETHODIMP +nsSocketTransportService::CreateUnixDomainTransport(nsIFile *aPath, + nsISocketTransport **result) +{ + nsresult rv; + + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + nsAutoCString path; + rv = aPath->GetNativePath(path); + if (NS_FAILED(rv)) + return rv; + + nsRefPtr trans = new nsSocketTransport(); + if (!trans) + return NS_ERROR_OUT_OF_MEMORY; + + rv = trans->InitWithFilename(path.get()); + if (NS_FAILED(rv)) + return rv; + + trans.forget(result); + return NS_OK; +} + NS_IMETHODIMP nsSocketTransportService::GetAutodialEnabled(bool *value) { diff --git a/netwerk/dns/DNS.cpp b/netwerk/dns/DNS.cpp index 66a1f109e0b..cf1a284f506 100644 --- a/netwerk/dns/DNS.cpp +++ b/netwerk/dns/DNS.cpp @@ -115,9 +115,20 @@ bool NetAddrToString(const NetAddr *addr, char *buf, uint32_t bufSize) #if defined(XP_UNIX) || defined(XP_OS2) else if (addr->raw.family == AF_LOCAL) { if (bufSize < sizeof(addr->local.path)) { + // Many callers don't bother checking our return value, so + // null-terminate just in case. + if (bufSize > 0) { + buf[0] = '\0'; + } return false; } - memcpy(buf, addr->local.path, bufSize); + + // Usually, the size passed to memcpy should be the size of the + // destination. Here, we know that the source is no larger than the + // destination, so using the source's size is always safe, whereas + // using the destination's size may cause us to read off the end of the + // source. + memcpy(buf, addr->local.path, sizeof(addr->local.path)); return true; } #endif diff --git a/netwerk/dns/DNS.h b/netwerk/dns/DNS.h index 26bcf05c607..4fe64bafb84 100644 --- a/netwerk/dns/DNS.h +++ b/netwerk/dns/DNS.h @@ -55,9 +55,12 @@ namespace net { // Windows requires longer buffers for some reason. const int kIPv4CStrBufSize = 22; const int kIPv6CStrBufSize = 65; +const int kNetAddrMaxCStrBufSize = kIPv6CStrBufSize; #else const int kIPv4CStrBufSize = 16; const int kIPv6CStrBufSize = 46; +const int kLocalCStrBufSize = 108; +const int kNetAddrMaxCStrBufSize = kLocalCStrBufSize; #endif // This was all created at a time in which we were using NSPR for host diff --git a/netwerk/test/unit/test_addr_in_use_error.js b/netwerk/test/unit/test_addr_in_use_error.js new file mode 100644 index 00000000000..d7f5167193c --- /dev/null +++ b/netwerk/test/unit/test_addr_in_use_error.js @@ -0,0 +1,32 @@ +// Opening a second listening socket on the same address as an extant +// socket should elicit NS_ERROR_SOCKET_ADDRESS_IN_USE on non-Windows +// machines. + +const { classes: Cc, interfaces: Ci, results: Cr, Constructor: CC } = Components; + +const ServerSocket = CC("@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "init"); + +function testAddrInUse() +{ + // Windows lets us have as many sockets listening on the same address as + // we like, evidently. + if ("@mozilla.org/windows-registry-key;1" in Cc) { + return; + } + + // Create listening socket: + // any port (-1), loopback only (true), default backlog (-1) + let listener = ServerSocket(-1, true, -1); + do_check_true(listener instanceof Ci.nsIServerSocket); + + // Try to create another listening socket on the same port, whatever that was. + do_check_throws_nsIException(() => ServerSocket(listener.port, true, -1), + "NS_ERROR_SOCKET_ADDRESS_IN_USE"); +} + +function run_test() +{ + testAddrInUse(); +} diff --git a/netwerk/test/unit/test_unix_domain.js b/netwerk/test/unit/test_unix_domain.js new file mode 100644 index 00000000000..230170debfd --- /dev/null +++ b/netwerk/test/unit/test_unix_domain.js @@ -0,0 +1,533 @@ +// Exercise Unix domain sockets. + +const Ci = Components.interfaces; +const Cc = Components.classes; +const CC = Components.Constructor; +const Cr = Components.results; + +const UnixServerSocket = CC("@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "initWithFilename"); + +const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", + "nsIScriptableInputStream", + "init"); + +const IOService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); +const socketTransportService = Cc["@mozilla.org/network/socket-transport-service;1"] + .getService(Ci.nsISocketTransportService); + +const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(); + +const allPermissions = parseInt("777", 8); + +function run_test() +{ + // If we're on Windows, simply check for graceful failure. + if ("@mozilla.org/windows-registry-key;1" in Cc) { + test_not_supported(); + return; + } + + add_test(test_echo); + add_test(test_name_too_long); + add_test(test_no_directory); + add_test(test_no_such_socket); + add_test(test_address_in_use); + add_test(test_file_in_way); + add_test(test_create_permission); + add_test(test_connect_permission); + add_test(test_long_socket_name); + add_test(test_keep_when_offline); + + run_next_test(); +} + +// Check that creating a Unix domain socket fails gracefully on Windows. +function test_not_supported() +{ + let socketName = do_get_tempdir(); + socketName.append('socket'); + do_print("creating socket: " + socketName.path); + + do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"); + + do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName), + "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"); +} + +// Actually exchange data with Unix domain sockets. +function test_echo() +{ + let log = ''; + + let socketName = do_get_tempdir(); + socketName.append('socket'); + + // Create a server socket, listening for connections. + do_print("creating socket: " + socketName.path); + let server = new UnixServerSocket(socketName, allPermissions, -1); + server.asyncListen({ + onSocketAccepted: function(aServ, aTransport) { + do_print("called test_echo's onSocketAccepted"); + log += 'a'; + + do_check_eq(aServ, server); + + let connection = aTransport; + + // Check the server socket's self address. + let connectionSelfAddr = connection.getScriptableSelfAddr(); + do_check_eq(connectionSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); + do_check_eq(connectionSelfAddr.address, socketName.path); + + // The client socket is anonymous, so the server transport should + // have an empty peer address. + do_check_eq(connection.host, ''); + do_check_eq(connection.port, 0); + let connectionPeerAddr = connection.getScriptablePeerAddr(); + do_check_eq(connectionPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); + do_check_eq(connectionPeerAddr.address, ''); + + let serverInput = new ScriptableInputStream(connection.openInputStream(0, 0, 0)); + let serverOutput = connection.openOutputStream(0, 0, 0); + + // Receive data from the client, and send back a response. + do_check_eq(serverInput.readBytes(17), "Mervyn Murgatroyd"); + serverOutput.write("Ruthven Murgatroyd", 18); + }, + + onStopListening: function(aServ, aStatus) { + do_print("called test_echo's onStopListening"); + log += 's'; + + do_check_eq(aServ, server); + do_check_eq(log, 'acs'); + + run_next_test(); + } + }); + + // Create a client socket, and connect to the server. + let client = socketTransportService.createUnixDomainTransport(socketName); + do_check_eq(client.host, socketName.path); + do_check_eq(client.port, 0); + + let clientAsyncInput = client.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream); + let clientInput = new ScriptableInputStream(clientAsyncInput); + let clientOutput = client.openOutputStream(0, 0, 0); + + clientOutput.write("Mervyn Murgatroyd", 17); + clientAsyncInput.asyncWait(function (aStream) { + do_print("called test_echo's onInputStreamReady"); + log += 'c'; + + do_check_eq(aStream, clientAsyncInput); + + // Now that the connection has been established, we can check the + // transport's self and peer addresses. + let clientSelfAddr = client.getScriptableSelfAddr(); + do_check_eq(clientSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); + do_check_eq(clientSelfAddr.address, ''); + + do_check_eq(client.host, socketName.path); // re-check, but hey + let clientPeerAddr = client.getScriptablePeerAddr(); + do_check_eq(clientPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); + do_check_eq(clientPeerAddr.address, socketName.path); + + do_check_eq(clientInput.readBytes(18), "Ruthven Murgatroyd"); + + server.close(); + }, 0, 0, threadManager.currentThread); +} + +// Create client and server sockets using a path that's too long. +function test_name_too_long() +{ + let socketName = do_get_tempdir(); + // The length limits on all the systems NSPR supports are a bit past 100. + socketName.append(new Array(1000).join('x')); + + // The length must be checked before we ever make any system calls --- we + // have to create the sockaddr first --- so it's unambiguous which error + // we should get here. + + do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1), + "NS_ERROR_FILE_NAME_TOO_LONG"); + + // Unlike most other client socket errors, this one gets reported + // immediately, as we can't even initialize the sockaddr with the given + // name. + do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName), + "NS_ERROR_FILE_NAME_TOO_LONG"); + + run_next_test(); +} + +// Try creating a socket in a directory that doesn't exist. +function test_no_directory() +{ + let socketName = do_get_tempdir(); + socketName.append('directory-that-does-not-exist'); + socketName.append('socket'); + + do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1), + "NS_ERROR_FILE_NOT_FOUND"); + + run_next_test(); +} + +// Try connecting to a server socket that isn't there. +function test_no_such_socket() +{ + let socketName = do_get_tempdir(); + socketName.append('nonexistent-socket'); + + let client = socketTransportService.createUnixDomainTransport(socketName); + let clientAsyncInput = client.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream); + clientAsyncInput.asyncWait(function (aStream) { + do_print("called test_no_such_socket's onInputStreamReady"); + + do_check_eq(aStream, clientAsyncInput); + + // nsISocketTransport puts off actually creating sockets as long as + // possible, so the error in connecting doesn't actually show up until + // this point. + do_check_throws_nsIException(() => clientAsyncInput.available(), + "NS_ERROR_FILE_NOT_FOUND"); + + clientAsyncInput.close(); + client.close(Cr.NS_OK); + + run_next_test(); + }, 0, 0, threadManager.currentThread); +} + +// Creating a socket with a name that another socket is already using is an +// error. +function test_address_in_use() +{ + let socketName = do_get_tempdir(); + socketName.append('socket-in-use'); + + // Create one server socket. + let server = new UnixServerSocket(socketName, allPermissions, -1); + + // Now try to create another with the same name. + do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_SOCKET_ADDRESS_IN_USE"); + + run_next_test(); +} + +// Creating a socket with a name that is already a file is an error. +function test_file_in_way() +{ + let socketName = do_get_tempdir(); + socketName.append('file_in_way'); + + // Create a file with the given name. + socketName.create(Ci.nsIFile.NORMAL_FILE_TYPE, allPermissions); + + // Try to create a socket with the same name. + do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_SOCKET_ADDRESS_IN_USE"); + + // Try to create a socket under a name that uses that as a parent directory. + socketName.append('socket'); + do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1), + "NS_ERROR_FILE_NOT_DIRECTORY"); + + run_next_test(); +} + +// It is not permitted to create a socket in a directory which we are not +// permitted to execute, or create files in. +function test_create_permission() +{ + let dirName = do_get_tempdir(); + dirName.append('unfriendly'); + + let socketName = dirName.clone(); + socketName.append('socket'); + + // The test harness has difficulty cleaning things up if we don't make + // everything writable before we're done. + try { + // Create a directory which we are not permitted to search. + dirName.create(Ci.nsIFile.DIRECTORY_TYPE, 0); + + // Try to create a socket in that directory. Because Linux returns EACCES + // when a 'connect' fails because of a local firewall rule, + // nsIServerSocket returns NS_ERROR_CONNECTION_REFUSED in this case. + do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_CONNECTION_REFUSED"); + + // Grant read and execute permission, but not write permission on the directory. + dirName.permissions = parseInt("0555", 8); + + // This should also fail; we need write permission. + do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_CONNECTION_REFUSED"); + + } finally { + // Make the directory writable, so the test harness can clean it up. + dirName.permissions = allPermissions; + } + + // This should succeed, since we now have all the permissions on the + // directory we could want. + do_check_instanceof(new UnixServerSocket(socketName, allPermissions, -1), + Ci.nsIServerSocket); + + run_next_test(); +} + +// To connect to a Unix domain socket, we need search permission on the +// directories containing it, and some kind of permission or other on the +// socket itself. +function test_connect_permission() +{ + // This test involves a lot of callbacks, but they're written out so that + // the actual control flow proceeds from top to bottom. + let log = ''; + + // Create a directory which we are permitted to search - at first. + let dirName = do_get_tempdir(); + dirName.append('inhospitable'); + dirName.create(Ci.nsIFile.DIRECTORY_TYPE, allPermissions); + + let socketName = dirName.clone(); + socketName.append('socket'); + + // Create a server socket in that directory, listening for connections, + // and accessible. + let server = new UnixServerSocket(socketName, allPermissions, -1); + server.asyncListen({ onSocketAccepted: socketAccepted, onStopListening: stopListening }); + + // Make the directory unsearchable. + dirName.permissions = 0; + + let client3; + + let client1 = socketTransportService.createUnixDomainTransport(socketName); + let client1AsyncInput = client1.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream); + client1AsyncInput.asyncWait(function (aStream) { + do_print("called test_connect_permission's client1's onInputStreamReady"); + log += '1'; + + // nsISocketTransport puts off actually creating sockets as long as + // possible, so the error doesn't actually show up until this point. + do_check_throws_nsIException(() => client1AsyncInput.available(), + "NS_ERROR_CONNECTION_REFUSED"); + + client1AsyncInput.close(); + client1.close(Cr.NS_OK); + + // Make the directory searchable, but make the socket inaccessible. + dirName.permissions = allPermissions; + socketName.permissions = 0; + + let client2 = socketTransportService.createUnixDomainTransport(socketName); + let client2AsyncInput = client2.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream); + client2AsyncInput.asyncWait(function (aStream) { + do_print("called test_connect_permission's client2's onInputStreamReady"); + log += '2'; + + do_check_throws_nsIException(() => client2AsyncInput.available(), + "NS_ERROR_CONNECTION_REFUSED"); + + client2AsyncInput.close(); + client2.close(Cr.NS_OK); + + // Now make everything accessible, and try one last time. + socketName.permissions = allPermissions; + + client3 = socketTransportService.createUnixDomainTransport(socketName); + + let client3Output = client3.openOutputStream(0, 0, 0); + client3Output.write("Hanratty", 8); + + let client3AsyncInput = client3.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream); + client3AsyncInput.asyncWait(client3InputStreamReady, 0, 0, threadManager.currentThread); + }, 0, 0, threadManager.currentThread); + }, 0, 0, threadManager.currentThread); + + function socketAccepted(aServ, aTransport) { + do_print("called test_connect_permission's onSocketAccepted"); + log += 'a'; + + let serverInput = new ScriptableInputStream(aTransport.openInputStream(0, 0, 0)); + let serverOutput = aTransport.openOutputStream(0, 0, 0); + + // Receive data from the client, and send back a response. + do_check_eq(serverInput.readBytes(8), "Hanratty"); + serverOutput.write("Ferlingatti", 11); + } + + function client3InputStreamReady(aStream) { + do_print("called client3's onInputStreamReady"); + log += '3'; + + let client3Input = new ScriptableInputStream(aStream); + + do_check_eq(client3Input.readBytes(11), "Ferlingatti"); + + aStream.close(); + client3.close(Cr.NS_OK); + server.close(); + } + + function stopListening(aServ, aStatus) { + do_print("called test_connect_permission's server's stopListening"); + log += 's'; + + do_check_eq(log, '12a3s'); + + run_next_test(); + } +} + +// Creating a socket with a long filename doesn't crash. +function test_long_socket_name() +{ + let socketName = do_get_tempdir(); + socketName.append(new Array(10000).join('long')); + + // Try to create a server socket with the long name. + do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_FILE_NAME_TOO_LONG"); + + // Try to connect to a socket with the long name. + do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName), + "NS_ERROR_FILE_NAME_TOO_LONG"); + + run_next_test(); +} + +// Going offline should not shut down Unix domain sockets. +function test_keep_when_offline() +{ + let log = ''; + + let socketName = do_get_tempdir(); + socketName.append('keep-when-offline'); + + // Create a listening socket. + let listener = new UnixServerSocket(socketName, allPermissions, -1); + listener.asyncListen({ onSocketAccepted: onAccepted, onStopListening: onStopListening }); + + // Connect a client socket to the listening socket. + let client = socketTransportService.createUnixDomainTransport(socketName); + let clientOutput = client.openOutputStream(0, 0, 0); + let clientInput = client.openInputStream(0, 0, 0); + clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread); + let clientScriptableInput = new ScriptableInputStream(clientInput); + + let server, serverInput, serverScriptableInput, serverOutput; + + // How many times has the server invited the client to go first? + let count = 0; + + // The server accepted connection callback. + function onAccepted(aListener, aServer) { + do_print("test_keep_when_offline: onAccepted called"); + log += 'a'; + do_check_eq(aListener, listener); + server = aServer; + + // Prepare to receive messages from the client. + serverInput = server.openInputStream(0, 0, 0); + serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread); + serverScriptableInput = new ScriptableInputStream(serverInput); + + // Start a conversation with the client. + serverOutput = server.openOutputStream(0, 0, 0); + serverOutput.write("After you, Alphonse!", 20); + count++; + } + + // The client has seen its end of the socket close. + function clientReady(aStream) { + log += 'c'; + do_print("test_keep_when_offline: clientReady called: " + log); + do_check_eq(aStream, clientInput); + + // If the connection has been closed, end the conversation and stop listening. + let available; + try { + available = clientInput.available(); + } catch (ex) { + do_check_instanceof(ex, Ci.nsIException); + do_check_eq(ex.result, Cr.NS_BASE_STREAM_CLOSED); + + do_print("client received end-of-stream; closing client output stream"); + log += ')'; + + client.close(Cr.NS_OK); + + // Now both output streams have been closed, and both input streams + // have received the close notification. Stop listening for + // connections. + listener.close(); + } + + if (available) { + // Check the message from the server. + do_check_eq(clientScriptableInput.readBytes(20), "After you, Alphonse!"); + + // Write our response to the server. + clientOutput.write("No, after you, Gaston!", 22); + + // Ask to be called again, when more input arrives. + clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread); + } + } + + function serverReady(aStream) { + log += 's'; + do_print("test_keep_when_offline: serverReady called: " + log); + do_check_eq(aStream, serverInput); + + // Check the message from the client. + do_check_eq(serverScriptableInput.readBytes(22), "No, after you, Gaston!"); + + // This should not shut things down: Unix domain sockets should + // remain open in offline mode. + if (count == 5) { + IOService.offline = true; + log += 'o'; + } + + if (count < 10) { + // Insist. + serverOutput.write("After you, Alphonse!", 20); + count++; + + // As long as the input stream is open, always ask to be called again + // when more input arrives. + serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread); + } else if (count == 10) { + // After sending ten times and receiving ten replies, we're not + // going to send any more. Close the server's output stream; the + // client's input stream should see this. + do_print("closing server transport"); + server.close(Cr.NS_OK); + log += '('; + } + } + + // We have stopped listening. + function onStopListening(aServ, aStatus) { + do_print("test_keep_when_offline: onStopListening called"); + log += 'L'; + do_check_eq(log, 'acscscscscsocscscscscs(c)L'); + + do_check_eq(aServ, listener); + do_check_eq(aStatus, Cr.NS_BINDING_ABORTED); + + run_next_test(); + } +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 5a172d5c29a..433942d8034 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -2,6 +2,12 @@ head = head_channels.js head_cache.js tail = +[test_unix_domain.js] +# The xpcshell temp directory on Android doesn't seem to let us create +# Unix domain sockets. (Perhaps it's a FAT filesystem?) +skip-if = os == "android" + +[test_addr_in_use_error.js] [test_304_responses.js] # Bug 675039: test hangs on Android-armv6 skip-if = os == "android" diff --git a/testing/xpcshell/example/unit/test_check_nsIException.js b/testing/xpcshell/example/unit/test_check_nsIException.js new file mode 100644 index 00000000000..115542fca25 --- /dev/null +++ b/testing/xpcshell/example/unit/test_check_nsIException.js @@ -0,0 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let env = Components.classes["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + do_check_throws_nsIException(function () { + env.QueryInterface(Components.interfaces.nsIFile); + }, "NS_NOINTERFACE"); +} + diff --git a/testing/xpcshell/example/unit/test_check_nsIException_failing.js b/testing/xpcshell/example/unit/test_check_nsIException_failing.js new file mode 100644 index 00000000000..4e562b6d8cd --- /dev/null +++ b/testing/xpcshell/example/unit/test_check_nsIException_failing.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_check_throws_nsIException(function () { + throw Error("I find your relaxed dishabille unpalatable"); + }, "NS_NOINTERFACE"); +} + diff --git a/testing/xpcshell/example/unit/xpcshell.ini b/testing/xpcshell/example/unit/xpcshell.ini index edd2c044679..fee391f0438 100644 --- a/testing/xpcshell/example/unit/xpcshell.ini +++ b/testing/xpcshell/example/unit/xpcshell.ini @@ -6,6 +6,10 @@ head = tail = +[test_check_nsIException.js] +[test_check_nsIException_failing.js] +fail-if = true + [test_do_get_tempdir.js] [test_execute_soon.js] [test_get_file.js] diff --git a/testing/xpcshell/head.js b/testing/xpcshell/head.js index 356f33fba35..083f01a17c4 100644 --- a/testing/xpcshell/head.js +++ b/testing/xpcshell/head.js @@ -868,6 +868,75 @@ function format_pattern_match_failure(diagnosis, indent="") { return indent + a; } +// Check that |func| throws an nsIException that has +// |Components.results[resultName]| as the value of its 'result' property. +function do_check_throws_nsIException(func, resultName, + stack=Components.stack.caller, todo=false) +{ + let expected = Components.results[resultName]; + if (typeof expected !== 'number') { + do_throw("do_check_throws_nsIException requires a Components.results" + + " property name, not " + uneval(resultName), stack); + } + + let msg = ("do_check_throws_nsIException: func should throw" + + " an nsIException whose 'result' is Components.results." + + resultName); + + try { + func(); + } catch (ex) { + if (!(ex instanceof Components.interfaces.nsIException) || + ex.result !== expected) { + do_report_result(false, msg + ", threw " + legible_exception(ex) + + " instead", stack, todo); + } + + do_report_result(true, msg, stack, todo); + return; + } + + // Call this here, not in the 'try' clause, so do_report_result's own + // throw doesn't get caught by our 'catch' clause. + do_report_result(false, msg + ", but returned normally", stack, todo); +} + +// Produce a human-readable form of |exception|. This looks up +// Components.results values, tries toString methods, and so on. +function legible_exception(exception) +{ + switch (typeof exception) { + case 'object': + if (exception instanceof Components.interfaces.nsIException) { + return "nsIException instance: " + uneval(exception.toString()); + } + return exception.toString(); + + case 'number': + for (let name in Components.results) { + if (exception === Components.results[name]) { + return "Components.results." + name; + } + } + + // Fall through. + default: + return uneval(exception); + } +} + +function do_check_instanceof(value, constructor, + stack=Components.stack.caller, todo=false) { + do_report_result(value instanceof constructor, + "value should be an instance of " + constructor.name, + stack, todo); +} + +function todo_check_instanceof(value, constructor, + stack=Components.stack.caller) { + do_check_instanceof(value, constructor, stack, true); +} + function do_test_pending(aName) { ++_tests_pending; diff --git a/toolkit/content/tests/chrome/window_titlebar.xul b/toolkit/content/tests/chrome/window_titlebar.xul index 922357d2595..9f6ce81bc8f 100644 --- a/toolkit/content/tests/chrome/window_titlebar.xul +++ b/toolkit/content/tests/chrome/window_titlebar.xul @@ -202,7 +202,7 @@ function popupshown_step2(oldrect, panel, anchored) function popupshown_step3(oldrect, panel, anchored) { // skip this check on Linux for the same window positioning reasons as above - if (navigator.platform.indexOf("Linux") == -1 || panel.id != "panelanchored") { + if (navigator.platform.indexOf("Linux") == -1 || (panel.id != "panelanchored" && panel.id != "paneltop")) { // next, drag the titlebar in the panel var newrect = panel.getBoundingClientRect(); SimpleTest.is(newrect.left, oldrect.left + 20, panel.id + " move popup horizontal"); diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 31f554d67ad..f6a5bb4a0d0 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -251,7 +251,10 @@ ERROR(NS_ERROR_UNKNOWN_SOCKET_TYPE, FAILURE(51)), /* The specified socket type could not be created. */ ERROR(NS_ERROR_SOCKET_CREATE_FAILED, FAILURE(52)), - + /* The operating system doesn't support the given type of address. */ + ERROR(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED, FAILURE(53)), + /* The address to which we tried to bind the socket was busy. */ + ERROR(NS_ERROR_SOCKET_ADDRESS_IN_USE, FAILURE(54)), /* Cache specific error codes: */ ERROR(NS_ERROR_CACHE_KEY_NOT_FOUND, FAILURE(61)), diff --git a/xpcom/io/nsIFile.idl b/xpcom/io/nsIFile.idl index 0f353ecb4d2..effd27654f7 100644 --- a/xpcom/io/nsIFile.idl +++ b/xpcom/io/nsIFile.idl @@ -18,9 +18,21 @@ interface nsISimpleEnumerator; /** - * This is the only correct cross-platform way to specify a file. - * Strings are not such a way. If you grew up on windows or unix, you - * may think they are. Welcome to reality. + * An nsIFile is an abstract representation of a filename. It manages + * filename encoding issues, pathname component separators ('/' vs. '\\' + * vs. ':') and weird stuff like differing volumes with identical names, as + * on pre-Darwin Macintoshes. + * + * This file has long introduced itself to new hackers with this opening + * paragraph: + * + * This is the only correct cross-platform way to specify a file. + * Strings are not such a way. If you grew up on windows or unix, you + * may think they are. Welcome to reality. + * + * While taking the pose struck here to heart would be uncalled for, one + * may safely conclude that writing cross-platform code is an embittering + * experience. * * All methods with string parameters have two forms. The preferred * form operates on UCS-2 encoded characters strings. An alternate @@ -28,7 +40,7 @@ interface nsISimpleEnumerator; * * A string containing characters encoded in the native charset cannot * be safely passed to javascript via xpconnect. Therefore, the "native - * methods" are not scriptable. + * methods" are not scriptable. */ [scriptable, uuid(272a5020-64f5-485c-a8c4-44b2882ae0a2), builtinclass] interface nsIFile : nsISupports