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