diff --git a/browser/base/content/test/general/title_test.svg b/browser/base/content/test/general/title_test.svg new file mode 100644 index 0000000000..7638fd5ccb --- /dev/null +++ b/browser/base/content/test/general/title_test.svg @@ -0,0 +1,59 @@ + + This is a root SVG element's title + + + + + This is a non-root SVG element title + + + + + + This contains only <title> + + + + This is a title + + + + + This contains only <desc> + This is a desc + + + This contains nothing. + + + This link contains <title> + + This is a title + + + + + + + This text contains <title> + + This is a title + + + + + + This link contains <title> & xlink:title attr. + This is a title + + + + + This link contains xlink:title attr. + + + + This contains nothing. + + diff --git a/build/mach_bootstrap.py b/build/mach_bootstrap.py index 211d98abe2..820fe27f23 100644 --- a/build/mach_bootstrap.py +++ b/build/mach_bootstrap.py @@ -50,9 +50,9 @@ SEARCH_PATHS = [ 'testing/firefox-ui/harness', 'testing/firefox-ui/tests', 'testing/luciddream', + 'testing/marionette/harness', + 'testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py', 'testing/marionette/client', - 'testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py', - 'testing/marionette/driver', 'testing/mozbase/mozcrash', 'testing/mozbase/mozdebug', 'testing/mozbase/mozdevice', diff --git a/build/pgo/server-locations.txt b/build/pgo/server-locations.txt index bb359d9f70..a034c02ad6 100644 --- a/build/pgo/server-locations.txt +++ b/build/pgo/server-locations.txt @@ -258,3 +258,6 @@ https://sha256ee.example.com:443 privileged,cer https://ssl3.example.com:443 privileged,ssl3 https://rc4.example.com:443 privileged,rc4 https://ssl3rc4.example.com:443 privileged,ssl3,rc4 + +# Hosts for youtube rewrite tests +https://mochitest.youtube.com:443 diff --git a/build/virtualenv_packages.txt b/build/virtualenv_packages.txt index 122804e652..4179884c74 100644 --- a/build/virtualenv_packages.txt +++ b/build/virtualenv_packages.txt @@ -1,7 +1,7 @@ -marionette_driver.pth:testing/marionette/driver -browsermobproxy.pth:testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py +marionette_driver.pth:testing/marionette/client +browsermobproxy.pth:testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py wptserve.pth:testing/web-platform/tests/tools/wptserve -marionette.pth:testing/marionette/client +marionette.pth:testing/marionette/harness blessings.pth:python/blessings configobj.pth:python/configobj jsmin.pth:python/jsmin diff --git a/configure.in b/configure.in index e5138eb512..4ae68a00bb 100644 --- a/configure.in +++ b/configure.in @@ -8839,6 +8839,16 @@ fi MOZ_CONFIG_ICU() +dnl ======================================================== +dnl mask as short hand property +dnl ======================================================== +dnl Uncommenting the next line would enable mask-as-shorthand feature. +dnl MOZ_ENABLE_MASK_AS_SHORTHAND=1 +if test "$MOZ_ENABLE_MASK_AS_SHORTHAND"; then + AC_DEFINE(MOZ_ENABLE_MASK_AS_SHORTHAND) +fi +AC_SUBST(MOZ_ENABLE_MASK_AS_SHORTHAND) + if test -z "$JS_SHARED_LIBRARY"; then AC_DEFINE(MOZ_STATIC_JS) fi diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index dcf2ed229d..aae0044498 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5783,10 +5783,14 @@ nsDocShell::SetPosition(int32_t aX, int32_t aY) NS_IMETHODIMP nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY) { - // Added to nsIBaseWindow in bug 1247335; - // implement if a use-case is found. - NS_ASSERTION(false, "implement me!"); - return NS_ERROR_NOT_IMPLEMENTED; + nsCOMPtr ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->SetPositionDesktopPix(aX, aY); + } + + double scale = 1.0; + GetDevicePixelsPerDesktopPixel(&scale); + return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); } NS_IMETHODIMP diff --git a/dom/animation/AnimationEffectTiming.cpp b/dom/animation/AnimationEffectTiming.cpp index 6fe676af43..07bb66a101 100644 --- a/dom/animation/AnimationEffectTiming.cpp +++ b/dom/animation/AnimationEffectTiming.cpp @@ -8,6 +8,7 @@ #include "mozilla/dom/AnimatableBinding.h" #include "mozilla/dom/AnimationEffectTimingBinding.h" +#include "mozilla/TimingParams.h" namespace mozilla { namespace dom { @@ -39,26 +40,20 @@ AnimationEffectTiming::SetEndDelay(double aEndDelay) } void -AnimationEffectTiming::SetDuration(const UnrestrictedDoubleOrString& aDuration) +AnimationEffectTiming::SetDuration(const UnrestrictedDoubleOrString& aDuration, + ErrorResult& aRv) { - if (mTiming.mDuration.IsUnrestrictedDouble() && - aDuration.IsUnrestrictedDouble() && - mTiming.mDuration.GetAsUnrestrictedDouble() == - aDuration.GetAsUnrestrictedDouble()) { + Maybe newDuration = + TimingParams::ParseDuration(aDuration, aRv); + if (aRv.Failed()) { return; } - if (mTiming.mDuration.IsString() && aDuration.IsString() && - mTiming.mDuration.GetAsString() == aDuration.GetAsString()) { + if (mTiming.mDuration == newDuration) { return; } - if (aDuration.IsUnrestrictedDouble()) { - mTiming.mDuration.SetAsUnrestrictedDouble() = - aDuration.GetAsUnrestrictedDouble(); - } else { - mTiming.mDuration.SetAsString() = aDuration.GetAsString(); - } + mTiming.mDuration = newDuration; NotifyTimingUpdate(); } diff --git a/dom/animation/AnimationEffectTiming.h b/dom/animation/AnimationEffectTiming.h index 1f3afae6dc..b98b28bbdc 100644 --- a/dom/animation/AnimationEffectTiming.h +++ b/dom/animation/AnimationEffectTiming.h @@ -25,7 +25,8 @@ public: void Unlink() override { mEffect = nullptr; } void SetEndDelay(double aEndDelay); - void SetDuration(const UnrestrictedDoubleOrString& aDuration); + void SetDuration(const UnrestrictedDoubleOrString& aDuration, + ErrorResult& aRv); private: void NotifyTimingUpdate(); diff --git a/dom/animation/AnimationEffectTimingReadOnly.cpp b/dom/animation/AnimationEffectTimingReadOnly.cpp index 1d61c6dbfc..52c7179830 100644 --- a/dom/animation/AnimationEffectTimingReadOnly.cpp +++ b/dom/animation/AnimationEffectTimingReadOnly.cpp @@ -26,6 +26,17 @@ AnimationEffectTimingReadOnly::WrapObject(JSContext* aCx, JS::Handle return AnimationEffectTimingReadOnlyBinding::Wrap(aCx, this, aGivenProto); } +void +AnimationEffectTimingReadOnly::GetDuration( + OwningUnrestrictedDoubleOrString& aRetVal) const +{ + if (mTiming.mDuration) { + aRetVal.SetAsUnrestrictedDouble() = mTiming.mDuration->ToMilliseconds(); + } else { + aRetVal.SetAsString().AssignLiteral("auto"); + } +} + void AnimationEffectTimingReadOnly::GetEasing(nsString& aRetVal) const { diff --git a/dom/animation/AnimationEffectTimingReadOnly.h b/dom/animation/AnimationEffectTimingReadOnly.h index 2715530493..6c570342b1 100644 --- a/dom/animation/AnimationEffectTimingReadOnly.h +++ b/dom/animation/AnimationEffectTimingReadOnly.h @@ -41,10 +41,7 @@ public: FillMode Fill() const { return mTiming.mFill; } double IterationStart() const { return mTiming.mIterationStart; } double Iterations() const { return mTiming.mIterations; } - void GetDuration(OwningUnrestrictedDoubleOrString& aRetVal) const - { - aRetVal = mTiming.mDuration; - } + void GetDuration(OwningUnrestrictedDoubleOrString& aRetVal) const; PlaybackDirection Direction() const { return mTiming.mDirection; } void GetEasing(nsString& aRetVal) const; diff --git a/dom/animation/KeyframeEffect.cpp b/dom/animation/KeyframeEffect.cpp index bf73d13dcb..5c0c9a2f51 100644 --- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -238,12 +238,12 @@ KeyframeEffectReadOnly::GetComputedTimingAt( // Always return the same object to benefit from return-value optimization. ComputedTiming result; - if (aTiming.mDuration.IsUnrestrictedDouble()) { - double durationMs = aTiming.mDuration.GetAsUnrestrictedDouble(); - if (!IsNaN(durationMs) && durationMs >= 0.0f) { - result.mDuration = StickyTimeDuration::FromMilliseconds(durationMs); - } + if (aTiming.mDuration) { + MOZ_ASSERT(aTiming.mDuration.ref() >= zeroDuration, + "Iteration duration should be positive"); + result.mDuration = aTiming.mDuration.ref(); } + result.mIterations = IsNaN(aTiming.mIterations) || aTiming.mIterations < 0.0f ? 1.0f : aTiming.mIterations; diff --git a/dom/animation/KeyframeEffect.h b/dom/animation/KeyframeEffect.h index f66460de3c..7f44bf1c18 100644 --- a/dom/animation/KeyframeEffect.h +++ b/dom/animation/KeyframeEffect.h @@ -204,9 +204,13 @@ public: const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, ErrorResult& aRv) { + TimingParams timingParams = + TimingParams::FromOptionsUnion(aOptions, aTarget, aRv); + if (aRv.Failed()) { + return nullptr; + } return ConstructKeyframeEffect( - aGlobal, aTarget, aFrames, - TimingParams::FromOptionsUnion(aOptions, aTarget), aRv); + aGlobal, aTarget, aFrames, timingParams, aRv); } void GetTarget(Nullable& aRv) const; @@ -431,9 +435,13 @@ public: const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, ErrorResult& aRv) { + TimingParams timingParams = + TimingParams::FromOptionsUnion(aOptions, aTarget, aRv); + if (aRv.Failed()) { + return nullptr; + } return ConstructKeyframeEffect( - aGlobal, aTarget, aFrames, - TimingParams::FromOptionsUnion(aOptions, aTarget), aRv); + aGlobal, aTarget, aFrames, timingParams, aRv); } // More generalized version for Animatable.animate. diff --git a/dom/animation/TimingParams.cpp b/dom/animation/TimingParams.cpp index dbb1df9f9d..702d14a629 100644 --- a/dom/animation/TimingParams.cpp +++ b/dom/animation/TimingParams.cpp @@ -8,24 +8,6 @@ namespace mozilla { -TimingParams::TimingParams(const dom::AnimationEffectTimingProperties& aRhs, - const dom::Element* aTarget) - : mDuration(aRhs.mDuration) - , mDelay(TimeDuration::FromMilliseconds(aRhs.mDelay)) - , mEndDelay(TimeDuration::FromMilliseconds(aRhs.mEndDelay)) - , mIterations(aRhs.mIterations) - , mIterationStart(aRhs.mIterationStart) - , mDirection(aRhs.mDirection) - , mFill(aRhs.mFill) -{ - mFunction = AnimationUtils::ParseEasing(aTarget, aRhs.mEasing); -} - -TimingParams::TimingParams(double aDuration) -{ - mDuration.SetAsUnrestrictedDouble() = aDuration; -} - template static const dom::AnimationEffectTimingProperties& GetTimingProperties(const OptionsType& aOptions); @@ -52,10 +34,18 @@ template static TimingParams TimingParamsFromOptionsUnion( const OptionsType& aOptions, - const Nullable& aTarget) + const Nullable& aTarget, + ErrorResult& aRv) { + TimingParams result; if (aOptions.IsUnrestrictedDouble()) { - return TimingParams(aOptions.GetAsUnrestrictedDouble()); + double durationInMs = aOptions.GetAsUnrestrictedDouble(); + if (durationInMs >= 0) { + result.mDuration.emplace( + StickyTimeDuration::FromMilliseconds(durationInMs)); + } else { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + } } else { // If aTarget is a pseudo element, we pass its parent element because // TimingParams only needs its owner doc to parse easing and both pseudo @@ -72,41 +62,48 @@ TimingParamsFromOptionsUnion( targetElement = target.GetAsCSSPseudoElement().ParentElement(); } } - return TimingParams(GetTimingProperties(aOptions), targetElement); + const dom::AnimationEffectTimingProperties& timing = + GetTimingProperties(aOptions); + Maybe duration = + TimingParams::ParseDuration(timing.mDuration, aRv); + if (aRv.Failed()) { + return result; + } + result.mDuration = duration; + result.mDelay = TimeDuration::FromMilliseconds(timing.mDelay); + result.mEndDelay = TimeDuration::FromMilliseconds(timing.mEndDelay); + result.mIterations = timing.mIterations; + result.mIterationStart = timing.mIterationStart; + result.mDirection = timing.mDirection; + result.mFill = timing.mFill; + result.mFunction = + AnimationUtils::ParseEasing(targetElement, timing.mEasing); } + return result; } /* static */ TimingParams TimingParams::FromOptionsUnion( const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, - const Nullable& aTarget) + const Nullable& aTarget, + ErrorResult& aRv) { - return TimingParamsFromOptionsUnion(aOptions, aTarget); + return TimingParamsFromOptionsUnion(aOptions, aTarget, aRv); } /* static */ TimingParams TimingParams::FromOptionsUnion( const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, - const Nullable& aTarget) + const Nullable& aTarget, + ErrorResult& aRv) { - return TimingParamsFromOptionsUnion(aOptions, aTarget); + return TimingParamsFromOptionsUnion(aOptions, aTarget, aRv); } bool TimingParams::operator==(const TimingParams& aOther) const { - bool durationEqual; - if (mDuration.IsUnrestrictedDouble()) { - durationEqual = aOther.mDuration.IsUnrestrictedDouble() && - (mDuration.GetAsUnrestrictedDouble() == - aOther.mDuration.GetAsUnrestrictedDouble()); - } else { - // We consider all string values and uninitialized values as meaning "auto". - // Since mDuration is either a string or uninitialized, we consider it equal - // if aOther.mDuration is also either a string or uninitialized. - durationEqual = !aOther.mDuration.IsUnrestrictedDouble(); - } - return durationEqual && + return mDuration == aOther.mDuration && mDelay == aOther.mDelay && mIterations == aOther.mIterations && mIterationStart == aOther.mIterationStart && diff --git a/dom/animation/TimingParams.h b/dom/animation/TimingParams.h index 442ebc482d..26829afcb2 100644 --- a/dom/animation/TimingParams.h +++ b/dom/animation/TimingParams.h @@ -33,20 +33,39 @@ class ElementOrCSSPseudoElement; struct TimingParams { TimingParams() = default; - TimingParams(const dom::AnimationEffectTimingProperties& aTimingProperties, - const dom::Element* aTarget); - explicit TimingParams(double aDuration); static TimingParams FromOptionsUnion( const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, - const Nullable& aTarget); + const Nullable& aTarget, + ErrorResult& aRv); static TimingParams FromOptionsUnion( const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, - const Nullable& aTarget); + const Nullable& aTarget, + ErrorResult& aRv); - // The unitialized state of mDuration represents "auto". - // Bug 1237173: We will replace this with Maybe. - dom::OwningUnrestrictedDoubleOrString mDuration; + // Range-checks and validates an UnrestrictedDoubleOrString or + // OwningUnrestrictedDoubleOrString object and converts to a + // StickyTimeDuration value or Nothing() if aDuration is "auto". + // Caller must check aRv.Failed(). + template + static Maybe ParseDuration(DoubleOrString& aDuration, + ErrorResult& aRv) { + Maybe result; + if (aDuration.IsUnrestrictedDouble()) { + double durationInMs = aDuration.GetAsUnrestrictedDouble(); + if (durationInMs >= 0) { + result.emplace(StickyTimeDuration::FromMilliseconds(durationInMs)); + return result; + } + } else if (aDuration.GetAsString().EqualsLiteral("auto")) { + return result; + } + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return result; + } + + // mDuration.isNothing() represents the "auto" value + Maybe mDuration; TimeDuration mDelay; // Initializes to zero TimeDuration mEndDelay; double mIterations = 1.0; // Can be NaN, negative, +/-Infinity diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 61a561fa5c..5b981c2fb3 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -3425,9 +3425,14 @@ Element::Animate(const Nullable& aTarget, } } + TimingParams timingParams = + TimingParams::FromOptionsUnion(aOptions, aTarget, aError); + if (aError.Failed()) { + return nullptr; + } + RefPtr effect = - KeyframeEffect::Constructor(global, aTarget, frames, - TimingParams::FromOptionsUnion(aOptions, aTarget), aError); + KeyframeEffect::Constructor(global, aTarget, frames, timingParams, aError); if (aError.Failed()) { return nullptr; } diff --git a/dom/base/File.cpp b/dom/base/File.cpp index ca07193795..724c281d2e 100644 --- a/dom/base/File.cpp +++ b/dom/base/File.cpp @@ -227,12 +227,6 @@ Blob::IsFile() const return mImpl->IsFile(); } -bool -Blob::IsDirectory() const -{ - return mImpl->IsDirectory(); -} - const nsTArray>* Blob::GetSubBlobImpls() const { @@ -420,11 +414,10 @@ File::Create(nsISupports* aParent, BlobImpl* aImpl) /* static */ already_AddRefed File::Create(nsISupports* aParent, const nsAString& aName, const nsAString& aContentType, uint64_t aLength, - int64_t aLastModifiedDate, BlobDirState aDirState) + int64_t aLastModifiedDate) { RefPtr file = new File(aParent, - new BlobImplBase(aName, aContentType, aLength, aLastModifiedDate, - aDirState)); + new BlobImplBase(aName, aContentType, aLength, aLastModifiedDate)); return file.forget(); } @@ -946,17 +939,6 @@ BlobImplFile::SetPath(const nsAString& aPath) mPath = aPath; } -void -BlobImplFile::LookupAndCacheIsDirectory() -{ - MOZ_ASSERT(mIsFile, - "This should only be called when this object has been created " - "from an nsIFile to note that the nsIFile is a directory"); - bool isDir; - mFile->IsDirectory(&isDir); - mDirState = isDir ? BlobDirState::eIsDir : BlobDirState::eIsNotDir; -} - //////////////////////////////////////////////////////////////////////////// // EmptyBlobImpl implementation diff --git a/dom/base/File.h b/dom/base/File.h index c43439071d..a052eb8b7b 100644 --- a/dom/base/File.h +++ b/dom/base/File.h @@ -44,18 +44,6 @@ class BlobImpl; class File; class OwningArrayBufferOrArrayBufferViewOrBlobOrString; -/** - * Used to indicate when a Blob/BlobImpl that was created from an nsIFile - * (when IsFile() will return true) was from an nsIFile for which - * nsIFile::IsDirectory() returned true. This is a tri-state to enable us to - * assert that the state is always set when callers request it. - */ -enum BlobDirState : uint32_t { - eIsDir, - eIsNotDir, - eUnknownIfDir -}; - class Blob : public nsIDOMBlob , public nsIXHRSendable , public nsIMutable @@ -101,12 +89,6 @@ public: bool IsFile() const; - /** - * This may return true if the Blob was created from an nsIFile that is a - * directory. - */ - bool IsDirectory() const; - const nsTArray>* GetSubBlobImpls() const; // This method returns null if this Blob is not a File; it returns @@ -114,9 +96,6 @@ public: // otherwise it returns a new File object with the same BlobImpl. already_AddRefed ToFile(); - // XXXjwatt Consider having a ToDirectory() method. The need for a FileSystem - // object complicates that though. - // This method creates a new File object with the given name and the same // BlobImpl. already_AddRefed ToFile(const nsAString& aName, @@ -194,7 +173,7 @@ public: static already_AddRefed Create(nsISupports* aParent, const nsAString& aName, const nsAString& aContentType, uint64_t aLength, - int64_t aLastModifiedDate, BlobDirState aDirState); + int64_t aLastModifiedDate); // The returned File takes ownership of aMemoryBuffer. aMemoryBuffer will be // freed by moz_free so it must be allocated by moz_malloc or something @@ -339,28 +318,6 @@ public: virtual bool IsFile() const = 0; - /** - * Called when this BlobImpl was created from an nsIFile in order to call - * nsIFile::IsDirectory() and cache the result so that when the BlobImpl is - * copied to another process that informaton is available. - * nsIFile::IsDirectory() does synchronous I/O, and BlobImpl objects may be - * created on the main thread or in a non-chrome process (where I/O is not - * allowed). Do not call this on a non-chrome process, and preferably do not - * call it on the main thread. - * - * Not all creators of BlobImplFile will call this method, in which case - * calling IsDirectory will MOZ_ASSERT. - */ - virtual void LookupAndCacheIsDirectory() = 0; - virtual void SetIsDirectory(bool aIsDir) = 0; - virtual bool IsDirectory() const = 0; - - /** - * Prefer IsDirectory(). This exists to help consumer code pass on state from - * one BlobImpl when creating another. - */ - virtual BlobDirState GetDirState() const = 0; - // True if this implementation can be sent to other threads. virtual bool MayBeClonedToOtherThreads() const { @@ -377,11 +334,9 @@ class BlobImplBase : public BlobImpl { public: BlobImplBase(const nsAString& aName, const nsAString& aContentType, - uint64_t aLength, int64_t aLastModifiedDate, - BlobDirState aDirState = BlobDirState::eUnknownIfDir) + uint64_t aLength, int64_t aLastModifiedDate) : mIsFile(true) , mImmutable(false) - , mDirState(aDirState) , mContentType(aContentType) , mName(aName) , mStart(0) @@ -397,7 +352,6 @@ public: uint64_t aLength) : mIsFile(true) , mImmutable(false) - , mDirState(BlobDirState::eUnknownIfDir) , mContentType(aContentType) , mName(aName) , mStart(0) @@ -412,7 +366,6 @@ public: BlobImplBase(const nsAString& aContentType, uint64_t aLength) : mIsFile(false) , mImmutable(false) - , mDirState(BlobDirState::eUnknownIfDir) , mContentType(aContentType) , mStart(0) , mLength(aLength) @@ -427,7 +380,6 @@ public: uint64_t aLength) : mIsFile(false) , mImmutable(false) - , mDirState(BlobDirState::eUnknownIfDir) , mContentType(aContentType) , mStart(aStart) , mLength(aLength) @@ -518,36 +470,6 @@ public: return mIsFile; } - virtual void LookupAndCacheIsDirectory() override - { - MOZ_ASSERT(false, "Why is this being called on a non-BlobImplFile?"); - } - - virtual void SetIsDirectory(bool aIsDir) override - { - MOZ_ASSERT(mIsFile, - "This should only be called when this object has been created " - "from an nsIFile to note that the nsIFile is a directory"); - mDirState = aIsDir ? BlobDirState::eIsDir : BlobDirState::eIsNotDir; - } - - /** - * Returns true if the nsIFile that this object wraps is a directory. - */ - virtual bool IsDirectory() const override - { - MOZ_ASSERT(mDirState != BlobDirState::eUnknownIfDir, - "Must only be used by callers for whom the code paths are " - "know to call LookupAndCacheIsDirectory() or " - "SetIsDirectory()"); - return mDirState == BlobDirState::eIsDir; - } - - virtual BlobDirState GetDirState() const override - { - return mDirState; - } - virtual bool IsSizeUnknown() const override { return mLength == UINT64_MAX; @@ -565,7 +487,6 @@ protected: bool mIsFile; bool mImmutable; - BlobDirState mDirState; nsString mContentType; nsString mName; @@ -590,8 +511,7 @@ public: BlobImplMemory(void* aMemoryBuffer, uint64_t aLength, const nsAString& aName, const nsAString& aContentType, int64_t aLastModifiedDate) - : BlobImplBase(aName, aContentType, aLength, aLastModifiedDate, - BlobDirState::eIsNotDir) + : BlobImplBase(aName, aContentType, aLength, aLastModifiedDate) , mDataOwner(new DataOwner(aMemoryBuffer, aLength)) { NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data"); @@ -792,8 +712,6 @@ public: void SetPath(const nsAString& aFullPath); - virtual void LookupAndCacheIsDirectory() override; - // We always have size and date for this kind of blob. virtual bool IsSizeUnknown() const override { return false; } virtual bool IsDateUnknown() const override { return false; } diff --git a/dom/base/FileList.cpp b/dom/base/FileList.cpp index 5b807e7dea..4018dc1e53 100644 --- a/dom/base/FileList.cpp +++ b/dom/base/FileList.cpp @@ -4,6 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/dom/Directory.h" #include "mozilla/dom/FileList.h" #include "mozilla/dom/FileListBinding.h" #include "mozilla/dom/File.h" @@ -11,7 +12,7 @@ namespace mozilla { namespace dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileList, mFiles, mParent) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileList, mFilesOrDirectories, mParent) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileList) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY @@ -28,6 +29,20 @@ FileList::WrapObject(JSContext* aCx, JS::Handle aGivenProto) return mozilla::dom::FileListBinding::Wrap(aCx, this, aGivenProto); } +void +FileList::Append(File* aFile) +{ + OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement(); + element->SetAsFile() = aFile; +} + +void +FileList::Append(Directory* aDirectory) +{ + OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement(); + element->SetAsDirectory() = aDirectory; +} + NS_IMETHODIMP FileList::GetLength(uint32_t* aLength) { @@ -37,12 +52,76 @@ FileList::GetLength(uint32_t* aLength) } NS_IMETHODIMP -FileList::Item(uint32_t aIndex, nsISupports** aFile) +FileList::Item(uint32_t aIndex, nsISupports** aValue) { - nsCOMPtr file = Item(aIndex); - file.forget(aFile); + if (aIndex >= mFilesOrDirectories.Length()) { + return NS_ERROR_FAILURE; + } + + if (mFilesOrDirectories[aIndex].IsFile()) { + nsCOMPtr file = mFilesOrDirectories[aIndex].GetAsFile(); + file.forget(aValue); + return NS_OK; + } + + MOZ_ASSERT(mFilesOrDirectories[aIndex].IsDirectory()); + RefPtr directory = mFilesOrDirectories[aIndex].GetAsDirectory(); + directory.forget(aValue); return NS_OK; } +void +FileList::Item(uint32_t aIndex, Nullable& aValue, + ErrorResult& aRv) const +{ + if (aIndex >= mFilesOrDirectories.Length()) { + aValue.SetNull(); + return; + } + + aValue.SetValue(mFilesOrDirectories[aIndex]); +} + +void +FileList::IndexedGetter(uint32_t aIndex, bool& aFound, + Nullable& aFileOrDirectory, + ErrorResult& aRv) const +{ + aFound = aIndex < mFilesOrDirectories.Length(); + Item(aIndex, aFileOrDirectory, aRv); +} + +void +FileList::ToSequence(Sequence& aSequence, + ErrorResult& aRv) const +{ + MOZ_ASSERT(aSequence.IsEmpty()); + if (mFilesOrDirectories.IsEmpty()) { + return; + } + + if (!aSequence.SetLength(mFilesOrDirectories.Length(), + mozilla::fallible_t())) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + for (uint32_t i = 0; i < mFilesOrDirectories.Length(); ++i) { + aSequence[i] = mFilesOrDirectories[i]; + } +} + +bool +FileList::ClonableToDifferentThreadOrProcess() const +{ + for (uint32_t i = 0; i < mFilesOrDirectories.Length(); ++i) { + if (mFilesOrDirectories[i].IsDirectory()) { + return false; + } + } + + return true; +} + } // namespace dom } // namespace mozilla diff --git a/dom/base/FileList.h b/dom/base/FileList.h index 3e262153c2..b6aeeb7940 100644 --- a/dom/base/FileList.h +++ b/dom/base/FileList.h @@ -8,6 +8,7 @@ #define mozilla_dom_FileList_h #include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/UnionTypes.h" #include "nsCycleCollectionParticipant.h" #include "nsIDOMFileList.h" #include "nsWrapperCache.h" @@ -39,15 +40,13 @@ public: return mParent; } - bool Append(File* aFile) - { - return mFiles.AppendElement(aFile); - } + void Append(File* aFile); + void Append(Directory* aDirectory); bool Remove(uint32_t aIndex) { - if (aIndex < mFiles.Length()) { - mFiles.RemoveElementAt(aIndex); + if (aIndex < mFilesOrDirectories.Length()) { + mFilesOrDirectories.RemoveElementAt(aIndex); return true; } @@ -56,7 +55,7 @@ public: void Clear() { - return mFiles.Clear(); + return mFilesOrDirectories.Clear(); } static FileList* FromSupports(nsISupports* aSupports) @@ -76,29 +75,34 @@ public: return static_cast(aSupports); } - File* Item(uint32_t aIndex) + const OwningFileOrDirectory& UnsafeItem(uint32_t aIndex) const { - return mFiles.SafeElementAt(aIndex); + MOZ_ASSERT(aIndex < Length()); + return mFilesOrDirectories[aIndex]; } - File* IndexedGetter(uint32_t aIndex, bool& aFound) + void Item(uint32_t aIndex, + Nullable& aFileOrDirectory, + ErrorResult& aRv) const; + + void IndexedGetter(uint32_t aIndex, bool& aFound, + Nullable& aFileOrDirectory, + ErrorResult& aRv) const; + + uint32_t Length() const { - aFound = aIndex < mFiles.Length(); - if (!aFound) { - return nullptr; - } - return mFiles.ElementAt(aIndex); + return mFilesOrDirectories.Length(); } - uint32_t Length() - { - return mFiles.Length(); - } + void ToSequence(Sequence& aSequence, + ErrorResult& aRv) const; + + bool ClonableToDifferentThreadOrProcess() const; private: ~FileList() {} - nsTArray> mFiles; + nsTArray mFilesOrDirectories; nsCOMPtr mParent; }; diff --git a/dom/base/FormData.cpp b/dom/base/FormData.cpp index fd55894bc4..d629eee431 100644 --- a/dom/base/FormData.cpp +++ b/dom/base/FormData.cpp @@ -125,7 +125,7 @@ FormData::Append(const nsAString& aName, Blob& aBlob, return; } - AddNameBlobPair(aName, file); + AddNameBlobOrNullPair(aName, file); } void @@ -179,12 +179,18 @@ FormData::Has(const nsAString& aName) } nsresult -FormData::AddNameBlobPair(const nsAString& aName, Blob* aBlob) +FormData::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) { - MOZ_ASSERT(aBlob); + RefPtr file; + + if (!aBlob) { + FormDataTuple* data = mFormData.AppendElement(); + SetNameValuePair(data, aName, EmptyString(), true /* aWasNullBlob */); + return NS_OK; + } ErrorResult rv; - RefPtr file = GetOrCreateFileCalledBlob(*aBlob, rv); + file = GetOrCreateFileCalledBlob(*aBlob, rv); if (NS_WARN_IF(rv.Failed())) { return rv.StealNSResult(); } @@ -269,10 +275,12 @@ FormData::GetValueAtIndex(uint32_t aIndex) const void FormData::SetNameValuePair(FormDataTuple* aData, const nsAString& aName, - const nsAString& aValue) + const nsAString& aValue, + bool aWasNullBlob) { MOZ_ASSERT(aData); aData->name = aName; + aData->wasNullBlob = aWasNullBlob; aData->value.SetAsUSVString() = aValue; } @@ -285,6 +293,7 @@ FormData::SetNameFilePair(FormDataTuple* aData, MOZ_ASSERT(aFile); aData->name = aName; + aData->wasNullBlob = false; aData->value.SetAsBlob() = aFile; } @@ -366,13 +375,16 @@ FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength, nsFSMultipartFormData fs(NS_LITERAL_CSTRING("UTF-8"), nullptr); for (uint32_t i = 0; i < mFormData.Length(); ++i) { - if (mFormData[i].value.IsBlob()) { - fs.AddNameBlobPair(mFormData[i].name, mFormData[i].value.GetAsBlob()); + if (mFormData[i].wasNullBlob) { + MOZ_ASSERT(mFormData[i].value.IsUSVString()); + fs.AddNameBlobOrNullPair(mFormData[i].name, nullptr); } else if (mFormData[i].value.IsUSVString()) { fs.AddNameValuePair(mFormData[i].name, mFormData[i].value.GetAsUSVString()); } else { - MOZ_CRASH("This should no be possible."); + MOZ_ASSERT(mFormData[i].value.IsBlob()); + fs.AddNameBlobOrNullPair(mFormData[i].name, + mFormData[i].value.GetAsBlob()); } } diff --git a/dom/base/FormData.h b/dom/base/FormData.h index 66373730cb..00652e8411 100644 --- a/dom/base/FormData.h +++ b/dom/base/FormData.h @@ -35,6 +35,7 @@ private: struct FormDataTuple { nsString name; + bool wasNullBlob; OwningBlobOrUSVString value; }; @@ -45,7 +46,8 @@ private: void SetNameValuePair(FormDataTuple* aData, const nsAString& aName, - const nsAString& aValue); + const nsAString& aValue, + bool aWasNullBlob = false); void SetNameFilePair(FormDataTuple* aData, const nsAString& aName, @@ -114,8 +116,8 @@ public: return NS_OK; } - virtual nsresult AddNameBlobPair(const nsAString& aName, - Blob* aBlob) override; + virtual nsresult AddNameBlobOrNullPair(const nsAString& aName, + Blob* aBlob) override; typedef bool (*FormDataEntryCallback)(const nsString& aName, const OwningBlobOrUSVString& aValue, diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp index f0da538093..1313e53251 100644 --- a/dom/base/StructuredCloneHolder.cpp +++ b/dom/base/StructuredCloneHolder.cpp @@ -10,6 +10,7 @@ #include "mozilla/AutoRestore.h" #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/CryptoKey.h" +#include "mozilla/dom/Directory.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileList.h" #include "mozilla/dom/FileListBinding.h" @@ -723,36 +724,59 @@ ReadFileList(JSContext* aCx, { RefPtr fileList = new FileList(aHolder->ParentDuringRead()); - uint32_t tag, offset; - // Offset is the index of the blobImpl from which we can find the blobImpl - // for this FileList. - if (!JS_ReadUint32Pair(aReader, &tag, &offset)) { - return nullptr; - } - - MOZ_ASSERT(tag == 0); - - // |aCount| is the number of BlobImpls to use from the |offset|. + // |aCount| is the number of Files or Directory for this FileList. for (uint32_t i = 0; i < aCount; ++i) { - uint32_t index = offset + i; - MOZ_ASSERT(index < aHolder->BlobImpls().Length()); - - RefPtr blobImpl = aHolder->BlobImpls()[index]; - MOZ_ASSERT(blobImpl->IsFile()); - - ErrorResult rv; - blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv); - if (NS_WARN_IF(rv.Failed())) { - rv.SuppressException(); + uint32_t tagOrDirectoryType, indexOrLengthOfString; + if (!JS_ReadUint32Pair(aReader, &tagOrDirectoryType, + &indexOrLengthOfString)) { return nullptr; } - MOZ_ASSERT(blobImpl); + MOZ_ASSERT(tagOrDirectoryType == SCTAG_DOM_BLOB || + tagOrDirectoryType == Directory::eDOMRootDirectory || + tagOrDirectoryType == Directory::eNotDOMRootDirectory); - RefPtr file = File::Create(aHolder->ParentDuringRead(), blobImpl); - if (!fileList->Append(file)) { + if (tagOrDirectoryType == SCTAG_DOM_BLOB) { + MOZ_ASSERT(indexOrLengthOfString < aHolder->BlobImpls().Length()); + + RefPtr blobImpl = + aHolder->BlobImpls()[indexOrLengthOfString]; + MOZ_ASSERT(blobImpl->IsFile()); + + ErrorResult rv; + blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return nullptr; + } + + RefPtr file = + File::Create(aHolder->ParentDuringRead(), blobImpl); + MOZ_ASSERT(file); + + fileList->Append(file); + continue; + } + + nsAutoString path; + path.SetLength(indexOrLengthOfString); + size_t charSize = sizeof(nsString::char_type); + if (!JS_ReadBytes(aReader, (void*) path.BeginWriting(), + indexOrLengthOfString * charSize)) { return nullptr; } + + nsCOMPtr file; + nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true, + getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + RefPtr directory = + Directory::Create(aHolder->ParentDuringRead(), file, + (Directory::DirectoryType) tagOrDirectoryType); + fileList->Append(directory); } if (!ToJSValue(aCx, fileList, &val)) { @@ -765,7 +789,13 @@ ReadFileList(JSContext* aCx, // The format of the FileList serialization is: // - pair of ints: SCTAG_DOM_FILELIST, Length of the FileList -// - pair of ints: 0, The offset of the BlobImpl array +// - for each element of the FileList: +// - if it's a blob: +// - pair of ints: SCTAG_DOM_BLOB, index of the BlobImpl in the array +// mBlobImplArray. +// - else: +// - pair of ints: 0/1 is root, string length +// - value string bool WriteFileList(JSStructuredCloneWriter* aWriter, FileList* aFileList, @@ -775,13 +805,8 @@ WriteFileList(JSStructuredCloneWriter* aWriter, MOZ_ASSERT(aFileList); MOZ_ASSERT(aHolder); - // A FileList is serialized writing the X number of elements and the offset - // from mBlobImplArray. The Read will take X elements from mBlobImplArray - // starting from the offset. if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST, - aFileList->Length()) || - !JS_WriteUint32Pair(aWriter, 0, - aHolder->BlobImpls().Length())) { + aFileList->Length())) { return false; } @@ -789,18 +814,39 @@ WriteFileList(JSStructuredCloneWriter* aWriter, nsTArray> blobImpls; for (uint32_t i = 0; i < aFileList->Length(); ++i) { - RefPtr blobImpl = - EnsureBlobForBackgroundManager(aFileList->Item(i)->Impl(), nullptr, rv); - if (NS_WARN_IF(rv.Failed())) { - rv.SuppressException(); - return false; + const OwningFileOrDirectory& data = aFileList->UnsafeItem(i); + + if (data.IsFile()) { + RefPtr blobImpl = + EnsureBlobForBackgroundManager(data.GetAsFile()->Impl(), nullptr, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, + aHolder->BlobImpls().Length())) { + return false; + } + + aHolder->BlobImpls().AppendElement(blobImpl); + continue; } - MOZ_ASSERT(blobImpl); - blobImpls.AppendElement(blobImpl); + MOZ_ASSERT(data.IsDirectory()); + + nsAutoString path; + data.GetAsDirectory()->GetFullRealPath(path); + + size_t charSize = sizeof(nsString::char_type); + if (!JS_WriteUint32Pair(aWriter, + (uint32_t)data.GetAsDirectory()->Type(), + path.Length()) || + !JS_WriteBytes(aWriter, path.get(), path.Length() * charSize)) { + return false; + } } - aHolder->BlobImpls().AppendElements(blobImpls); return true; } @@ -1004,7 +1050,9 @@ StructuredCloneHolder::CustomWriteHandler(JSContext* aCx, // See if this is a FileList object. { FileList* fileList = nullptr; - if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) { + if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList)) && + (mSupportedContext == SameProcessSameThread || + fileList->ClonableToDifferentThreadOrProcess())) { return WriteFileList(aWriter, fileList, this); } } diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 384a092666..20c7cb7d5c 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -7576,7 +7576,6 @@ nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable, // has this data available to it when passed over: blobImpl->GetSize(rv); blobImpl->GetLastModified(rv); - blobImpl->LookupAndCacheIsDirectory(); } else { if (aInSyncMessage) { // Can't do anything. diff --git a/dom/base/nsIDOMFileList.idl b/dom/base/nsIDOMFileList.idl index 338bd72793..8e6dcf52a3 100644 --- a/dom/base/nsIDOMFileList.idl +++ b/dom/base/nsIDOMFileList.idl @@ -5,7 +5,7 @@ #include "nsISupports.idl" -[uuid(57128a85-34de-42db-a252-84dd57724a59)] +[builtinclass, uuid(57128a85-34de-42db-a252-84dd57724a59)] interface nsIDOMFileList : nsISupports { readonly attribute unsigned long length; diff --git a/dom/base/test/file_bug1250148.sjs b/dom/base/test/file_bug1250148.sjs new file mode 100644 index 0000000000..a85347896b --- /dev/null +++ b/dom/base/test/file_bug1250148.sjs @@ -0,0 +1,60 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function utf8decode(s) { + return decodeURIComponent(escape(s)); +} + +function utf8encode(s) { + return unescape(encodeURIComponent(s)); +} + +function handleRequest(request, response) { + var bodyStream = new BinaryInputStream(request.bodyInputStream); + + var requestBody = ""; + while ((bodyAvail = bodyStream.available()) > 0) { + requestBody += bodyStream.readBytes(bodyAvail); + } + + var result = []; + + if (request.method == "POST") { + var contentTypeParams = {}; + request.getHeader("Content-Type").split(/\s*\;\s*/).forEach(function(s) { + if (s.indexOf('=') >= 0) { + let [name, value] = s.split('='); + contentTypeParams[name] = value; + } + else { + contentTypeParams[''] = s; + } + }); + + if (contentTypeParams[''] == "multipart/form-data" && + request.queryString == "") { + requestBody.split("--" + contentTypeParams.boundary).slice(1, -1).forEach(function (s) { + + let headers = {}; + let headerEnd = s.indexOf("\r\n\r\n"); + s.substr(2, headerEnd-2).split("\r\n").forEach(function(s) { + // We're assuming UTF8 for now + let [name, value] = s.split(': '); + headers[name] = utf8decode(value); + }); + + let body = s.substring(headerEnd + 4, s.length - 2); + if (!headers["Content-Type"] || headers["Content-Type"] == "text/plain") { + // We're assuming UTF8 for now + body = utf8decode(body); + } + result.push({ headers: headers, body: body}); + }); + } + } + + response.setHeader("Content-Type", "text/plain; charset=utf-8", false); + response.write(utf8encode(JSON.stringify(result))); +} diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 0ea90b5b13..edec3586a8 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -257,6 +257,7 @@ support-files = referrer_change_server.sjs file_change_policy_redirect.html file_bug1198095.js + file_bug1250148.sjs [test_anonymousContent_api.html] [test_anonymousContent_append_after_reflow.html] @@ -848,3 +849,5 @@ skip-if = buildapp == 'b2g' #no ssl support [test_document.all_iteration.html] [test_bug1198095.html] [test_bug1187157.html] +[test_bug769117.html] +[test_bug1250148.html] diff --git a/dom/base/test/script_postmessages_fileList.js b/dom/base/test/script_postmessages_fileList.js index 8c8af4289a..8382958f32 100644 --- a/dom/base/test/script_postmessages_fileList.js +++ b/dom/base/test/script_postmessages_fileList.js @@ -1,15 +1,25 @@ -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +var { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.importGlobalProperties(["File"]); -let testFile = Cc["@mozilla.org/file/directory_service;1"] - .getService(Ci.nsIDirectoryService) - .QueryInterface(Ci.nsIProperties) - .get("ProfD", Ci.nsIFile); -testFile.append("prefs.js"); - addMessageListener("file.open", function () { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryService) + .QueryInterface(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + testFile.append("prefs.js"); + sendAsyncMessage("file.opened", { file: new File(testFile) }); }); +addMessageListener("dir.open", function () { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryService) + .QueryInterface(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + + sendAsyncMessage("dir.opened", { + dir: testFile.path + }); +}); diff --git a/dom/base/test/test_bug1187157.html b/dom/base/test/test_bug1187157.html index dda4804a7b..37942f0051 100644 --- a/dom/base/test/test_bug1187157.html +++ b/dom/base/test/test_bug1187157.html @@ -15,14 +15,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=789315 diff --git a/dom/base/test/test_bug1250148.html b/dom/base/test/test_bug1250148.html new file mode 100644 index 0000000000..1584360442 --- /dev/null +++ b/dom/base/test/test_bug1250148.html @@ -0,0 +1,52 @@ + + + + + + Test for Bug 1250148 - FormData and HTML submission compatibility + + + + +
+ + + diff --git a/dom/base/test/test_bug769117.html b/dom/base/test/test_bug769117.html new file mode 100644 index 0000000000..bd9e002e22 --- /dev/null +++ b/dom/base/test/test_bug769117.html @@ -0,0 +1,51 @@ + + + + + + Test for Bug 769117 + + + + + + + + + diff --git a/dom/base/test/test_postMessages.html b/dom/base/test/test_postMessages.html index e2d146eac9..4157ff0665 100644 --- a/dom/base/test/test_postMessages.html +++ b/dom/base/test/test_postMessages.html @@ -61,18 +61,18 @@ function compare(a, b) { } var clonableObjects = [ - 'hello world', - 123, - null, - true, - new Date(), - [ 1, 'test', true, new Date() ], - { a: true, b: null, c: new Date(), d: [ true, false, {} ] }, - new Blob([123], { type: 'plain/text' }), - new ImageData(2, 2), + { crossThreads: true, data: 'hello world' }, + { crossThreads: true, data: 123 }, + { crossThreads: true, data: null }, + { crossThreads: true, data: true }, + { crossThreads: true, data: new Date() }, + { crossThreads: true, data: [ 1, 'test', true, new Date() ] }, + { crossThreads: true, data: { a: true, b: null, c: new Date(), d: [ true, false, {} ] } }, + { crossThreads: true, data: new Blob([123], { type: 'plain/text' }) }, + { crossThreads: true, data: new ImageData(2, 2) }, ]; -function create_fileList() { +function create_fileList_forFile() { var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js"); var script = SpecialPowers.loadChromeScript(url); @@ -84,7 +84,7 @@ function create_fileList() { var domFile = fileList.files[0]; is(domFile.name, "prefs.js", "fileName should be prefs.js"); - clonableObjects.push(fileList.files); + clonableObjects.push({ crossThreads: true, data: fileList.files }); script.destroy(); next(); } @@ -93,6 +93,27 @@ function create_fileList() { script.sendAsyncMessage("file.open"); } +function create_fileList_forDir() { + var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js"); + var script = SpecialPowers.loadChromeScript(url); + + function onOpened(message) { + var fileList = document.getElementById('fileList'); + SpecialPowers.wrap(fileList).mozSetDirectory(message.dir); + + // Just a simple test + is(fileList.files.length, 1, "Filelist has 1 element"); + ok(fileList.files[0] instanceof Directory, "We have a directory."); + + clonableObjects.push({ crossThreads: false, data: fileList.files }); + script.destroy(); + next(); + } + + script.addMessageListener("dir.opened", onOpened); + script.sendAsyncMessage("dir.open"); +} + function runTests(obj) { ok(('clonableObjects' in obj) && ('transferableObjects' in obj) && @@ -113,8 +134,16 @@ function runTests(obj) { } var object = clonableObjects[clonableObjectsId++]; - obj.send(object, []).then(function(received) { - compare(received.data, object); + + // If this test requires a cross-thread structured clone algorithm, maybe + // we have to skip it. + if (!object.crossThread && obj.crossThread) { + runClonableTest(); + return; + } + + obj.send(object.data, []).then(function(received) { + compare(received.data, object.data); runClonableTest(); }); } @@ -203,6 +232,7 @@ function test_windowToWindow() { runTests({ clonableObjects: true, transferableObjects: true, + crossThread: false, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -256,6 +286,7 @@ function test_windowToIframeURL(url) { runTests({ clonableObjects: true, transferableObjects: true, + crossThread: false, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -303,6 +334,7 @@ function test_workers() { runTests({ clonableObjects: true, transferableObjects: true, + crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -346,6 +378,7 @@ function test_broadcastChannel() { runTests({ clonableObjects: true, transferableObjects: false, + crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { if (ports.length) { @@ -391,6 +424,7 @@ function test_broadcastChannel_inWorkers() { runTests({ clonableObjects: true, transferableObjects: false, + crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { if (ports.length) { @@ -432,6 +466,7 @@ function test_messagePort() { runTests({ clonableObjects: true, transferableObjects: true, + crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -477,6 +512,7 @@ function test_messagePort_inWorkers() { runTests({ clonableObjects: true, transferableObjects: true, + crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -498,7 +534,8 @@ function test_messagePort_inWorkers() { } var tests = [ - create_fileList, + create_fileList_forFile, + create_fileList_forDir, test_windowToWindow, test_windowToIframe, diff --git a/dom/browser-element/BrowserElementAudioChannel.cpp b/dom/browser-element/BrowserElementAudioChannel.cpp index c4a2d9715c..44fd49fb62 100644 --- a/dom/browser-element/BrowserElementAudioChannel.cpp +++ b/dom/browser-element/BrowserElementAudioChannel.cpp @@ -26,6 +26,7 @@ #include "nsNetUtil.h" #include "nsPIDOMWindow.h" #include "nsServiceManagerUtils.h" +#include "nsContentUtils.h" namespace mozilla { namespace dom { @@ -520,12 +521,7 @@ BrowserElementAudioChannel::NotifyChannel(const nsAString& aEvent, do_GetService("@mozilla.org/system-message-internal;1"); MOZ_ASSERT(systemMessenger); - AutoJSAPI jsAPI; - if (!jsAPI.Init(GetOwner())) { - return nullptr; - } - - JS::Rooted value(jsAPI.cx()); + JS::Rooted value(nsContentUtils::RootingCxForThread()); value.setInt32((uint32_t)mAudioChannel); nsCOMPtr manifestURI; diff --git a/dom/devicestorage/DeviceStorage.h b/dom/devicestorage/DeviceStorage.h index 05949ca3a2..31d79f9495 100644 --- a/dom/devicestorage/DeviceStorage.h +++ b/dom/devicestorage/DeviceStorage.h @@ -101,8 +101,9 @@ public: // we want to make sure that the names of file can't reach // outside of the type of storage the user asked for. - bool IsSafePath(); - bool IsSafePath(const nsAString& aPath); + bool IsSafePath() const; + bool ValidateAndSplitPath(const nsAString& aPath, + nsTArray* aParts = nullptr) const; void Dump(const char* label); @@ -137,7 +138,6 @@ public: private: ~DeviceStorageFile() {} void Init(); - void NormalizeFilePath(); void AppendRelativePath(const nsAString& aPath); void AccumDirectoryUsage(nsIFile* aFile, uint64_t* aPicturesSoFar, diff --git a/dom/devicestorage/DeviceStorageStatics.cpp b/dom/devicestorage/DeviceStorageStatics.cpp index 806162ffd0..f02d904fdc 100644 --- a/dom/devicestorage/DeviceStorageStatics.cpp +++ b/dom/devicestorage/DeviceStorageStatics.cpp @@ -573,7 +573,16 @@ DeviceStorageStatics::ResetOverrideRootDir() nsCOMPtr f; DS_LOG_INFO(""); - if (Preferences::GetBool(kPrefTesting, false)) { + // For users running on desktop, it's convenient to be able to override + // all of the directories to point to a single tree, much like what happens + // on a real device. + const nsAdoptingString& overrideRootDir = + mozilla::Preferences::GetString(kPrefOverrideRootDir); + if (overrideRootDir && !overrideRootDir.IsEmpty()) { + NS_NewLocalFile(overrideRootDir, false, getter_AddRefs(f)); + } + + if (!f && Preferences::GetBool(kPrefTesting, false)) { DS_LOG_INFO("temp"); nsCOMPtr dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); @@ -583,15 +592,6 @@ DeviceStorageStatics::ResetOverrideRootDir() f->AppendRelativeNativePath( NS_LITERAL_CSTRING("device-storage-testing")); } - } else { - // For users running on desktop, it's convenient to be able to override - // all of the directories to point to a single tree, much like what happens - // on a real device. - const nsAdoptingString& overrideRootDir = - mozilla::Preferences::GetString(kPrefOverrideRootDir); - if (overrideRootDir && !overrideRootDir.IsEmpty()) { - NS_NewLocalFile(overrideRootDir, false, getter_AddRefs(f)); - } } if (f) { diff --git a/dom/devicestorage/ipc/ipc.json b/dom/devicestorage/ipc/ipc.json deleted file mode 100644 index 747179bccb..0000000000 --- a/dom/devicestorage/ipc/ipc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ -"runtests":{ -}, -"excludetests":{ - "dom/devicestorage/test/test_dirs.html":"excluded", - "dom/devicestorage/test/test_storageAreaListener.html":"excluded" - } -} diff --git a/dom/devicestorage/ipc/mochitest.ini b/dom/devicestorage/ipc/mochitest.ini deleted file mode 100644 index 0a6ed59708..0000000000 --- a/dom/devicestorage/ipc/mochitest.ini +++ /dev/null @@ -1,8 +0,0 @@ -[DEFAULT] -skip-if = toolkit == 'android' || e10s #bug 781789 & bug 782275 -support-files = - ../test/devicestorage_common.js - ipc.json - -[test_ipc.html] -skip-if = buildapp == 'mulet' || buildapp == 'b2g' # b2g(nested ipc not working) b2g-debug(nested ipc not working) b2g-desktop(nested ipc not working) diff --git a/dom/devicestorage/ipc/test_ipc.html b/dom/devicestorage/ipc/test_ipc.html deleted file mode 100644 index 353b3e5e8e..0000000000 --- a/dom/devicestorage/ipc/test_ipc.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - Test for OOP DeviceStorage - - - - - - - - diff --git a/dom/devicestorage/moz.build b/dom/devicestorage/moz.build index dcf862a512..c6b31af706 100644 --- a/dom/devicestorage/moz.build +++ b/dom/devicestorage/moz.build @@ -41,7 +41,6 @@ LOCAL_INCLUDES += [ ] MOCHITEST_MANIFESTS += [ - 'ipc/mochitest.ini', 'test/mochitest.ini', ] MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini'] diff --git a/dom/devicestorage/nsDeviceStorage.cpp b/dom/devicestorage/nsDeviceStorage.cpp index d06f780775..b451d8e4bc 100644 --- a/dom/devicestorage/nsDeviceStorage.cpp +++ b/dom/devicestorage/nsDeviceStorage.cpp @@ -30,6 +30,7 @@ #include "nsArrayUtils.h" #include "nsAutoPtr.h" +#include "nsCharSeparatedTokenizer.h" #include "nsGlobalWindow.h" #include "nsServiceManagerUtils.h" #include "nsIFile.h" @@ -76,10 +77,35 @@ using namespace mozilla::dom; using namespace mozilla::dom::devicestorage; using namespace mozilla::ipc; -namespace mozilla { +namespace mozilla +{ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close); } // namespace mozilla +namespace { + +void +NormalizeFilePath(nsAString& aPath) +{ +#if defined(XP_WIN) + char16_t* cur = aPath.BeginWriting(); + char16_t* end = aPath.EndWriting(); + for (; cur < end; ++cur) { + if (char16_t('\\') == *cur) { + *cur = FILESYSTEM_DOM_PATH_SEPARATOR_CHAR; + } + } +#endif +} + +bool +TokenizerIgnoreNothing(char16_t /* aChar */) +{ + return false; +} + +} // anonymous namespace + StaticAutoPtr DeviceStorageUsedSpaceCache::sDeviceStorageUsedSpaceCache; @@ -510,7 +536,8 @@ DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, if (!mPath.EqualsLiteral("")) { AppendRelativePath(mPath); } - NormalizeFilePath(); + + NormalizeFilePath(mPath); } DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, @@ -525,7 +552,7 @@ DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, { Init(); AppendRelativePath(aPath); - NormalizeFilePath(); + NormalizeFilePath(mPath); } DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, @@ -734,7 +761,7 @@ DeviceStorageFile::CreateUnique(const nsAString& aStorageType, void DeviceStorageFile::SetPath(const nsAString& aPath) { mPath.Assign(aPath); - NormalizeFilePath(); + NormalizeFilePath(mPath); } void @@ -745,13 +772,14 @@ DeviceStorageFile::SetEditable(bool aEditable) { // we want to make sure that the names of file can't reach // outside of the type of storage the user asked for. bool -DeviceStorageFile::IsSafePath() +DeviceStorageFile::IsSafePath() const { - return IsSafePath(mRootDir) && IsSafePath(mPath); + return ValidateAndSplitPath(mRootDir) && ValidateAndSplitPath(mPath); } bool -DeviceStorageFile::IsSafePath(const nsAString& aPath) +DeviceStorageFile::ValidateAndSplitPath(const nsAString& aPath, + nsTArray* aParts) const { nsAString::const_iterator start, end; aPath.BeginReading(start); @@ -764,33 +792,43 @@ DeviceStorageFile::IsSafePath(const nsAString& aPath) StringBeginsWith(aPath, tildeSlash)) { NS_WARNING("Path name starts with tilde!"); return false; - } - // split on /. if any token is "", ., or .., return false. - NS_ConvertUTF16toUTF8 cname(aPath); - char* buffer = cname.BeginWriting(); - const char* token; + } - while ((token = nsCRT::strtok(buffer, "/", &buffer))) { - if (PL_strcmp(token, "") == 0 || - PL_strcmp(token, ".") == 0 || - PL_strcmp(token, "..") == 0 ) { + NS_NAMED_LITERAL_STRING(kCurrentDir, "."); + NS_NAMED_LITERAL_STRING(kParentDir, ".."); + + // Split path and check each path component. + nsCharSeparatedTokenizerTemplate + tokenizer(aPath, FILESYSTEM_DOM_PATH_SEPARATOR_CHAR); + + while (tokenizer.hasMoreTokens()) { + nsDependentSubstring pathComponent = tokenizer.nextToken(); + // The path containing empty components, such as "foo//bar", is invalid. + // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar", + // to walk up the directory. + if (pathComponent.IsEmpty() || + pathComponent.Equals(kCurrentDir) || + pathComponent.Equals(kParentDir)) { return false; } + + if (aParts) { + aParts->AppendElement(pathComponent); + } } return true; } void -DeviceStorageFile::NormalizeFilePath() { - FileSystemUtils::LocalPathToNormalizedPath(mPath, mPath); -} - -void -DeviceStorageFile::AppendRelativePath(const nsAString& aPath) { +DeviceStorageFile::AppendRelativePath(const nsAString& aPath) +{ if (!mFile) { return; } - if (!IsSafePath(aPath)) { + + nsTArray parts; + + if (!ValidateAndSplitPath(aPath, &parts)) { // All of the APIs (in the child) do checks to verify that the path is // valid and return PERMISSION_DENIED if a non-safe path is entered. // This check is done in the parent and prevents a compromised @@ -800,9 +838,13 @@ DeviceStorageFile::AppendRelativePath(const nsAString& aPath) { NS_WARNING(NS_LossyConvertUTF16toASCII(aPath).get()); return; } - nsString localPath; - FileSystemUtils::NormalizedPathToLocalPath(aPath, localPath); - mFile->AppendRelativePath(localPath); + + for (uint32_t i = 0; i < parts.Length(); ++i) { + nsresult rv = mFile->AppendRelativePath(parts[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } } nsresult @@ -3932,14 +3974,8 @@ DeviceStorageRequestManager::Resolve(uint32_t aId, uint64_t aValue, return NS_OK; } - nsIGlobalObject* global = mPending[i].mRequest->GetOwnerGlobal(); - - AutoJSAPI jsapi; - if (NS_WARN_IF(!jsapi.Init(global))) { - return RejectInternal(i, NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN)); - } - - JS::RootedValue value(jsapi.cx(), JS_NumberValue((double)aValue)); + JS::RootedValue value(nsContentUtils::RootingCxForThread(), + JS_NumberValue((double)aValue)); return ResolveInternal(i, value); } diff --git a/dom/devicestorage/test/devicestorage_common.js b/dom/devicestorage/test/devicestorage_common.js index 22f4be047f..7b4cf5dd61 100644 --- a/dom/devicestorage/test/devicestorage_common.js +++ b/dom/devicestorage/test/devicestorage_common.js @@ -4,7 +4,7 @@ */ var oldVal = false; - + Object.defineProperty(Array.prototype, "remove", { enumerable: false, configurable: false, @@ -17,36 +17,26 @@ Object.defineProperty(Array.prototype, "remove", { } }); -function devicestorage_setup() { - - // ensure that the directory we are writing into is empty - try { - const Cc = SpecialPowers.Cc; - const Ci = SpecialPowers.Ci; - var directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); - var f = directoryService.get("TmpD", Ci.nsIFile); - f.appendRelativePath("device-storage-testing"); - f.remove(true); - } catch(e) {} - +function devicestorage_setup(callback) { SimpleTest.waitForExplicitFinish(); - if (SpecialPowers.isMainProcess()) { - try { - oldVal = SpecialPowers.getBoolPref("device.storage.enabled"); - } catch(e) {} - SpecialPowers.setBoolPref("device.storage.enabled", true); - SpecialPowers.setBoolPref("device.storage.testing", true); - SpecialPowers.setBoolPref("device.storage.prompt.testing", true); - } -} -function devicestorage_cleanup() { - if (SpecialPowers.isMainProcess()) { - SpecialPowers.setBoolPref("device.storage.enabled", oldVal); - SpecialPowers.setBoolPref("device.storage.testing", false); - SpecialPowers.setBoolPref("device.storage.prompt.testing", false); - } - SimpleTest.finish(); + const Cc = SpecialPowers.Cc; + const Ci = SpecialPowers.Ci; + var directoryService = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties); + var f = directoryService.get("TmpD", Ci.nsIFile); + f.appendRelativePath("device-storage-testing"); + + let script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('remove_testing_directory.js')); + + script.addMessageListener('directory-removed', function listener () { + script.removeMessageListener('directory-removed', listener); + var prefs = [["device.storage.enabled", true], + ["device.storage.testing", true], + ["device.storage.overrideRootDir", f.path], + ["device.storage.prompt.testing", true]]; + SpecialPowers.pushPrefEnv({"set": prefs}, callback); + }); } function getRandomBuffer() { @@ -75,7 +65,7 @@ function randomFilename(l) { function reportErrorAndQuit(e) { ok(false, "handleError was called : " + e.target.error.name); - devicestorage_cleanup(); + SimpleTest.finish(); } function createTestFiles(storage, paths) { diff --git a/dom/devicestorage/test/mochitest.ini b/dom/devicestorage/test/mochitest.ini index ff65429bca..cee91b441d 100644 --- a/dom/devicestorage/test/mochitest.ini +++ b/dom/devicestorage/test/mochitest.ini @@ -1,6 +1,7 @@ [DEFAULT] -skip-if = e10s || (toolkit == 'android' && processor == 'x86') # bug 781789 & bug 782275 +skip-if = (toolkit == 'android' && processor == 'x86') # Android: bug 781789 & bug 782275 support-files = devicestorage_common.js + remove_testing_directory.js [test_823965.html] # [test_add.html] @@ -9,6 +10,7 @@ support-files = devicestorage_common.js [test_available.html] [test_basic.html] [test_dirs.html] +skip-if = e10s # Bug 1063569. # [test_diskSpace.html] # Possible race between the time we write a file, and the # time it takes to be reflected by statfs(). Bug # 791287 diff --git a/dom/devicestorage/test/remove_testing_directory.js b/dom/devicestorage/test/remove_testing_directory.js new file mode 100644 index 0000000000..2a970dd65e --- /dev/null +++ b/dom/devicestorage/test/remove_testing_directory.js @@ -0,0 +1,11 @@ +// ensure that the directory we are writing into is empty +try { + var Cc = Components.classes; + var Ci = Components.interfaces; + var directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + var f = directoryService.get("TmpD", Ci.nsIFile); + f.appendRelativePath("device-storage-testing"); + f.remove(true); +} catch(e) {} + +sendAsyncMessage('directory-removed', {}); diff --git a/dom/devicestorage/test/test_823965.html b/dom/devicestorage/test/test_823965.html index 2230fd8256..6863b22844 100644 --- a/dom/devicestorage/test/test_823965.html +++ b/dom/devicestorage/test/test_823965.html @@ -22,7 +22,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=823965
 
 
- diff --git a/dom/devicestorage/test/test_add.html b/dom/devicestorage/test/test_add.html index 7e0d8d1d12..a91c8cae40 100644 --- a/dom/devicestorage/test/test_add.html +++ b/dom/devicestorage/test/test_add.html @@ -18,13 +18,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=786922 Mozilla Bug 786922

 
 
- diff --git a/dom/devicestorage/test/test_addCorrectType.html b/dom/devicestorage/test/test_addCorrectType.html index 1a4cd8b3f4..8b42c21c17 100644 --- a/dom/devicestorage/test/test_addCorrectType.html +++ b/dom/devicestorage/test/test_addCorrectType.html @@ -18,13 +18,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=786922 Mozilla Bug 786922

 
 
- diff --git a/dom/devicestorage/test/test_available.html b/dom/devicestorage/test/test_available.html index 32ba645d88..6949a66208 100644 --- a/dom/devicestorage/test/test_available.html +++ b/dom/devicestorage/test/test_available.html @@ -22,16 +22,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=834595
 
 
diff --git a/dom/devicestorage/test/test_basic.html b/dom/devicestorage/test/test_basic.html index 9892a8ea86..291093ac0a 100644 --- a/dom/devicestorage/test/test_basic.html +++ b/dom/devicestorage/test/test_basic.html @@ -17,12 +17,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_diskSpace.html b/dom/devicestorage/test/test_diskSpace.html index 34496e28e3..b5f11d29d7 100644 --- a/dom/devicestorage/test/test_diskSpace.html +++ b/dom/devicestorage/test/test_diskSpace.html @@ -17,13 +17,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_dotdot.html b/dom/devicestorage/test/test_dotdot.html index f3b14c7ad0..8dceac81ce 100644 --- a/dom/devicestorage/test/test_dotdot.html +++ b/dom/devicestorage/test/test_dotdot.html @@ -17,12 +17,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_enumerate.html b/dom/devicestorage/test/test_enumerate.html index 0d5bea671a..2381cb00fb 100644 --- a/dom/devicestorage/test/test_enumerate.html +++ b/dom/devicestorage/test/test_enumerate.html @@ -17,22 +17,22 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_enumerateMultipleContinue.html b/dom/devicestorage/test/test_enumerateMultipleContinue.html index c8c4c33ca6..6e46cc33e8 100644 --- a/dom/devicestorage/test/test_enumerateMultipleContinue.html +++ b/dom/devicestorage/test/test_enumerateMultipleContinue.html @@ -17,12 +17,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_enumerateNoParam.html b/dom/devicestorage/test/test_enumerateNoParam.html index 6ad83f7d2c..aa6e4acac0 100644 --- a/dom/devicestorage/test/test_enumerateNoParam.html +++ b/dom/devicestorage/test/test_enumerateNoParam.html @@ -35,7 +35,7 @@ function enumerateSuccess(e) { if (e.target.result == null) { ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array") - devicestorage_cleanup(); + SimpleTest.finish(); return; } @@ -67,7 +67,7 @@ function enumerateSuccess(e) { function handleError(e) { ok(false, "handleError was called : " + e.target.error.name); - devicestorage_cleanup(); + SimpleTest.finish(); } function addSuccess(e) { @@ -82,7 +82,7 @@ function addSuccess(e) { function addError(e) { ok(false, "addError was called : " + e.target.error.name); - devicestorage_cleanup(); + SimpleTest.finish(); } var storage = navigator.getDeviceStorage("pictures"); diff --git a/dom/devicestorage/test/test_enumerateOptions.html b/dom/devicestorage/test/test_enumerateOptions.html index 48af8f81cd..b4a7cf3c2a 100644 --- a/dom/devicestorage/test/test_enumerateOptions.html +++ b/dom/devicestorage/test/test_enumerateOptions.html @@ -18,12 +18,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_freeSpace.html b/dom/devicestorage/test/test_freeSpace.html index a2a5f42b45..73c8e3cc08 100644 --- a/dom/devicestorage/test/test_freeSpace.html +++ b/dom/devicestorage/test/test_freeSpace.html @@ -22,23 +22,23 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103
 
 
- diff --git a/dom/devicestorage/test/test_fs_appendFile.html b/dom/devicestorage/test/test_fs_appendFile.html index e3acfc6aeb..3cb6c39547 100644 --- a/dom/devicestorage/test/test_fs_appendFile.html +++ b/dom/devicestorage/test/test_fs_appendFile.html @@ -26,15 +26,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=855952 var file = new Blob(["This is a text file."], {type: "text/plain"}); var appendFile = new Blob([" Another text file."], {type: "text/plain"}); -devicestorage_setup(); +devicestorage_setup(function () { function deleteSuccess(e) { - devicestorage_cleanup(); + SimpleTest.finish(); } function deleteError(e) { ok(false, "deleteError was called : " + e.target.error.name); - devicestorage_cleanup(); + SimpleTest.finish(); } function appendSuccess(e) { @@ -47,7 +47,7 @@ function appendSuccess(e) { function appendError(e) { ok(false, "appendError was called."); - devicestorage_cleanup(); + SimpleTest.finish(); } function addSuccess(e) { @@ -83,8 +83,9 @@ ok(navigator.getDeviceStorage, "Should have getDeviceStorage"); ok(gStorage, "Should get storage from sdcard"); runtest(); +}); + - diff --git a/dom/devicestorage/test/test_fs_basic.html b/dom/devicestorage/test/test_fs_basic.html index 22c5a943ab..a665af4d93 100644 --- a/dom/devicestorage/test/test_fs_basic.html +++ b/dom/devicestorage/test/test_fs_basic.html @@ -21,7 +21,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=910412
 
 
- diff --git a/dom/devicestorage/test/test_fs_createDirectory.html b/dom/devicestorage/test/test_fs_createDirectory.html index 59d6591780..3b573b086f 100644 --- a/dom/devicestorage/test/test_fs_createDirectory.html +++ b/dom/devicestorage/test/test_fs_createDirectory.html @@ -21,7 +21,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=910412
 
 
- diff --git a/dom/devicestorage/test/test_fs_createFile.html b/dom/devicestorage/test/test_fs_createFile.html index 530d2e83f5..2fcce61566 100644 --- a/dom/devicestorage/test/test_fs_createFile.html +++ b/dom/devicestorage/test/test_fs_createFile.html @@ -21,7 +21,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=910412
 
 
- diff --git a/dom/devicestorage/test/test_fs_get.html b/dom/devicestorage/test/test_fs_get.html index b5c572b92f..287f57e1ef 100644 --- a/dom/devicestorage/test/test_fs_get.html +++ b/dom/devicestorage/test/test_fs_get.html @@ -21,7 +21,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=910412
 
 
- diff --git a/dom/devicestorage/test/test_fs_getFilesAndDirectories.html b/dom/devicestorage/test/test_fs_getFilesAndDirectories.html index 84196ec6e2..9dd88bb515 100644 --- a/dom/devicestorage/test/test_fs_getFilesAndDirectories.html +++ b/dom/devicestorage/test/test_fs_getFilesAndDirectories.html @@ -21,7 +21,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=XXX
 
 
- diff --git a/dom/devicestorage/test/test_fs_remove.html b/dom/devicestorage/test/test_fs_remove.html index b495e244f1..29b50c2b76 100644 --- a/dom/devicestorage/test/test_fs_remove.html +++ b/dom/devicestorage/test/test_fs_remove.html @@ -21,7 +21,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=934368
 
 
- diff --git a/dom/devicestorage/test/test_lastModificationFilter.html b/dom/devicestorage/test/test_lastModificationFilter.html index 85bb692d41..00391495e1 100644 --- a/dom/devicestorage/test/test_lastModificationFilter.html +++ b/dom/devicestorage/test/test_lastModificationFilter.html @@ -17,13 +17,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_overrideDir.html b/dom/devicestorage/test/test_overrideDir.html index 19b7ac0b97..0a9d03c7e1 100644 --- a/dom/devicestorage/test/test_overrideDir.html +++ b/dom/devicestorage/test/test_overrideDir.html @@ -17,12 +17,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_overwrite.html b/dom/devicestorage/test/test_overwrite.html index e5dce30a4b..8e6d950bf0 100644 --- a/dom/devicestorage/test/test_overwrite.html +++ b/dom/devicestorage/test/test_overwrite.html @@ -18,28 +18,28 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_sanity.html b/dom/devicestorage/test/test_sanity.html index a5640e6e38..4104befba1 100644 --- a/dom/devicestorage/test/test_sanity.html +++ b/dom/devicestorage/test/test_sanity.html @@ -18,12 +18,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103 Mozilla Bug 717103

 
 
- diff --git a/dom/devicestorage/test/test_storageAreaListener.html b/dom/devicestorage/test/test_storageAreaListener.html index 6f4ffac607..b7b527e461 100644 --- a/dom/devicestorage/test/test_storageAreaListener.html +++ b/dom/devicestorage/test/test_storageAreaListener.html @@ -23,7 +23,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1126694
 
 
diff --git a/dom/devicestorage/test/test_usedSpace.html b/dom/devicestorage/test/test_usedSpace.html index 71532e60c3..4513262310 100644 --- a/dom/devicestorage/test/test_usedSpace.html +++ b/dom/devicestorage/test/test_usedSpace.html @@ -22,23 +22,23 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103
 
 
diff --git a/dom/devicestorage/test/test_watch.html b/dom/devicestorage/test/test_watch.html index 986bdade28..adfca08015 100644 --- a/dom/devicestorage/test/test_watch.html +++ b/dom/devicestorage/test/test_watch.html @@ -22,7 +22,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103
 
 
diff --git a/dom/devicestorage/test/test_watchOther.html b/dom/devicestorage/test/test_watchOther.html index ff1db68730..c729626e62 100644 --- a/dom/devicestorage/test/test_watchOther.html +++ b/dom/devicestorage/test/test_watchOther.html @@ -22,7 +22,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=717103
 
 
diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp index 78fe254590..bab955a531 100644 --- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -49,7 +49,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mFiles) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList) NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragTarget) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragImage) @@ -57,7 +57,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DataTransfer) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mItems) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragImage) @@ -281,11 +281,12 @@ DataTransfer::GetMozUserCancelled(bool* aUserCancelled) FileList* DataTransfer::GetFiles(ErrorResult& aRv) { - return GetFilesInternal(aRv, nsContentUtils::SubjectPrincipal()); + return GetFileListInternal(aRv, nsContentUtils::SubjectPrincipal()); } FileList* -DataTransfer::GetFilesInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal) +DataTransfer::GetFileListInternal(ErrorResult& aRv, + nsIPrincipal* aSubjectPrincipal) { if (mEventMessage != eDrop && mEventMessage != eLegacyDragDrop && @@ -293,14 +294,15 @@ DataTransfer::GetFilesInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal return nullptr; } - if (!mFiles) { - mFiles = new FileList(static_cast(this)); + if (!mFileList) { + mFileList = new FileList(static_cast(this)); uint32_t count = mItems.Length(); for (uint32_t i = 0; i < count; i++) { nsCOMPtr variant; - aRv = GetDataAtInternal(NS_ConvertUTF8toUTF16(kFileMime), i, aSubjectPrincipal, getter_AddRefs(variant)); + aRv = GetDataAtInternal(NS_ConvertUTF8toUTF16(kFileMime), i, + aSubjectPrincipal, getter_AddRefs(variant)); if (aRv.Failed()) { return nullptr; } @@ -338,21 +340,18 @@ DataTransfer::GetFilesInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal MOZ_ASSERT(domFile); } - if (!mFiles->Append(domFile)) { - aRv.Throw(NS_ERROR_FAILURE); - return nullptr; - } + mFileList->Append(domFile); } } - return mFiles; + return mFileList; } NS_IMETHODIMP DataTransfer::GetFiles(nsIDOMFileList** aFileList) { ErrorResult rv; - NS_IF_ADDREF(*aFileList = GetFilesInternal(rv, nsContentUtils::GetSystemPrincipal())); + NS_IF_ADDREF(*aFileList = GetFileListInternal(rv, nsContentUtils::GetSystemPrincipal())); return rv.StealNSResult(); } @@ -874,7 +873,7 @@ DataTransfer::GetFilesAndDirectories(ErrorResult& aRv) return nullptr; } - if (!mFiles) { + if (!mFileList) { GetFiles(aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; @@ -882,40 +881,9 @@ DataTransfer::GetFilesAndDirectories(ErrorResult& aRv) } Sequence filesAndDirsSeq; - - if (mFiles && mFiles->Length()) { - if (!filesAndDirsSeq.SetLength(mFiles->Length(), mozilla::fallible_t())) { - p->MaybeReject(NS_ERROR_OUT_OF_MEMORY); - return p.forget(); - } - - for (uint32_t i = 0; i < mFiles->Length(); ++i) { - if (mFiles->Item(i)->Impl()->IsDirectory()) { -#if defined(ANDROID) || defined(MOZ_B2G) - MOZ_ASSERT(false, - "Directory picking should have been redirected to normal " - "file picking for platforms that don't have a directory " - "picker"); -#endif - nsAutoString path; - mFiles->Item(i)->GetMozFullPathInternal(path, aRv); - if (aRv.Failed()) { - return nullptr; - } - int32_t leafSeparatorIndex = path.RFind(FILE_PATH_SEPARATOR); - nsDependentSubstring dirname = Substring(path, 0, leafSeparatorIndex); - nsDependentSubstring basename = Substring(path, leafSeparatorIndex); - - RefPtr fs = new OSFileSystem(dirname); - fs->Init(parentNode->OwnerDoc()->GetInnerWindow()); - - RefPtr directory = new Directory(fs, basename); - directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive")); - filesAndDirsSeq[i].SetAsDirectory() = directory; - } else { - filesAndDirsSeq[i].SetAsFile() = mFiles->Item(i); - } - } + mFileList->ToSequence(filesAndDirsSeq, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; } p->MaybeResolve(filesAndDirsSeq); diff --git a/dom/events/DataTransfer.h b/dom/events/DataTransfer.h index ef7fa83bdc..08b795bacf 100644 --- a/dom/events/DataTransfer.h +++ b/dom/events/DataTransfer.h @@ -252,7 +252,7 @@ protected: void FillInExternalData(TransferItem& aItem, uint32_t aIndex); - FileList* GetFilesInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal); + FileList* GetFileListInternal(ErrorResult& aRv, nsIPrincipal* aSubjectPrincipal); nsresult GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex, nsIPrincipal* aSubjectPrincipal, nsIVariant** aData); nsresult SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex, @@ -300,8 +300,9 @@ protected: // array of items, each containing an array of format->data pairs nsTArray > mItems; - // array of files, containing only the files present in the dataTransfer - RefPtr mFiles; + // array of files and directories, containing only the files present in the + // dataTransfer + RefPtr mFileList; // the target of the drag. The drag and dragend events will fire at this. nsCOMPtr mDragTarget; diff --git a/dom/filehandle/ActorsChild.cpp b/dom/filehandle/ActorsChild.cpp index 8ade9df479..20885683d6 100644 --- a/dom/filehandle/ActorsChild.cpp +++ b/dom/filehandle/ActorsChild.cpp @@ -318,8 +318,7 @@ ConvertActorToFile(FileHandleBase* aFileHandle, actor->SetMysteryBlobInfo(mutableFile->Name(), mutableFile->Type(), size.get_uint64_t(), - lastModified.get_int64_t(), - BlobDirState::eUnknownIfDir); + lastModified.get_int64_t()); RefPtr blobImpl = actor->GetBlobImpl(); MOZ_ASSERT(blobImpl); diff --git a/dom/filesystem/CreateDirectoryTask.cpp b/dom/filesystem/CreateDirectoryTask.cpp index 47bd2e2033..57a1f432c4 100644 --- a/dom/filesystem/CreateDirectoryTask.cpp +++ b/dom/filesystem/CreateDirectoryTask.cpp @@ -6,7 +6,6 @@ #include "CreateDirectoryTask.h" -#include "DOMError.h" #include "mozilla/dom/Directory.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" @@ -17,33 +16,73 @@ namespace mozilla { namespace dom { -CreateDirectoryTask::CreateDirectoryTask(FileSystemBase* aFileSystem, - const nsAString& aPath, - ErrorResult& aRv) - : FileSystemTaskBase(aFileSystem) - , mTargetRealPath(aPath) +/* static */ already_AddRefed +CreateDirectoryTask::Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); + + RefPtr task = + new CreateDirectoryTask(aFileSystem, aTargetPath); + + // aTargetPath can be null. In this case SetError will be called. + nsCOMPtr globalObject = - do_QueryInterface(aFileSystem->GetWindow()); - if (!globalObject) { - return; + do_QueryInterface(aFileSystem->GetParentObject()); + if (NS_WARN_IF(!globalObject)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; } - mPromise = Promise::Create(globalObject, aRv); + + task->mPromise = Promise::Create(globalObject, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return task.forget(); } -CreateDirectoryTask::CreateDirectoryTask( - FileSystemBase* aFileSystem, - const FileSystemCreateDirectoryParams& aParam, - FileSystemRequestParent* aParent) - : FileSystemTaskBase(aFileSystem, aParam, aParent) +/* static */ already_AddRefed +CreateDirectoryTask::Create(FileSystemBase* aFileSystem, + const FileSystemCreateDirectoryParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) { - MOZ_ASSERT(XRE_IsParentProcess(), - "Only call from parent process!"); + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new CreateDirectoryTask(aFileSystem, aParam, aParent); + + NS_ConvertUTF16toUTF8 path(aParam.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return task.forget(); +} + +CreateDirectoryTask::CreateDirectoryTask(FileSystemBase* aFileSystem, + nsIFile* aTargetPath) + : FileSystemTaskBase(aFileSystem) + , mTargetPath(aTargetPath) +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); +} + +CreateDirectoryTask::CreateDirectoryTask(FileSystemBase* aFileSystem, + const FileSystemCreateDirectoryParams& aParam, + FileSystemRequestParent* aParent) + : FileSystemTaskBase(aFileSystem, aParam, aParent) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - mTargetRealPath = aParam.realPath(); } CreateDirectoryTask::~CreateDirectoryTask() @@ -60,25 +99,44 @@ CreateDirectoryTask::GetPromise() } FileSystemParams -CreateDirectoryTask::GetRequestParams(const nsString& aFileSystem) const +CreateDirectoryTask::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - return FileSystemCreateDirectoryParams(aFileSystem, mTargetRealPath); + + nsAutoString path; + aRv = mTargetPath->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return FileSystemCreateDirectoryParams(); + } + + return FileSystemCreateDirectoryParams(aSerializedDOMPath, path); } FileSystemResponseValue -CreateDirectoryTask::GetSuccessRequestResult() const +CreateDirectoryTask::GetSuccessRequestResult(ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - return FileSystemDirectoryResponse(mTargetRealPath); + + nsAutoString path; + aRv = mTargetPath->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return FileSystemDirectoryResponse(); + } + + return FileSystemDirectoryResponse(path); } void -CreateDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue) +CreateDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); FileSystemDirectoryResponse r = aValue; - mTargetRealPath = r.realPath(); + + NS_ConvertUTF16toUTF8 path(r.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(mTargetPath)); + NS_WARN_IF(aRv.Failed()); } nsresult @@ -92,13 +150,8 @@ CreateDirectoryTask::Work() return NS_ERROR_FAILURE; } - nsCOMPtr file = mFileSystem->GetLocalFile(mTargetRealPath); - if (!file) { - return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - } - bool fileExists; - nsresult rv = file->Exists(&fileExists); + nsresult rv = mTargetPath->Exists(&fileExists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -107,8 +160,12 @@ CreateDirectoryTask::Work() return NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR; } - rv = file->Create(nsIFile::DIRECTORY_TYPE, 0770); - return rv; + rv = mTargetPath->Create(nsIFile::DIRECTORY_TYPE, 0770); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; } void @@ -121,13 +178,16 @@ CreateDirectoryTask::HandlerCallback() } if (HasError()) { - RefPtr domError = new DOMError(mFileSystem->GetWindow(), - mErrorValue); - mPromise->MaybeRejectBrokenly(domError); + mPromise->MaybeReject(mErrorValue); mPromise = nullptr; return; } - RefPtr dir = new Directory(mFileSystem, mTargetRealPath); + RefPtr dir = Directory::Create(mFileSystem->GetParentObject(), + mTargetPath, + Directory::eNotDOMRootDirectory, + mFileSystem); + MOZ_ASSERT(dir); + mPromise->MaybeResolve(dir); mPromise = nullptr; } diff --git a/dom/filesystem/CreateDirectoryTask.h b/dom/filesystem/CreateDirectoryTask.h index fb342c94ba..837730629d 100644 --- a/dom/filesystem/CreateDirectoryTask.h +++ b/dom/filesystem/CreateDirectoryTask.h @@ -16,16 +16,19 @@ namespace dom { class Promise; -class CreateDirectoryTask final - : public FileSystemTaskBase +class CreateDirectoryTask final : public FileSystemTaskBase { public: - CreateDirectoryTask(FileSystemBase* aFileSystem, - const nsAString& aPath, - ErrorResult& aRv); - CreateDirectoryTask(FileSystemBase* aFileSystem, - const FileSystemCreateDirectoryParams& aParam, - FileSystemRequestParent* aParent); + static already_AddRefed + Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + ErrorResult& aRv); + + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemCreateDirectoryParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); virtual ~CreateDirectoryTask(); @@ -38,13 +41,15 @@ public: protected: virtual FileSystemParams - GetRequestParams(const nsString& aFileSystem) const override; + GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const override; virtual FileSystemResponseValue - GetSuccessRequestResult() const override; + GetSuccessRequestResult(ErrorResult& aRv) const override; virtual void - SetSuccessRequestResult(const FileSystemResponseValue& aValue) override; + SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) override; virtual nsresult Work() override; @@ -53,8 +58,15 @@ protected: HandlerCallback() override; private: + CreateDirectoryTask(FileSystemBase* aFileSystem, + nsIFile* aTargetPath); + + CreateDirectoryTask(FileSystemBase* aFileSystem, + const FileSystemCreateDirectoryParams& aParam, + FileSystemRequestParent* aParent); + RefPtr mPromise; - nsString mTargetRealPath; + nsCOMPtr mTargetPath; }; } // namespace dom diff --git a/dom/filesystem/CreateFileTask.cpp b/dom/filesystem/CreateFileTask.cpp index 1dbbcea7cb..4276630572 100644 --- a/dom/filesystem/CreateFileTask.cpp +++ b/dom/filesystem/CreateFileTask.cpp @@ -8,7 +8,6 @@ #include -#include "DOMError.h" #include "mozilla/Preferences.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" @@ -26,34 +25,104 @@ namespace dom { uint32_t CreateFileTask::sOutputBufferSize = 0; -CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem, - const nsAString& aPath, - Blob* aBlobData, - InfallibleTArray& aArrayData, - bool replace, - ErrorResult& aRv) - : FileSystemTaskBase(aFileSystem) - , mTargetRealPath(aPath) - , mReplace(replace) +/* static */ already_AddRefed +CreateFileTask::Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Blob* aBlobData, + InfallibleTArray& aArrayData, + bool aReplace, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - GetOutputBufferSize(); + + RefPtr task = + new CreateFileTask(aFileSystem, aTargetPath, aReplace); + + // aTargetPath can be null. In this case SetError will be called. + + task->GetOutputBufferSize(); + if (aBlobData) { if (XRE_IsParentProcess()) { - aBlobData->GetInternalStream(getter_AddRefs(mBlobStream), aRv); - NS_WARN_IF(aRv.Failed()); + aBlobData->GetInternalStream(getter_AddRefs(task->mBlobStream), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } } else { - mBlobData = aBlobData; + task->mBlobData = aBlobData; } } - mArrayData.SwapElements(aArrayData); + + task->mArrayData.SwapElements(aArrayData); + nsCOMPtr globalObject = - do_QueryInterface(aFileSystem->GetWindow()); - if (!globalObject) { - return; + do_QueryInterface(aFileSystem->GetParentObject()); + if (NS_WARN_IF(!globalObject)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; } - mPromise = Promise::Create(globalObject, aRv); + + task->mPromise = Promise::Create(globalObject, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return task.forget(); +} + +/* static */ already_AddRefed +CreateFileTask::Create(FileSystemBase* aFileSystem, + const FileSystemCreateFileParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new CreateFileTask(aFileSystem, aParam, aParent); + + task->GetOutputBufferSize(); + + NS_ConvertUTF16toUTF8 path(aParam.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + task->mReplace = aParam.replace(); + + auto& data = aParam.data(); + + if (data.type() == FileSystemFileDataValue::TArrayOfuint8_t) { + task->mArrayData = data; + return task.forget(); + } + + BlobParent* bp = static_cast(static_cast(data)); + RefPtr blobImpl = bp->GetBlobImpl(); + MOZ_ASSERT(blobImpl, "blobData should not be null."); + + ErrorResult rv; + blobImpl->GetInternalStream(getter_AddRefs(task->mBlobStream), rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + } + + return task.forget(); +} + +CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + bool aReplace) + : FileSystemTaskBase(aFileSystem) + , mTargetPath(aTargetPath) + , mReplace(aReplace) +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); } CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem, @@ -62,32 +131,9 @@ CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem, : FileSystemTaskBase(aFileSystem, aParam, aParent) , mReplace(false) { - MOZ_ASSERT(XRE_IsParentProcess(), - "Only call from parent process!"); + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - GetOutputBufferSize(); - - mTargetRealPath = aParam.realPath(); - - mReplace = aParam.replace(); - - auto& data = aParam.data(); - - if (data.type() == FileSystemFileDataValue::TArrayOfuint8_t) { - mArrayData = data; - return; - } - - BlobParent* bp = static_cast(static_cast(data)); - RefPtr blobImpl = bp->GetBlobImpl(); - MOZ_ASSERT(blobImpl, "blobData should not be null."); - - ErrorResult rv; - blobImpl->GetInternalStream(getter_AddRefs(mBlobStream), rv); - if (NS_WARN_IF(rv.Failed())) { - rv.SuppressException(); - } } CreateFileTask::~CreateFileTask() @@ -108,16 +154,22 @@ CreateFileTask::GetPromise() } FileSystemParams -CreateFileTask::GetRequestParams(const nsString& aFileSystem) const +CreateFileTask::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); FileSystemCreateFileParams param; - param.filesystem() = aFileSystem; - param.realPath() = mTargetRealPath; + param.filesystem() = aSerializedDOMPath; + + aRv = mTargetPath->GetPath(param.realPath()); + if (NS_WARN_IF(aRv.Failed())) { + return param; + } + param.replace() = mReplace; if (mBlobData) { - BlobChild* actor - = ContentChild::GetSingleton()->GetOrCreateActorForBlob(mBlobData); + BlobChild* actor = + ContentChild::GetSingleton()->GetOrCreateActorForBlob(mBlobData); if (actor) { param.data() = actor; } @@ -128,7 +180,7 @@ CreateFileTask::GetRequestParams(const nsString& aFileSystem) const } FileSystemResponseValue -CreateFileTask::GetSuccessRequestResult() const +CreateFileTask::GetSuccessRequestResult(ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); BlobParent* actor = GetBlobParent(mTargetBlobImpl); @@ -141,7 +193,8 @@ CreateFileTask::GetSuccessRequestResult() const } void -CreateFileTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue) +CreateFileTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); FileSystemFileResponse r = aValue; @@ -152,7 +205,7 @@ CreateFileTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue) nsresult CreateFileTask::Work() { - class AutoClose + class MOZ_RAII AutoClose final { public: explicit AutoClose(nsIOutputStream* aStream) @@ -165,6 +218,7 @@ CreateFileTask::Work() { mStream->Close(); } + private: nsCOMPtr mStream; }; @@ -177,24 +231,19 @@ CreateFileTask::Work() return NS_ERROR_FAILURE; } - nsCOMPtr file = mFileSystem->GetLocalFile(mTargetRealPath); - if (!file) { - return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - } - - if (!mFileSystem->IsSafeFile(file)) { + if (!mFileSystem->IsSafeFile(mTargetPath)) { return NS_ERROR_DOM_SECURITY_ERR; } bool exists = false; - nsresult rv = file->Exists(&exists); + nsresult rv = mTargetPath->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (exists) { bool isFile = false; - rv = file->IsFile(&isFile); + rv = mTargetPath->IsFile(&isFile); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -208,19 +257,19 @@ CreateFileTask::Work() } // Remove the old file before creating. - rv = file->Remove(false); + rv = mTargetPath->Remove(false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } - rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0600); + rv = mTargetPath->Create(nsIFile::NORMAL_FILE_TYPE, 0600); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr outputStream; - rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file); + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mTargetPath); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -263,7 +312,7 @@ CreateFileTask::Work() return NS_ERROR_FAILURE; } - mTargetBlobImpl = new BlobImplFile(file); + mTargetBlobImpl = new BlobImplFile(mTargetPath); return NS_OK; } @@ -282,7 +331,7 @@ CreateFileTask::Work() return NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR; } - mTargetBlobImpl = new BlobImplFile(file); + mTargetBlobImpl = new BlobImplFile(mTargetPath); return NS_OK; } @@ -297,15 +346,14 @@ CreateFileTask::HandlerCallback() } if (HasError()) { - RefPtr domError = new DOMError(mFileSystem->GetWindow(), - mErrorValue); - mPromise->MaybeRejectBrokenly(domError); + mPromise->MaybeReject(mErrorValue); mPromise = nullptr; mBlobData = nullptr; return; } - RefPtr blob = Blob::Create(mFileSystem->GetWindow(), mTargetBlobImpl); + RefPtr blob = Blob::Create(mFileSystem->GetParentObject(), + mTargetBlobImpl); mPromise->MaybeResolve(blob); mPromise = nullptr; mBlobData = nullptr; diff --git a/dom/filesystem/CreateFileTask.h b/dom/filesystem/CreateFileTask.h index 7adf49f14f..fc0ba14ceb 100644 --- a/dom/filesystem/CreateFileTask.h +++ b/dom/filesystem/CreateFileTask.h @@ -20,19 +20,22 @@ class Blob; class BlobImpl; class Promise; -class CreateFileTask final - : public FileSystemTaskBase +class CreateFileTask final : public FileSystemTaskBase { public: - CreateFileTask(FileSystemBase* aFileSystem, - const nsAString& aPath, - Blob* aBlobData, - InfallibleTArray& aArrayData, - bool replace, - ErrorResult& aRv); - CreateFileTask(FileSystemBase* aFileSystem, - const FileSystemCreateFileParams& aParam, - FileSystemRequestParent* aParent); + static already_AddRefed + Create(FileSystemBase* aFileSystem, + nsIFile* aFile, + Blob* aBlobData, + InfallibleTArray& aArrayData, + bool replace, + ErrorResult& aRv); + + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemCreateFileParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); virtual ~CreateFileTask(); @@ -45,13 +48,15 @@ public: protected: virtual FileSystemParams - GetRequestParams(const nsString& aFileSystem) const override; + GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const override; virtual FileSystemResponseValue - GetSuccessRequestResult() const override; + GetSuccessRequestResult(ErrorResult& aRv) const override; virtual void - SetSuccessRequestResult(const FileSystemResponseValue& aValue) override; + SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) override; virtual nsresult Work() override; @@ -60,12 +65,20 @@ protected: HandlerCallback() override; private: + CreateFileTask(FileSystemBase* aFileSystem, + nsIFile* aFile, + bool aReplace); + + CreateFileTask(FileSystemBase* aFileSystem, + const FileSystemCreateFileParams& aParam, + FileSystemRequestParent* aParent); + void GetOutputBufferSize() const; static uint32_t sOutputBufferSize; RefPtr mPromise; - nsString mTargetRealPath; + nsCOMPtr mTargetPath; // Not thread-safe and should be released on main thread. RefPtr mBlobData; diff --git a/dom/filesystem/DeviceStorageFileSystem.cpp b/dom/filesystem/DeviceStorageFileSystem.cpp index 05be084194..5c89a8c95e 100644 --- a/dom/filesystem/DeviceStorageFileSystem.cpp +++ b/dom/filesystem/DeviceStorageFileSystem.cpp @@ -21,9 +21,8 @@ namespace mozilla { namespace dom { -DeviceStorageFileSystem::DeviceStorageFileSystem( - const nsAString& aStorageType, - const nsAString& aStorageName) +DeviceStorageFileSystem::DeviceStorageFileSystem(const nsAString& aStorageType, + const nsAString& aStorageName) : mWindowId(0) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); @@ -31,12 +30,6 @@ DeviceStorageFileSystem::DeviceStorageFileSystem( mStorageType = aStorageType; mStorageName = aStorageName; - // Generate the string representation of the file system. - mString.AppendLiteral("devicestorage-"); - mString.Append(mStorageType); - mString.Append('-'); - mString.Append(mStorageName); - mRequiresPermissionChecks = !mozilla::Preferences::GetBool("device.storage.prompt.testing", false); @@ -46,19 +39,16 @@ DeviceStorageFileSystem::DeviceStorageFileSystem( NS_WARN_IF(NS_FAILED(rv)); // Get the local path of the file system root. - // Since the child process is not allowed to access the file system, we only - // do this from the parent process. - if (!XRE_IsParentProcess()) { - return; - } nsCOMPtr rootFile; DeviceStorageFile::GetRootDirectoryForType(aStorageType, aStorageName, getter_AddRefs(rootFile)); NS_WARN_IF(!rootFile || NS_FAILED(rootFile->GetPath(mLocalRootPath))); - FileSystemUtils::LocalPathToNormalizedPath(mLocalRootPath, - mNormalizedLocalRootPath); + + if (!XRE_IsParentProcess()) { + return; + } // DeviceStorageTypeChecker is a singleton object and must be initialized on // the main thread. We initialize it here so that we can use it on the worker @@ -72,6 +62,17 @@ DeviceStorageFileSystem::~DeviceStorageFileSystem() { } +already_AddRefed +DeviceStorageFileSystem::Clone() +{ + RefPtr fs = + new DeviceStorageFileSystem(mStorageType, mStorageName); + + fs->mWindowId = mWindowId; + + return fs.forget(); +} + void DeviceStorageFileSystem::Init(nsDOMDeviceStorage* aDeviceStorage) { @@ -89,13 +90,13 @@ DeviceStorageFileSystem::Shutdown() mShutdown = true; } -nsPIDOMWindow* -DeviceStorageFileSystem::GetWindow() const +nsISupports* +DeviceStorageFileSystem::GetParentObject() const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId); MOZ_ASSERT_IF(!mShutdown, window); - return window; + return ToSupports(window); } void @@ -111,12 +112,15 @@ DeviceStorageFileSystem::IsSafeFile(nsIFile* aFile) const "Should be on parent process!"); MOZ_ASSERT(aFile); - // Check if this file belongs to this storage. - nsAutoString path; - if (NS_FAILED(aFile->GetPath(path))) { + nsCOMPtr rootPath; + nsresult rv = NS_NewLocalFile(GetLocalRootPath(), false, + getter_AddRefs(rootPath)); + if (NS_WARN_IF(NS_FAILED(rv))) { return false; } - if (!LocalPathToRealPath(path, path)) { + + // Check if this file belongs to this storage. + if (NS_WARN_IF(!FileSystemUtils::IsDescendantPath(rootPath, aFile))) { return false; } @@ -132,10 +136,32 @@ DeviceStorageFileSystem::IsSafeDirectory(Directory* aDir) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aDir); - RefPtr fs = aDir->GetFileSystem(); - MOZ_ASSERT(fs); + + ErrorResult rv; + RefPtr fs = aDir->GetFileSystem(rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + nsAutoString fsSerialization; + fs->SerializeDOMPath(fsSerialization); + + nsAutoString thisSerialization; + SerializeDOMPath(thisSerialization); + // Check if the given directory is from this storage. - return fs->ToString() == mString; + return fsSerialization == thisSerialization; +} + +void +DeviceStorageFileSystem::SerializeDOMPath(nsAString& aString) const +{ + // Generate the string representation of the file system. + aString.AssignLiteral("devicestorage-"); + aString.Append(mStorageType); + aString.Append('-'); + aString.Append(mStorageName); } } // namespace dom diff --git a/dom/filesystem/DeviceStorageFileSystem.h b/dom/filesystem/DeviceStorageFileSystem.h index 1e46fdf199..32168bc825 100644 --- a/dom/filesystem/DeviceStorageFileSystem.h +++ b/dom/filesystem/DeviceStorageFileSystem.h @@ -27,11 +27,14 @@ public: // Overrides FileSystemBase + virtual already_AddRefed + Clone() override; + virtual void Shutdown() override; - virtual nsPIDOMWindow* - GetWindow() const override; + virtual nsISupports* + GetParentObject() const override; virtual void GetRootName(nsAString& aRetval) const override; @@ -41,6 +44,10 @@ public: virtual bool IsSafeDirectory(Directory* aDir) const override; + + virtual void + SerializeDOMPath(nsAString& aSerializedString) const override; + private: virtual ~DeviceStorageFileSystem(); diff --git a/dom/filesystem/Directory.cpp b/dom/filesystem/Directory.cpp index 4d746a9541..edab5908f5 100644 --- a/dom/filesystem/Directory.cpp +++ b/dom/filesystem/Directory.cpp @@ -18,6 +18,7 @@ #include "mozilla/dom/DirectoryBinding.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" +#include "mozilla/dom/OSFileSystem.h" // Resolve the name collision of Microsoft's API name with macros defined in // Windows header files. Undefine the macro of CreateDirectory to avoid @@ -34,15 +35,70 @@ namespace mozilla { namespace dom { +namespace { + +bool +TokenizerIgnoreNothing(char16_t /* aChar */) +{ + return false; +} + +bool +IsValidRelativeDOMPath(const nsString& aPath, nsTArray& aParts) +{ + // We don't allow empty relative path to access the root. + if (aPath.IsEmpty()) { + return false; + } + + // Leading and trailing "/" are not allowed. + if (aPath.First() == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR || + aPath.Last() == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR) { + return false; + } + + NS_NAMED_LITERAL_STRING(kCurrentDir, "."); + NS_NAMED_LITERAL_STRING(kParentDir, ".."); + + // Split path and check each path component. + nsCharSeparatedTokenizerTemplate + tokenizer(aPath, FILESYSTEM_DOM_PATH_SEPARATOR_CHAR); + + while (tokenizer.hasMoreTokens()) { + nsDependentSubstring pathComponent = tokenizer.nextToken(); + // The path containing empty components, such as "foo//bar", is invalid. + // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar", + // to walk up the directory. + if (pathComponent.IsEmpty() || + pathComponent.Equals(kCurrentDir) || + pathComponent.Equals(kParentDir)) { + return false; + } + + aParts.AppendElement(pathComponent); + } + + return true; +} + +} // anonymous namespace + NS_IMPL_CYCLE_COLLECTION_CLASS(Directory) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Directory) - tmp->mFileSystem->Unlink(); + if (tmp->mFileSystem) { + tmp->mFileSystem->Unlink(); + tmp->mFileSystem = nullptr; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Directory) - tmp->mFileSystem->Traverse(cb); + if (tmp->mFileSystem) { + tmp->mFileSystem->Traverse(cb); + } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -59,33 +115,73 @@ NS_INTERFACE_MAP_END already_AddRefed Directory::GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv) { - RefPtr task = new GetFileOrDirectoryTask( - aFileSystem, EmptyString(), true, aRv); - if (aRv.Failed()) { + MOZ_ASSERT(aFileSystem); + + nsCOMPtr path; + aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(aFileSystem->GetLocalRootPath()), + true, getter_AddRefs(path)); + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } + + RefPtr task = + GetFileOrDirectoryTask::Create(aFileSystem, path, eDOMRootDirectory, true, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + FileSystemPermissionRequest::RequestForTask(task); return task->GetPromise(); } -Directory::Directory(FileSystemBase* aFileSystem, - const nsAString& aPath) - : mFileSystem(aFileSystem) - , mPath(aPath) +/* static */ already_AddRefed +Directory::Create(nsISupports* aParent, nsIFile* aFile, + DirectoryType aType, FileSystemBase* aFileSystem) { - MOZ_ASSERT(aFileSystem, "aFileSystem should not be null."); - // Remove the trailing "/". - mPath.Trim(FILESYSTEM_DOM_PATH_SEPARATOR, false, true); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aParent); + MOZ_ASSERT(aFile); + +#ifdef DEBUG + bool isDir; + nsresult rv = aFile->IsDirectory(&isDir); + MOZ_ASSERT(NS_SUCCEEDED(rv) && isDir); +#endif + + RefPtr directory = + new Directory(aParent, aFile, aType, aFileSystem); + return directory.forget(); +} + +Directory::Directory(nsISupports* aParent, + nsIFile* aFile, + DirectoryType aType, + FileSystemBase* aFileSystem) + : mParent(aParent) + , mFile(aFile) + , mType(aType) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aFile); + + // aFileSystem can be null. In this case we create a OSFileSystem when needed. + if (aFileSystem) { + // More likely, this is a OSFileSystem. This object keeps a reference of + // mParent but it's not cycle collectable and to avoid manual + // addref/release, it's better to have 1 object per directory. For this + // reason we clone it here. + mFileSystem = aFileSystem->Clone(); + } } Directory::~Directory() { } -nsPIDOMWindow* +nsISupports* Directory::GetParentObject() const { - return mFileSystem->GetWindow(); + return mParent; } JSObject* @@ -95,25 +191,28 @@ Directory::WrapObject(JSContext* aCx, JS::Handle aGivenProto) } void -Directory::GetName(nsAString& aRetval) const +Directory::GetName(nsAString& aRetval, ErrorResult& aRv) { aRetval.Truncate(); - if (mPath.IsEmpty()) { - mFileSystem->GetRootName(aRetval); + if (mType == eDOMRootDirectory) { + RefPtr fs = GetFileSystem(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + fs->GetRootName(aRetval); return; } - aRetval = Substring(mPath, - mPath.RFindChar(FileSystemUtils::kSeparatorChar) + 1); + aRv = mFile->GetLeafName(aRetval); + NS_WARN_IF(aRv.Failed()); } already_AddRefed Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions, ErrorResult& aRv) { - nsresult error = NS_OK; - nsAutoString realPath; RefPtr blobData; InfallibleTArray arrayData; bool replace = (aOptions.mIfExists == CreateIfExistsMode::Replace); @@ -138,15 +237,20 @@ Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions, } } - if (!DOMPathToRealPath(aPath, realPath)) { - error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; + nsCOMPtr realPath; + nsresult error = DOMPathToRealPath(aPath, getter_AddRefs(realPath)); + + RefPtr fs = GetFileSystem(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; } RefPtr task = - new CreateFileTask(mFileSystem, realPath, blobData, arrayData, replace, aRv); - if (aRv.Failed()) { + CreateFileTask::Create(fs, realPath, blobData, arrayData, replace, aRv); + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } + task->SetError(error); FileSystemPermissionRequest::RequestForTask(task); return task->GetPromise(); @@ -155,16 +259,20 @@ Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions, already_AddRefed Directory::CreateDirectory(const nsAString& aPath, ErrorResult& aRv) { - nsresult error = NS_OK; - nsAutoString realPath; - if (!DOMPathToRealPath(aPath, realPath)) { - error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - } - RefPtr task = new CreateDirectoryTask( - mFileSystem, realPath, aRv); - if (aRv.Failed()) { + nsCOMPtr realPath; + nsresult error = DOMPathToRealPath(aPath, getter_AddRefs(realPath)); + + RefPtr fs = GetFileSystem(aRv); + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } + + RefPtr task = + CreateDirectoryTask::Create(fs, realPath, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + task->SetError(error); FileSystemPermissionRequest::RequestForTask(task); return task->GetPromise(); @@ -173,16 +281,21 @@ Directory::CreateDirectory(const nsAString& aPath, ErrorResult& aRv) already_AddRefed Directory::Get(const nsAString& aPath, ErrorResult& aRv) { - nsresult error = NS_OK; - nsAutoString realPath; - if (!DOMPathToRealPath(aPath, realPath)) { - error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - } - RefPtr task = new GetFileOrDirectoryTask( - mFileSystem, realPath, false, aRv); - if (aRv.Failed()) { + nsCOMPtr realPath; + nsresult error = DOMPathToRealPath(aPath, getter_AddRefs(realPath)); + + RefPtr fs = GetFileSystem(aRv); + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } + + RefPtr task = + GetFileOrDirectoryTask::Create(fs, realPath, eNotDOMRootDirectory, false, + aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + task->SetError(error); FileSystemPermissionRequest::RequestForTask(task); return task->GetPromise(); @@ -205,30 +318,33 @@ Directory::RemoveInternal(const StringOrFileOrDirectory& aPath, bool aRecursive, ErrorResult& aRv) { nsresult error = NS_OK; - nsAutoString realPath; + nsCOMPtr realPath; RefPtr blob; // Check and get the target path. + RefPtr fs = GetFileSystem(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + if (aPath.IsFile()) { blob = aPath.GetAsFile().Impl(); } else if (aPath.IsString()) { - if (!DOMPathToRealPath(aPath.GetAsString(), realPath)) { - error = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - } - } else if (!mFileSystem->IsSafeDirectory(&aPath.GetAsDirectory())) { + error = DOMPathToRealPath(aPath.GetAsString(), getter_AddRefs(realPath)); + } else if (!fs->IsSafeDirectory(&aPath.GetAsDirectory())) { error = NS_ERROR_DOM_SECURITY_ERR; } else { - realPath = aPath.GetAsDirectory().mPath; + realPath = aPath.GetAsDirectory().mFile; // The target must be a descendant of this directory. - if (!FileSystemUtils::IsDescendantPath(mPath, realPath)) { + if (!FileSystemUtils::IsDescendantPath(mFile, realPath)) { error = NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR; } } - RefPtr task = new RemoveTask(mFileSystem, mPath, blob, realPath, - aRecursive, aRv); - if (aRv.Failed()) { + RefPtr task = + RemoveTask::Create(fs, mFile, blob, realPath, aRecursive, aRv); + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } task->SetError(error); @@ -237,23 +353,38 @@ Directory::RemoveInternal(const StringOrFileOrDirectory& aPath, bool aRecursive, } void -Directory::GetPath(nsAString& aRetval) const +Directory::GetPath(nsAString& aRetval, ErrorResult& aRv) { - if (mPath.IsEmpty()) { - // The Directory ctor removes any trailing '/'; this is the root directory. - aRetval.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR); + if (mType == eDOMRootDirectory) { + aRetval.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); } else { - aRetval = mPath; + // TODO: this should be a bit different... + GetName(aRetval, aRv); } } -already_AddRefed -Directory::GetFilesAndDirectories() +nsresult +Directory::GetFullRealPath(nsAString& aPath) { - ErrorResult rv; + nsresult rv = mFile->GetPath(aPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +already_AddRefed +Directory::GetFilesAndDirectories(ErrorResult& aRv) +{ + RefPtr fs = GetFileSystem(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + RefPtr task = - new GetDirectoryListingTask(mFileSystem, mPath, mFilters, rv); - if (NS_WARN_IF(rv.Failed())) { + GetDirectoryListingTask::Create(fs, mFile, mType, mFilters, aRv); + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -268,16 +399,38 @@ Directory::SetContentFilters(const nsAString& aFilters) } FileSystemBase* -Directory::GetFileSystem() const +Directory::GetFileSystem(ErrorResult& aRv) { - return mFileSystem.get(); + if (!mFileSystem) { + nsCOMPtr parent; + aRv = mFile->GetParent(getter_AddRefs(parent)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Parent can be null if mFile is pointing to the top directory. + if (!parent) { + parent = mFile; + } + + nsAutoString path; + aRv = parent->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + RefPtr fs = new OSFileSystem(path); + fs->Init(mParent); + + mFileSystem = fs; + } + + return mFileSystem; } -bool -Directory::DOMPathToRealPath(const nsAString& aPath, nsAString& aRealPath) const +nsresult +Directory::DOMPathToRealPath(const nsAString& aPath, nsIFile** aFile) const { - aRealPath.Truncate(); - nsString relativePath; relativePath = aPath; @@ -285,49 +438,26 @@ Directory::DOMPathToRealPath(const nsAString& aPath, nsAString& aRealPath) const static const char kWhitespace[] = "\b\t\r\n "; relativePath.Trim(kWhitespace); - if (!IsValidRelativePath(relativePath)) { - return false; + nsTArray parts; + if (!IsValidRelativeDOMPath(relativePath, parts)) { + return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; } - aRealPath = mPath + NS_LITERAL_STRING(FILESYSTEM_DOM_PATH_SEPARATOR) + - relativePath; - - return true; -} - -// static -bool -Directory::IsValidRelativePath(const nsString& aPath) -{ - // We don't allow empty relative path to access the root. - if (aPath.IsEmpty()) { - return false; + nsCOMPtr file; + nsresult rv = mFile->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } - // Leading and trailing "/" are not allowed. - if (aPath.First() == FileSystemUtils::kSeparatorChar || - aPath.Last() == FileSystemUtils::kSeparatorChar) { - return false; - } - - NS_NAMED_LITERAL_STRING(kCurrentDir, "."); - NS_NAMED_LITERAL_STRING(kParentDir, ".."); - - // Split path and check each path component. - nsCharSeparatedTokenizer tokenizer(aPath, FileSystemUtils::kSeparatorChar); - while (tokenizer.hasMoreTokens()) { - nsDependentSubstring pathComponent = tokenizer.nextToken(); - // The path containing empty components, such as "foo//bar", is invalid. - // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar", - // to walk up the directory. - if (pathComponent.IsEmpty() || - pathComponent.Equals(kCurrentDir) || - pathComponent.Equals(kParentDir)) { - return false; + for (uint32_t i = 0; i < parts.Length(); ++i) { + rv = file->AppendRelativePath(parts[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } } - return true; + file.forget(aFile); + return NS_OK; } } // namespace dom diff --git a/dom/filesystem/Directory.h b/dom/filesystem/Directory.h index 6617e00f35..5e6ac2deef 100644 --- a/dom/filesystem/Directory.h +++ b/dom/filesystem/Directory.h @@ -13,7 +13,6 @@ #include "mozilla/dom/File.h" #include "nsAutoPtr.h" #include "nsCycleCollectionParticipant.h" -#include "nsPIDOMWindow.h" #include "nsWrapperCache.h" // Resolve the name collision of Microsoft's API name with macros defined in @@ -41,25 +40,48 @@ class Directory final , public nsWrapperCache { public: + struct BlobImplOrDirectoryPath + { + RefPtr mBlobImpl; + nsString mDirectoryPath; + + enum { + eBlobImpl, + eDirectoryPath + } mType; + }; + NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Directory) -public: static already_AddRefed GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv); - Directory(FileSystemBase* aFileSystem, const nsAString& aPath); + enum DirectoryType { + // When a directory is selected using a HTMLInputElement, that will be the + // DOM root directory and its name will be '/'. All the sub directory will + // be called with they real name. We use this enum to mark what we must + // consider the '/' of this DOM filesystem. + eDOMRootDirectory, + + // All the sub directories of the '/' will be marked using this other value. + eNotDOMRootDirectory + }; + + static already_AddRefed + Create(nsISupports* aParent, nsIFile* aDirectory, + DirectoryType aType, FileSystemBase* aFileSystem = 0); // ========= Begin WebIDL bindings. =========== - nsPIDOMWindow* + nsISupports* GetParentObject() const; virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; void - GetName(nsAString& aRetval) const; + GetName(nsAString& aRetval, ErrorResult& aRv); already_AddRefed CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions, @@ -80,10 +102,13 @@ public: // From https://microsoftedge.github.io/directory-upload/proposal.html#directory-interface : void - GetPath(nsAString& aRetval) const; + GetPath(nsAString& aRetval, ErrorResult& aRv); + + nsresult + GetFullRealPath(nsAString& aPath); already_AddRefed - GetFilesAndDirectories(); + GetFilesAndDirectories(ErrorResult& aRv); // =========== End WebIDL bindings.============ @@ -113,26 +138,34 @@ public: SetContentFilters(const nsAString& aFilters); FileSystemBase* - GetFileSystem() const; -private: - ~Directory(); + GetFileSystem(ErrorResult& aRv); - static bool - IsValidRelativePath(const nsString& aPath); + DirectoryType Type() const + { + return mType; + } + +private: + Directory(nsISupports* aParent, + nsIFile* aFile, DirectoryType aType, + FileSystemBase* aFileSystem = nullptr); + ~Directory(); /* * Convert relative DOM path to the absolute real path. - * @return true if succeed. false if the DOM path is invalid. */ - bool - DOMPathToRealPath(const nsAString& aPath, nsAString& aRealPath) const; + nsresult + DOMPathToRealPath(const nsAString& aPath, nsIFile** aFile) const; already_AddRefed RemoveInternal(const StringOrFileOrDirectory& aPath, bool aRecursive, ErrorResult& aRv); + nsCOMPtr mParent; RefPtr mFileSystem; - nsString mPath; + nsCOMPtr mFile; + DirectoryType mType; + nsString mFilters; }; diff --git a/dom/filesystem/FileSystemBase.cpp b/dom/filesystem/FileSystemBase.cpp index dd8aa6b94d..3d6e4529ae 100644 --- a/dom/filesystem/FileSystemBase.cpp +++ b/dom/filesystem/FileSystemBase.cpp @@ -15,7 +15,7 @@ namespace dom { // static already_AddRefed -FileSystemBase::FromString(const nsAString& aString) +FileSystemBase::DeserializeDOMPath(const nsAString& aString) { if (StringBeginsWith(aString, NS_LITERAL_STRING("devicestorage-"))) { // The string representation of devicestorage file system is of the format: @@ -38,6 +38,7 @@ FileSystemBase::FromString(const nsAString& aString) new DeviceStorageFileSystem(storageType, storageName); return f.forget(); } + return RefPtr(new OSFileSystem(aString)).forget(); } @@ -57,37 +58,19 @@ FileSystemBase::Shutdown() mShutdown = true; } -nsPIDOMWindow* -FileSystemBase::GetWindow() const +nsISupports* +FileSystemBase::GetParentObject() const { return nullptr; } -already_AddRefed -FileSystemBase::GetLocalFile(const nsAString& aRealPath) const -{ - MOZ_ASSERT(XRE_IsParentProcess(), - "Should be on parent process!"); - nsAutoString localPath; - FileSystemUtils::NormalizedPathToLocalPath(aRealPath, localPath); - localPath = mLocalRootPath + localPath; - nsCOMPtr file; - nsresult rv = NS_NewLocalFile(localPath, false, getter_AddRefs(file)); - if (NS_WARN_IF(NS_FAILED(rv))) { - return nullptr; - } - return file.forget(); -} - bool -FileSystemBase::GetRealPath(BlobImpl* aFile, nsAString& aRealPath) const +FileSystemBase::GetRealPath(BlobImpl* aFile, nsIFile** aPath) const { MOZ_ASSERT(XRE_IsParentProcess(), "Should be on parent process!"); MOZ_ASSERT(aFile, "aFile Should not be null."); - aRealPath.Truncate(); - nsAutoString filePath; ErrorResult rv; aFile->GetMozFullPathInternal(filePath, rv); @@ -95,7 +78,13 @@ FileSystemBase::GetRealPath(BlobImpl* aFile, nsAString& aRealPath) const return false; } - return LocalPathToRealPath(filePath, aRealPath); + rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(filePath), + true, aPath); + if (NS_WARN_IF(rv.Failed())) { + return false; + } + + return true; } bool @@ -110,19 +99,5 @@ FileSystemBase::IsSafeDirectory(Directory* aDir) const return false; } -bool -FileSystemBase::LocalPathToRealPath(const nsAString& aLocalPath, - nsAString& aRealPath) const -{ - nsAutoString path; - FileSystemUtils::LocalPathToNormalizedPath(aLocalPath, path); - if (!FileSystemUtils::IsDescendantPath(mNormalizedLocalRootPath, path)) { - aRealPath.Truncate(); - return false; - } - aRealPath = Substring(path, mNormalizedLocalRootPath.Length()); - return true; -} - } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/FileSystemBase.h b/dom/filesystem/FileSystemBase.h index f304410cd1..6dc97247f4 100644 --- a/dom/filesystem/FileSystemBase.h +++ b/dom/filesystem/FileSystemBase.h @@ -10,8 +10,6 @@ #include "nsAutoPtr.h" #include "nsString.h" -class nsPIDOMWindow; - namespace mozilla { namespace dom { @@ -25,28 +23,22 @@ public: // Create file system object from its string representation. static already_AddRefed - FromString(const nsAString& aString); + DeserializeDOMPath(const nsAString& aString); FileSystemBase(); virtual void Shutdown(); - // Get the string representation of the file system. - const nsString& - ToString() const - { - return mString; - } + // SerializeDOMPath the FileSystem to string. + virtual void + SerializeDOMPath(nsAString& aOutput) const = 0; - virtual nsPIDOMWindow* - GetWindow() const; + virtual already_AddRefed + Clone() = 0; - /** - * Create nsIFile object from the given real path (absolute DOM path). - */ - already_AddRefed - GetLocalFile(const nsAString& aRealPath) const; + virtual nsISupports* + GetParentObject() const; /* * Get the virtual name of the root directory. This name will be exposed to @@ -73,13 +65,8 @@ public: virtual bool IsSafeDirectory(Directory* aDir) const; - /* - * Get the real path (absolute DOM path) of the DOM file in the file system. - * If succeeded, returns true. Otherwise, returns false and set aRealPath to - * empty string. - */ bool - GetRealPath(BlobImpl* aFile, nsAString& aRealPath) const; + GetRealPath(BlobImpl* aFile, nsIFile** aPath) const; /* * Get the permission name required to access this file system. @@ -103,21 +90,12 @@ public: protected: virtual ~FileSystemBase(); - bool - LocalPathToRealPath(const nsAString& aLocalPath, nsAString& aRealPath) const; - // The local path of the root (i.e. the OS path, with OS path separators, of // the OS directory that acts as the root of this OSFileSystem). // Only available in the parent process. // In the child process, we don't use it and its value should be empty. nsString mLocalRootPath; - // The same, but with path separators normalized to "/". - nsString mNormalizedLocalRootPath; - - // The string representation of the file system. - nsString mString; - bool mShutdown; // The permission name required to access the file system. diff --git a/dom/filesystem/FileSystemPermissionRequest.cpp b/dom/filesystem/FileSystemPermissionRequest.cpp index 15355648d0..b08eb1bdd9 100644 --- a/dom/filesystem/FileSystemPermissionRequest.cpp +++ b/dom/filesystem/FileSystemPermissionRequest.cpp @@ -15,7 +15,8 @@ namespace mozilla { namespace dom { -NS_IMPL_ISUPPORTS(FileSystemPermissionRequest, nsIRunnable, nsIContentPermissionRequest) +NS_IMPL_ISUPPORTS(FileSystemPermissionRequest, nsIRunnable, + nsIContentPermissionRequest) // static void @@ -28,8 +29,7 @@ FileSystemPermissionRequest::RequestForTask(FileSystemTaskBase* aTask) NS_DispatchToCurrentThread(request); } -FileSystemPermissionRequest::FileSystemPermissionRequest( - FileSystemTaskBase* aTask) +FileSystemPermissionRequest::FileSystemPermissionRequest(FileSystemTaskBase* aTask) : mTask(aTask) { MOZ_ASSERT(mTask, "aTask should not be null!"); @@ -44,8 +44,8 @@ FileSystemPermissionRequest::FileSystemPermissionRequest( mPermissionType = filesystem->GetPermission(); - mWindow = filesystem->GetWindow(); - if (!mWindow) { + mWindow = do_QueryInterface(filesystem->GetParentObject()); + if (NS_WARN_IF(!mWindow)) { return; } diff --git a/dom/filesystem/FileSystemRequestParent.cpp b/dom/filesystem/FileSystemRequestParent.cpp index b43c7bb1c0..d2742a261c 100644 --- a/dom/filesystem/FileSystemRequestParent.cpp +++ b/dom/filesystem/FileSystemRequestParent.cpp @@ -28,8 +28,11 @@ FileSystemRequestParent::~FileSystemRequestParent() #define FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(name) \ case FileSystemParams::TFileSystem##name##Params: { \ const FileSystem##name##Params& p = aParams; \ - mFileSystem = FileSystemBase::FromString(p.filesystem()); \ - task = new name##Task(mFileSystem, p, this); \ + mFileSystem = FileSystemBase::DeserializeDOMPath(p.filesystem()); \ + task = name##Task::Create(mFileSystem, p, this, rv); \ + if (NS_WARN_IF(rv.Failed())) { \ + return false; \ + } \ break; \ } @@ -39,6 +42,8 @@ FileSystemRequestParent::Dispatch(ContentParent* aParent, { MOZ_ASSERT(aParent, "aParent should not be null."); RefPtr task; + ErrorResult rv; + switch (aParams.type()) { FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(CreateDirectory) diff --git a/dom/filesystem/FileSystemTaskBase.cpp b/dom/filesystem/FileSystemTaskBase.cpp index e77cf25126..46cae6d86c 100644 --- a/dom/filesystem/FileSystemTaskBase.cpp +++ b/dom/filesystem/FileSystemTaskBase.cpp @@ -23,7 +23,7 @@ namespace dom { namespace { -class FileSystemReleaseRunnable : public nsRunnable +class FileSystemReleaseRunnable final : public nsRunnable { public: explicit FileSystemReleaseRunnable(RefPtr& aDoomed) @@ -106,12 +106,22 @@ FileSystemTaskBase::Start() return; } + nsAutoString serialization; + mFileSystem->SerializeDOMPath(serialization); + + ErrorResult rv; + FileSystemParams params = GetRequestParams(serialization, rv); + if (NS_WARN_IF(rv.Failed())) { + return; + } + // Retain a reference so the task object isn't deleted without IPDL's // knowledge. The reference will be released by // mozilla::dom::ContentChild::DeallocPFileSystemRequestChild. NS_ADDREF_THIS(); + ContentChild::GetSingleton()->SendPFileSystemRequestConstructor(this, - GetRequestParams(mFileSystem->ToString())); + params); } NS_IMETHODIMP @@ -154,11 +164,17 @@ FileSystemTaskBase::GetRequestResult() const MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - if (HasError()) { - return FileSystemErrorResponse(mErrorValue); - } else { - return GetSuccessRequestResult(); + if (!HasError()) { + ErrorResult rv; + FileSystemResponseValue value = GetSuccessRequestResult(rv); + if (NS_WARN_IF(rv.Failed())) { + return FileSystemErrorResponse(rv.StealNSResult()); + } + + return value; } + + return FileSystemErrorResponse(mErrorValue); } void @@ -171,7 +187,9 @@ FileSystemTaskBase::SetRequestResult(const FileSystemResponseValue& aValue) FileSystemErrorResponse r = aValue; mErrorValue = r.error(); } else { - SetSuccessRequestResult(aValue); + ErrorResult rv; + SetSuccessRequestResult(aValue, rv); + mErrorValue = rv.StealNSResult(); } } diff --git a/dom/filesystem/FileSystemTaskBase.h b/dom/filesystem/FileSystemTaskBase.h index 43dcceb930..3da16d9b25 100644 --- a/dom/filesystem/FileSystemTaskBase.h +++ b/dom/filesystem/FileSystemTaskBase.h @@ -176,7 +176,8 @@ protected: * @param filesystem The string representation of the file system. */ virtual FileSystemParams - GetRequestParams(const nsString& aFileSystem) const = 0; + GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const = 0; /* * Wrap the task success result to FileSystemResponseValue for sending it @@ -185,7 +186,7 @@ protected: * send the task success result back to the child process. */ virtual FileSystemResponseValue - GetSuccessRequestResult() const = 0; + GetSuccessRequestResult(ErrorResult& aRv) const = 0; /* * Unwrap the IPC message to get the task success result. @@ -194,7 +195,8 @@ protected: * success result. */ virtual void - SetSuccessRequestResult(const FileSystemResponseValue& aValue) = 0; + SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) = 0; bool HasError() const { return mErrorValue != NS_OK; } diff --git a/dom/filesystem/FileSystemUtils.cpp b/dom/filesystem/FileSystemUtils.cpp index cf175bcaa2..20d2198b29 100644 --- a/dom/filesystem/FileSystemUtils.cpp +++ b/dom/filesystem/FileSystemUtils.cpp @@ -6,59 +6,28 @@ #include "mozilla/dom/FileSystemUtils.h" -#include "nsXULAppAPI.h" - namespace mozilla { namespace dom { -// static -void -FileSystemUtils::LocalPathToNormalizedPath(const nsAString& aLocal, - nsAString& aNorm) +/* static */ bool +FileSystemUtils::IsDescendantPath(nsIFile* aFile, + nsIFile* aDescendantFile) { - nsString result; - result = aLocal; -#if defined(XP_WIN) - char16_t* cur = result.BeginWriting(); - char16_t* end = result.EndWriting(); - for (; cur < end; ++cur) { - if (char16_t('\\') == *cur) - *cur = char16_t('/'); + nsAutoString path; + nsresult rv = aFile->GetPath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; } -#endif - aNorm = result; -} -// static -void -FileSystemUtils::NormalizedPathToLocalPath(const nsAString& aNorm, - nsAString& aLocal) -{ - nsString result; - result = aNorm; -#if defined(XP_WIN) - char16_t* cur = result.BeginWriting(); - char16_t* end = result.EndWriting(); - for (; cur < end; ++cur) { - if (char16_t('/') == *cur) - *cur = char16_t('\\'); + nsAutoString descendantPath; + rv = aDescendantFile->GetPath(descendantPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; } -#endif - aLocal = result; -} - -// static -bool -FileSystemUtils::IsDescendantPath(const nsAString& aPath, - const nsAString& aDescendantPath) -{ - // The descendant path should begin with its ancestor path. - nsAutoString prefix; - prefix = aPath + NS_LITERAL_STRING(FILESYSTEM_DOM_PATH_SEPARATOR); // Check the sub-directory path to see if it has the parent path as prefix. - if (aDescendantPath.Length() < prefix.Length() || - !StringBeginsWith(aDescendantPath, prefix)) { + if (descendantPath.Length() <= path.Length() || + !StringBeginsWith(descendantPath, path)) { return false; } diff --git a/dom/filesystem/FileSystemUtils.h b/dom/filesystem/FileSystemUtils.h index abf30c3cf5..6c1fbc456d 100644 --- a/dom/filesystem/FileSystemUtils.h +++ b/dom/filesystem/FileSystemUtils.h @@ -7,12 +7,13 @@ #ifndef mozilla_dom_FileSystemUtils_h #define mozilla_dom_FileSystemUtils_h -#include "nsString.h" +class nsIFile; namespace mozilla { namespace dom { -#define FILESYSTEM_DOM_PATH_SEPARATOR "/" +#define FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL "/" +#define FILESYSTEM_DOM_PATH_SEPARATOR_CHAR '/' /* * This class is for error handling. @@ -22,26 +23,10 @@ class FileSystemUtils { public: /* - * Convert the path separator to "/". - */ - static void - LocalPathToNormalizedPath(const nsAString& aLocal, nsAString& aNorm); - - /* - * Convert the normalized path separator "/" to the system dependent path - * separator, which is "/" on Mac and Linux, and "\" on Windows. - */ - static void - NormalizedPathToLocalPath(const nsAString& aNorm, nsAString& aLocal); - - /* - * Return true if aDescendantPath is a descendant of aPath. Both aPath and - * aDescendantPath are absolute DOM path. + * Return true if aDescendantPath is a descendant of aPath. */ static bool - IsDescendantPath(const nsAString& aPath, const nsAString& aDescendantPath); - - static const char16_t kSeparatorChar = char16_t('/'); + IsDescendantPath(nsIFile* aPath, nsIFile* aDescendantPath); }; } // namespace dom diff --git a/dom/filesystem/GetDirectoryListingTask.cpp b/dom/filesystem/GetDirectoryListingTask.cpp index 7a450753a3..f2672d1e27 100644 --- a/dom/filesystem/GetDirectoryListingTask.cpp +++ b/dom/filesystem/GetDirectoryListingTask.cpp @@ -8,7 +8,6 @@ #include "HTMLSplitOnSpacesTokenizer.h" #include "js/Value.h" -#include "mozilla/dom/Directory.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" @@ -21,33 +20,80 @@ namespace mozilla { namespace dom { -GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem, - const nsAString& aTargetPath, - const nsAString& aFilters, - ErrorResult& aRv) - : FileSystemTaskBase(aFileSystem) - , mTargetRealPath(aTargetPath) - , mFilters(aFilters) +/* static */ already_AddRefed +GetDirectoryListingTask::Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + const nsAString& aFilters, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); + + RefPtr task = + new GetDirectoryListingTask(aFileSystem, aTargetPath, aType, aFilters); + + // aTargetPath can be null. In this case SetError will be called. + nsCOMPtr globalObject = - do_QueryInterface(aFileSystem->GetWindow()); - if (!globalObject) { - return; + do_QueryInterface(aFileSystem->GetParentObject()); + if (NS_WARN_IF(!globalObject)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; } - mPromise = Promise::Create(globalObject, aRv); + + task->mPromise = Promise::Create(globalObject, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return task.forget(); +} + +/* static */ already_AddRefed +GetDirectoryListingTask::Create(FileSystemBase* aFileSystem, + const FileSystemGetDirectoryListingParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new GetDirectoryListingTask(aFileSystem, aParam, aParent); + + NS_ConvertUTF16toUTF8 path(aParam.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + task->mType = aParam.isRoot() + ? Directory::eDOMRootDirectory : Directory::eNotDOMRootDirectory; + return task.forget(); +} + +GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + const nsAString& aFilters) + : FileSystemTaskBase(aFileSystem) + , mTargetPath(aTargetPath) + , mFilters(aFilters) + , mType(aType) +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); } GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem, const FileSystemGetDirectoryListingParams& aParam, FileSystemRequestParent* aParent) : FileSystemTaskBase(aFileSystem, aParam, aParent) - , mTargetRealPath(aParam.realPath()) , mFilters(aParam.filters()) { - MOZ_ASSERT(XRE_IsParentProcess(), - "Only call from parent process!"); + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); } @@ -66,43 +112,79 @@ GetDirectoryListingTask::GetPromise() } FileSystemParams -GetDirectoryListingTask::GetRequestParams(const nsString& aFileSystem) const +GetDirectoryListingTask::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - return FileSystemGetDirectoryListingParams(aFileSystem, mTargetRealPath, + + nsAutoString path; + aRv = mTargetPath->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return FileSystemGetDirectoryListingParams(); + } + + return FileSystemGetDirectoryListingParams(aSerializedDOMPath, path, + mType == Directory::eDOMRootDirectory, mFilters); } FileSystemResponseValue -GetDirectoryListingTask::GetSuccessRequestResult() const +GetDirectoryListingTask::GetSuccessRequestResult(ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); InfallibleTArray blobs; - for (unsigned i = 0; i < mTargetBlobImpls.Length(); i++) { - BlobParent* blobParent = GetBlobParent(mTargetBlobImpls[i]); - if (blobParent) { - blobs.AppendElement(blobParent); + nsTArray inputs; + + for (unsigned i = 0; i < mTargetData.Length(); i++) { + if (mTargetData[i].mType == Directory::BlobImplOrDirectoryPath::eBlobImpl) { + BlobParent* blobParent = GetBlobParent(mTargetData[i].mBlobImpl); + if (!blobParent) { + continue; + } + + FileSystemDirectoryListingResponseBlob blobData; + blobData.blobParent() = blobParent; + inputs.AppendElement(blobData); + } else { + MOZ_ASSERT(mTargetData[i].mType == Directory::BlobImplOrDirectoryPath::eDirectoryPath); + FileSystemDirectoryListingResponseDirectory directoryData; + directoryData.directoryRealPath() = mTargetData[i].mDirectoryPath; + inputs.AppendElement(directoryData); } } + FileSystemDirectoryListingResponse response; - response.blobsParent().SwapElements(blobs); + response.data().SwapElements(inputs); return response; } void -GetDirectoryListingTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue) +GetDirectoryListingTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aValue.type() == FileSystemResponseValue::TFileSystemDirectoryListingResponse); FileSystemDirectoryListingResponse r = aValue; - nsTArray& blobs = r.blobsChild(); + for (uint32_t i = 0; i < r.data().Length(); ++i) { + const FileSystemDirectoryListingResponseData& data = r.data()[i]; - for (unsigned i = 0; i < blobs.Length(); i++) { - mTargetBlobImpls.AppendElement(static_cast(blobs[i])->GetBlobImpl()); + if (data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseBlob) { + PBlobChild* blob = data.get_FileSystemDirectoryListingResponseBlob().blobChild(); + + Directory::BlobImplOrDirectoryPath* element = mTargetData.AppendElement(); + element->mType = Directory::BlobImplOrDirectoryPath::eBlobImpl; + element->mBlobImpl = static_cast(blob)->GetBlobImpl(); + } else { + MOZ_ASSERT(data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseDirectory); + + Directory::BlobImplOrDirectoryPath* element = mTargetData.AppendElement(); + element->mType = Directory::BlobImplOrDirectoryPath::eDirectoryPath; + element->mDirectoryPath = data.get_FileSystemDirectoryListingResponseDirectory().directoryRealPath(); + } } } @@ -117,27 +199,19 @@ GetDirectoryListingTask::Work() return NS_ERROR_FAILURE; } - // Whether we want to get the root directory. - bool getRoot = mTargetRealPath.IsEmpty(); - - nsCOMPtr dir = mFileSystem->GetLocalFile(mTargetRealPath); - if (!dir) { - return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - } - bool exists; - nsresult rv = dir->Exists(&exists); + nsresult rv = mTargetPath->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { - if (!getRoot) { + if (mType == Directory::eNotDOMRootDirectory) { return NS_ERROR_DOM_FILE_NOT_FOUND_ERR; } // If the root directory doesn't exit, create it. - rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0777); + rv = mTargetPath->Create(nsIFile::DIRECTORY_TYPE, 0777); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -145,7 +219,7 @@ GetDirectoryListingTask::Work() // Get isDirectory. bool isDir; - rv = dir->IsDirectory(&isDir); + rv = mTargetPath->IsDirectory(&isDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -155,7 +229,7 @@ GetDirectoryListingTask::Work() } nsCOMPtr entries; - rv = dir->GetDirectoryEntries(getter_AddRefs(entries)); + rv = mTargetPath->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -186,7 +260,7 @@ GetDirectoryListingTask::Work() nsCOMPtr currFile = do_QueryInterface(supp); MOZ_ASSERT(currFile); - + bool isLink, isSpecial, isFile; if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) || NS_FAILED(currFile->IsSpecial(&isSpecial))) || @@ -213,9 +287,22 @@ GetDirectoryListingTask::Work() } } - BlobImplFile* impl = new BlobImplFile(currFile); - impl->LookupAndCacheIsDirectory(); - mTargetBlobImpls.AppendElement(impl); + if (isDir) { + nsAutoString path; + if (NS_WARN_IF(NS_FAILED(currFile->GetPath(path)))) { + continue; + } + + Directory::BlobImplOrDirectoryPath* element = mTargetData.AppendElement(); + element->mType = Directory::BlobImplOrDirectoryPath::eDirectoryPath; + element->mDirectoryPath = path; + } else { + BlobImplFile* impl = new BlobImplFile(currFile); + + Directory::BlobImplOrDirectoryPath* element = mTargetData.AppendElement(); + element->mType = Directory::BlobImplOrDirectoryPath::eBlobImpl; + element->mBlobImpl = impl; + } } return NS_OK; } @@ -235,37 +322,55 @@ GetDirectoryListingTask::HandlerCallback() return; } - size_t count = mTargetBlobImpls.Length(); + size_t count = mTargetData.Length(); Sequence listing; if (!listing.SetLength(count, mozilla::fallible_t())) { - mPromise->MaybeReject(NS_ERROR_FAILURE); + mPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); mPromise = nullptr; return; } for (unsigned i = 0; i < count; i++) { - if (mTargetBlobImpls[i]->IsDirectory()) { - nsAutoString name; - mTargetBlobImpls[i]->GetName(name); - nsAutoString path(mTargetRealPath); - path.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR); - path.Append(name); -#ifdef DEBUG - if (XRE_IsParentProcess()) { - nsCOMPtr file = mFileSystem->GetLocalFile(path); - bool exist; - file->Exists(&exist); - MOZ_ASSERT(exist); + if (mTargetData[i].mType == Directory::BlobImplOrDirectoryPath::eDirectoryPath) { + nsCOMPtr directoryPath; + NS_ConvertUTF16toUTF8 path(mTargetData[i].mDirectoryPath); + nsresult rv = NS_NewNativeLocalFile(path, true, + getter_AddRefs(directoryPath)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mPromise->MaybeReject(rv); + mPromise = nullptr; + return; } + +#ifdef DEBUG + nsCOMPtr rootPath; + rv = NS_NewLocalFile(mFileSystem->GetLocalRootPath(), false, + getter_AddRefs(rootPath)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mPromise->MaybeReject(rv); + mPromise = nullptr; + return; + } + + MOZ_ASSERT(FileSystemUtils::IsDescendantPath(rootPath, directoryPath)); #endif - RefPtr directory = new Directory(mFileSystem, path); + + RefPtr directory = + Directory::Create(mFileSystem->GetParentObject(), + directoryPath, + Directory::eNotDOMRootDirectory, + mFileSystem); + MOZ_ASSERT(directory); + // Propogate mFilter onto sub-Directory object: directory->SetContentFilters(mFilters); listing[i].SetAsDirectory() = directory; } else { - listing[i].SetAsFile() = File::Create(mFileSystem->GetWindow(), mTargetBlobImpls[i]); + MOZ_ASSERT(mTargetData[i].mType == Directory::BlobImplOrDirectoryPath::eBlobImpl); + listing[i].SetAsFile() = + File::Create(mFileSystem->GetParentObject(), mTargetData[i].mBlobImpl); } } diff --git a/dom/filesystem/GetDirectoryListingTask.h b/dom/filesystem/GetDirectoryListingTask.h index 4a6dc7b045..f0a3ae7fd5 100644 --- a/dom/filesystem/GetDirectoryListingTask.h +++ b/dom/filesystem/GetDirectoryListingTask.h @@ -7,6 +7,7 @@ #ifndef mozilla_dom_GetDirectoryListing_h #define mozilla_dom_GetDirectoryListing_h +#include "mozilla/dom/Directory.h" #include "mozilla/dom/FileSystemTaskBase.h" #include "mozilla/ErrorResult.h" #include "nsAutoPtr.h" @@ -16,18 +17,21 @@ namespace dom { class BlobImpl; -class GetDirectoryListingTask final - : public FileSystemTaskBase +class GetDirectoryListingTask final : public FileSystemTaskBase { public: - // If aDirectoryOnly is set, we should ensure that the target is a directory. - GetDirectoryListingTask(FileSystemBase* aFileSystem, - const nsAString& aTargetPath, - const nsAString& aFilters, - ErrorResult& aRv); - GetDirectoryListingTask(FileSystemBase* aFileSystem, - const FileSystemGetDirectoryListingParams& aParam, - FileSystemRequestParent* aParent); + static already_AddRefed + Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + const nsAString& aFilters, + ErrorResult& aRv); + + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemGetDirectoryListingParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); virtual ~GetDirectoryListingTask(); @@ -37,15 +41,28 @@ public: virtual void GetPermissionAccessType(nsCString& aAccess) const override; -protected: + +private: + // If aDirectoryOnly is set, we should ensure that the target is a directory. + GetDirectoryListingTask(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + const nsAString& aFilters); + + GetDirectoryListingTask(FileSystemBase* aFileSystem, + const FileSystemGetDirectoryListingParams& aParam, + FileSystemRequestParent* aParent); + virtual FileSystemParams - GetRequestParams(const nsString& aFileSystem) const override; + GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const override; virtual FileSystemResponseValue - GetSuccessRequestResult() const override; + GetSuccessRequestResult(ErrorResult& aRv) const override; virtual void - SetSuccessRequestResult(const FileSystemResponseValue& aValue) override; + SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) override; virtual nsresult Work() override; @@ -53,14 +70,14 @@ protected: virtual void HandlerCallback() override; -private: RefPtr mPromise; - nsString mTargetRealPath; + nsCOMPtr mTargetPath; nsString mFilters; + Directory::DirectoryType mType; // We cannot store File or Directory objects bacause this object is created // on a different thread and File and Directory are not thread-safe. - nsTArray> mTargetBlobImpls; + nsTArray mTargetData; }; } // namespace dom diff --git a/dom/filesystem/GetFileOrDirectoryTask.cpp b/dom/filesystem/GetFileOrDirectoryTask.cpp index a046a5ac34..ccb4b851b4 100644 --- a/dom/filesystem/GetFileOrDirectoryTask.cpp +++ b/dom/filesystem/GetFileOrDirectoryTask.cpp @@ -7,8 +7,6 @@ #include "GetFileOrDirectoryTask.h" #include "js/Value.h" -#include "mozilla/dom/Directory.h" -#include "mozilla/dom/DOMError.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" @@ -21,37 +19,82 @@ namespace mozilla { namespace dom { -GetFileOrDirectoryTask::GetFileOrDirectoryTask( - FileSystemBase* aFileSystem, - const nsAString& aTargetPath, - bool aDirectoryOnly, - ErrorResult& aRv) - : FileSystemTaskBase(aFileSystem) - , mTargetRealPath(aTargetPath) - , mIsDirectory(aDirectoryOnly) +/* static */ already_AddRefed +GetFileOrDirectoryTask::Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + bool aDirectoryOnly, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); + + RefPtr task = + new GetFileOrDirectoryTask(aFileSystem, aTargetPath, aType, aDirectoryOnly); + + // aTargetPath can be null. In this case SetError will be called. + nsCOMPtr globalObject = - do_QueryInterface(aFileSystem->GetWindow()); - if (!globalObject) { - return; + do_QueryInterface(aFileSystem->GetParentObject()); + if (NS_WARN_IF(!globalObject)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; } - mPromise = Promise::Create(globalObject, aRv); + + task->mPromise = Promise::Create(globalObject, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return task.forget(); } -GetFileOrDirectoryTask::GetFileOrDirectoryTask( - FileSystemBase* aFileSystem, - const FileSystemGetFileOrDirectoryParams& aParam, - FileSystemRequestParent* aParent) +/* static */ already_AddRefed +GetFileOrDirectoryTask::Create(FileSystemBase* aFileSystem, + const FileSystemGetFileOrDirectoryParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new GetFileOrDirectoryTask(aFileSystem, aParam, aParent); + + NS_ConvertUTF16toUTF8 path(aParam.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + task->mType = aParam.isRoot() + ? Directory::eDOMRootDirectory : Directory::eNotDOMRootDirectory; + return task.forget(); +} + +GetFileOrDirectoryTask::GetFileOrDirectoryTask(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + bool aDirectoryOnly) + : FileSystemTaskBase(aFileSystem) + , mTargetPath(aTargetPath) + , mIsDirectory(aDirectoryOnly) + , mType(aType) +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); +} + +GetFileOrDirectoryTask::GetFileOrDirectoryTask(FileSystemBase* aFileSystem, + const FileSystemGetFileOrDirectoryParams& aParam, + FileSystemRequestParent* aParent) : FileSystemTaskBase(aFileSystem, aParam, aParent) , mIsDirectory(false) { - MOZ_ASSERT(XRE_IsParentProcess(), - "Only call from parent process!"); + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - mTargetRealPath = aParam.realPath(); } GetFileOrDirectoryTask::~GetFileOrDirectoryTask() @@ -68,18 +111,33 @@ GetFileOrDirectoryTask::GetPromise() } FileSystemParams -GetFileOrDirectoryTask::GetRequestParams(const nsString& aFileSystem) const +GetFileOrDirectoryTask::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - return FileSystemGetFileOrDirectoryParams(aFileSystem, mTargetRealPath); + + nsAutoString path; + aRv = mTargetPath->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return FileSystemGetFileOrDirectoryParams(); + } + + return FileSystemGetFileOrDirectoryParams(aSerializedDOMPath, path, + mType == Directory::eDOMRootDirectory); } FileSystemResponseValue -GetFileOrDirectoryTask::GetSuccessRequestResult() const +GetFileOrDirectoryTask::GetSuccessRequestResult(ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); if (mIsDirectory) { - return FileSystemDirectoryResponse(mTargetRealPath); + nsAutoString path; + aRv = mTargetPath->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return FileSystemDirectoryResponse(); + } + + return FileSystemDirectoryResponse(path); } BlobParent* actor = GetBlobParent(mTargetBlobImpl); @@ -92,7 +150,8 @@ GetFileOrDirectoryTask::GetSuccessRequestResult() const } void -GetFileOrDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue) +GetFileOrDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); switch (aValue.type()) { @@ -105,7 +164,13 @@ GetFileOrDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& a } case FileSystemResponseValue::TFileSystemDirectoryResponse: { FileSystemDirectoryResponse r = aValue; - mTargetRealPath = r.realPath(); + + NS_ConvertUTF16toUTF8 path(r.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + mIsDirectory = true; break; } @@ -128,33 +193,26 @@ GetFileOrDirectoryTask::Work() } // Whether we want to get the root directory. - bool getRoot = mTargetRealPath.IsEmpty(); - - nsCOMPtr file = mFileSystem->GetLocalFile(mTargetRealPath); - if (!file) { - return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - } - bool exists; - nsresult rv = file->Exists(&exists); + nsresult rv = mTargetPath->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { - if (!getRoot) { + if (mType == Directory::eNotDOMRootDirectory) { return NS_ERROR_DOM_FILE_NOT_FOUND_ERR; } // If the root directory doesn't exit, create it. - rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777); + rv = mTargetPath->Create(nsIFile::DIRECTORY_TYPE, 0777); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // Get isDirectory. - rv = file->IsDirectory(&mIsDirectory); + rv = mTargetPath->IsDirectory(&mIsDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -164,13 +222,13 @@ GetFileOrDirectoryTask::Work() } // Check if the root is a directory. - if (getRoot) { + if (mType == Directory::eDOMRootDirectory) { return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR; } bool isFile; // Get isFile - rv = file->IsFile(&isFile); + rv = mTargetPath->IsFile(&isFile); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -180,11 +238,11 @@ GetFileOrDirectoryTask::Work() return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR; } - if (!mFileSystem->IsSafeFile(file)) { + if (!mFileSystem->IsSafeFile(mTargetPath)) { return NS_ERROR_DOM_SECURITY_ERR; } - mTargetBlobImpl = new BlobImplFile(file); + mTargetBlobImpl = new BlobImplFile(mTargetPath); return NS_OK; } @@ -199,21 +257,25 @@ GetFileOrDirectoryTask::HandlerCallback() } if (HasError()) { - RefPtr domError = new DOMError(mFileSystem->GetWindow(), - mErrorValue); - mPromise->MaybeRejectBrokenly(domError); + mPromise->MaybeReject(mErrorValue); mPromise = nullptr; return; } if (mIsDirectory) { - RefPtr dir = new Directory(mFileSystem, mTargetRealPath); + RefPtr dir = Directory::Create(mFileSystem->GetParentObject(), + mTargetPath, + mType, + mFileSystem); + MOZ_ASSERT(dir); + mPromise->MaybeResolve(dir); mPromise = nullptr; return; } - RefPtr blob = Blob::Create(mFileSystem->GetWindow(), mTargetBlobImpl); + RefPtr blob = Blob::Create(mFileSystem->GetParentObject(), + mTargetBlobImpl); mPromise->MaybeResolve(blob); mPromise = nullptr; } diff --git a/dom/filesystem/GetFileOrDirectoryTask.h b/dom/filesystem/GetFileOrDirectoryTask.h index 3d76bdca98..4430e567d7 100644 --- a/dom/filesystem/GetFileOrDirectoryTask.h +++ b/dom/filesystem/GetFileOrDirectoryTask.h @@ -7,6 +7,7 @@ #ifndef mozilla_dom_GetFileOrDirectory_h #define mozilla_dom_GetFileOrDirectory_h +#include "mozilla/dom/Directory.h" #include "mozilla/dom/FileSystemTaskBase.h" #include "nsAutoPtr.h" #include "mozilla/ErrorResult.h" @@ -16,18 +17,21 @@ namespace dom { class BlobImpl; -class GetFileOrDirectoryTask final - : public FileSystemTaskBase +class GetFileOrDirectoryTask final : public FileSystemTaskBase { public: - // If aDirectoryOnly is set, we should ensure that the target is a directory. - GetFileOrDirectoryTask(FileSystemBase* aFileSystem, - const nsAString& aTargetPath, - bool aDirectoryOnly, - ErrorResult& aRv); - GetFileOrDirectoryTask(FileSystemBase* aFileSystem, - const FileSystemGetFileOrDirectoryParams& aParam, - FileSystemRequestParent* aParent); + static already_AddRefed + Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + bool aDirectoryOnly, + ErrorResult& aRv); + + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemGetFileOrDirectoryParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); virtual ~GetFileOrDirectoryTask(); @@ -39,13 +43,15 @@ public: GetPermissionAccessType(nsCString& aAccess) const override; protected: virtual FileSystemParams - GetRequestParams(const nsString& aFileSystem) const override; + GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const override; virtual FileSystemResponseValue - GetSuccessRequestResult() const override; + GetSuccessRequestResult(ErrorResult& aRv) const override; virtual void - SetSuccessRequestResult(const FileSystemResponseValue& aValue) override; + SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) override; virtual nsresult Work() override; @@ -54,10 +60,22 @@ protected: HandlerCallback() override; private: + // If aDirectoryOnly is set, we should ensure that the target is a directory. + GetFileOrDirectoryTask(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + bool aDirectoryOnly); + + GetFileOrDirectoryTask(FileSystemBase* aFileSystem, + const FileSystemGetFileOrDirectoryParams& aParam, + FileSystemRequestParent* aParent); + RefPtr mPromise; - nsString mTargetRealPath; + nsCOMPtr mTargetPath; + // Whether we get a directory. bool mIsDirectory; + Directory::DirectoryType mType; // This cannot be a File bacause this object is created on a different // thread and File is not thread-safe. Let's use the BlobImpl instead. diff --git a/dom/filesystem/OSFileSystem.cpp b/dom/filesystem/OSFileSystem.cpp index 9937357ebf..50335a982b 100644 --- a/dom/filesystem/OSFileSystem.cpp +++ b/dom/filesystem/OSFileSystem.cpp @@ -9,10 +9,10 @@ #include "mozilla/dom/Directory.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemUtils.h" +#include "nsIGlobalObject.h" #include "nsCOMPtr.h" #include "nsDebug.h" #include "nsIFile.h" -#include "nsPIDOMWindow.h" namespace mozilla { namespace dom { @@ -20,40 +20,52 @@ namespace dom { OSFileSystem::OSFileSystem(const nsAString& aRootDir) { mLocalRootPath = aRootDir; - FileSystemUtils::LocalPathToNormalizedPath(mLocalRootPath, - mNormalizedLocalRootPath); // Non-mobile devices don't have the concept of separate permissions to // access different parts of devices storage like Pictures, or Videos, etc. mRequiresPermissionChecks = false; - mString = mLocalRootPath; - #ifdef DEBUG mPermission.AssignLiteral("never-used"); #endif } -void -OSFileSystem::Init(nsPIDOMWindow* aWindow) +already_AddRefed +OSFileSystem::Clone() { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(!mWindow, "No duple Init() calls"); - MOZ_ASSERT(aWindow); - mWindow = aWindow; + RefPtr fs = new OSFileSystem(mLocalRootPath); + if (mParent) { + fs->Init(mParent); + } + + return fs.forget(); } -nsPIDOMWindow* -OSFileSystem::GetWindow() const +void +OSFileSystem::Init(nsISupports* aParent) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - return mWindow; + MOZ_ASSERT(!mParent, "No duple Init() calls"); + MOZ_ASSERT(aParent); + mParent = aParent; + +#ifdef DEBUG + nsCOMPtr obj = do_QueryInterface(aParent); + MOZ_ASSERT(obj); +#endif +} + +nsISupports* +OSFileSystem::GetParentObject() const +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + return mParent; } void OSFileSystem::GetRootName(nsAString& aRetval) const { - return aRetval.AssignLiteral("/"); + aRetval.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); } bool @@ -79,14 +91,20 @@ OSFileSystem::IsSafeDirectory(Directory* aDir) const void OSFileSystem::Unlink() { - mWindow = nullptr; + mParent = nullptr; } void OSFileSystem::Traverse(nsCycleCollectionTraversalCallback &cb) { OSFileSystem* tmp = this; - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent); +} + +void +OSFileSystem::SerializeDOMPath(nsAString& aOutput) const +{ + aOutput = mLocalRootPath; } } // namespace dom diff --git a/dom/filesystem/OSFileSystem.h b/dom/filesystem/OSFileSystem.h index 039a7e13b0..bf0708cf9b 100644 --- a/dom/filesystem/OSFileSystem.h +++ b/dom/filesystem/OSFileSystem.h @@ -18,12 +18,15 @@ public: explicit OSFileSystem(const nsAString& aRootDir); void - Init(nsPIDOMWindow* aWindow); + Init(nsISupports* aParent); // Overrides FileSystemBase - virtual nsPIDOMWindow* - GetWindow() const override; + virtual already_AddRefed + Clone() override; + + virtual nsISupports* + GetParentObject() const override; virtual void GetRootName(nsAString& aRetval) const override; @@ -34,6 +37,9 @@ public: virtual bool IsSafeDirectory(Directory* aDir) const override; + virtual void + SerializeDOMPath(nsAString& aOutput) const override; + // CC methods virtual void Unlink() override; virtual void Traverse(nsCycleCollectionTraversalCallback &cb) override; @@ -41,7 +47,7 @@ public: private: virtual ~OSFileSystem() {} - nsCOMPtr mWindow; + nsCOMPtr mParent; }; } // namespace dom diff --git a/dom/filesystem/PFileSystemRequest.ipdl b/dom/filesystem/PFileSystemRequest.ipdl index 9e9a7c71c0..6d2e934930 100644 --- a/dom/filesystem/PFileSystemRequest.ipdl +++ b/dom/filesystem/PFileSystemRequest.ipdl @@ -20,9 +20,26 @@ struct FileSystemDirectoryResponse nsString realPath; }; +struct FileSystemDirectoryListingResponseBlob +{ + PBlob blob; +}; + +struct FileSystemDirectoryListingResponseDirectory +{ + // This is the full real path for the directory that we are sending via IPC. + nsString directoryRealPath; +}; + +union FileSystemDirectoryListingResponseData +{ + FileSystemDirectoryListingResponseBlob; + FileSystemDirectoryListingResponseDirectory; +}; + struct FileSystemDirectoryListingResponse { - PBlob[] blobs; + FileSystemDirectoryListingResponseData[] data; }; struct FileSystemErrorResponse diff --git a/dom/filesystem/RemoveTask.cpp b/dom/filesystem/RemoveTask.cpp index 221d47f229..8bebeef1a2 100644 --- a/dom/filesystem/RemoveTask.cpp +++ b/dom/filesystem/RemoveTask.cpp @@ -6,7 +6,6 @@ #include "RemoveTask.h" -#include "DOMError.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" @@ -19,27 +18,94 @@ namespace mozilla { namespace dom { +/* static */ already_AddRefed +RemoveTask::Create(FileSystemBase* aFileSystem, + nsIFile* aDirPath, + BlobImpl* aTargetBlob, + nsIFile* aTargetPath, + bool aRecursive, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); + MOZ_ASSERT(aDirPath); + + RefPtr task = + new RemoveTask(aFileSystem, aDirPath, aTargetBlob, aTargetPath, aRecursive); + + // aTargetPath can be null. In this case SetError will be called. + + nsCOMPtr globalObject = + do_QueryInterface(aFileSystem->GetParentObject()); + if (NS_WARN_IF(!globalObject)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + task->mPromise = Promise::Create(globalObject, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return task.forget(); +} + +/* static */ already_AddRefed +RemoveTask::Create(FileSystemBase* aFileSystem, + const FileSystemRemoveParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new RemoveTask(aFileSystem, aParam, aParent); + + NS_ConvertUTF16toUTF8 directoryPath(aParam.directory()); + aRv = NS_NewNativeLocalFile(directoryPath, true, + getter_AddRefs(task->mDirPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + task->mRecursive = aParam.recursive(); + + const FileSystemPathOrFileValue& target = aParam.target(); + + if (target.type() == FileSystemPathOrFileValue::TnsString) { + NS_ConvertUTF16toUTF8 path(target); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return task.forget(); + } + + BlobParent* bp = static_cast(static_cast(target)); + task->mTargetBlobImpl = bp->GetBlobImpl(); + MOZ_ASSERT(task->mTargetBlobImpl); + + return task.forget(); +} + RemoveTask::RemoveTask(FileSystemBase* aFileSystem, - const nsAString& aDirPath, + nsIFile* aDirPath, BlobImpl* aTargetBlob, - const nsAString& aTargetPath, - bool aRecursive, - ErrorResult& aRv) + nsIFile* aTargetPath, + bool aRecursive) : FileSystemTaskBase(aFileSystem) - , mDirRealPath(aDirPath) + , mDirPath(aDirPath) , mTargetBlobImpl(aTargetBlob) - , mTargetRealPath(aTargetPath) + , mTargetPath(aTargetPath) , mRecursive(aRecursive) , mReturnValue(false) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - nsCOMPtr globalObject = - do_QueryInterface(aFileSystem->GetWindow()); - if (!globalObject) { - return; - } - mPromise = Promise::Create(globalObject, aRv); + MOZ_ASSERT(aDirPath); } RemoveTask::RemoveTask(FileSystemBase* aFileSystem, @@ -49,25 +115,9 @@ RemoveTask::RemoveTask(FileSystemBase* aFileSystem, , mRecursive(false) , mReturnValue(false) { - MOZ_ASSERT(XRE_IsParentProcess(), - "Only call from parent process!"); + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - - mDirRealPath = aParam.directory(); - - mRecursive = aParam.recursive(); - - const FileSystemPathOrFileValue& target = aParam.target(); - - if (target.type() == FileSystemPathOrFileValue::TnsString) { - mTargetRealPath = target; - return; - } - - BlobParent* bp = static_cast(static_cast(target)); - mTargetBlobImpl = bp->GetBlobImpl(); - MOZ_ASSERT(mTargetBlobImpl); } RemoveTask::~RemoveTask() @@ -84,36 +134,49 @@ RemoveTask::GetPromise() } FileSystemParams -RemoveTask::GetRequestParams(const nsString& aFileSystem) const +RemoveTask::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); FileSystemRemoveParams param; - param.filesystem() = aFileSystem; - param.directory() = mDirRealPath; + param.filesystem() = aSerializedDOMPath; + + aRv = mDirPath->GetPath(param.directory()); + if (NS_WARN_IF(aRv.Failed())) { + return param; + } + param.recursive() = mRecursive; if (mTargetBlobImpl) { - RefPtr blob = Blob::Create(mFileSystem->GetWindow(), - mTargetBlobImpl); + RefPtr blob = Blob::Create(mFileSystem->GetParentObject(), + mTargetBlobImpl); BlobChild* actor = ContentChild::GetSingleton()->GetOrCreateActorForBlob(blob); if (actor) { param.target() = actor; } } else { - param.target() = mTargetRealPath; + nsAutoString path; + aRv = mTargetPath->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return param; + } + + param.target() = path; } return param; } FileSystemResponseValue -RemoveTask::GetSuccessRequestResult() const +RemoveTask::GetSuccessRequestResult(ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); return FileSystemBooleanResponse(mReturnValue); } void -RemoveTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue) +RemoveTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); FileSystemBooleanResponse r = aValue; @@ -131,23 +194,19 @@ RemoveTask::Work() return NS_ERROR_FAILURE; } - // Get the DOM path if a File is passed as the target. + // Get the path if a File is passed as the target. if (mTargetBlobImpl) { - if (!mFileSystem->GetRealPath(mTargetBlobImpl, mTargetRealPath)) { + if (!mFileSystem->GetRealPath(mTargetBlobImpl, + getter_AddRefs(mTargetPath))) { return NS_ERROR_DOM_SECURITY_ERR; } - if (!FileSystemUtils::IsDescendantPath(mDirRealPath, mTargetRealPath)) { + if (!FileSystemUtils::IsDescendantPath(mDirPath, mTargetPath)) { return NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR; } } - nsCOMPtr file = mFileSystem->GetLocalFile(mTargetRealPath); - if (!file) { - return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - } - bool exists = false; - nsresult rv = file->Exists(&exists); + nsresult rv = mTargetPath->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -158,16 +217,16 @@ RemoveTask::Work() } bool isFile = false; - rv = file->IsFile(&isFile); + rv = mTargetPath->IsFile(&isFile); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (isFile && !mFileSystem->IsSafeFile(file)) { + if (isFile && !mFileSystem->IsSafeFile(mTargetPath)) { return NS_ERROR_DOM_SECURITY_ERR; } - rv = file->Remove(mRecursive); + rv = mTargetPath->Remove(mRecursive); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -187,9 +246,7 @@ RemoveTask::HandlerCallback() } if (HasError()) { - RefPtr domError = new DOMError(mFileSystem->GetWindow(), - mErrorValue); - mPromise->MaybeRejectBrokenly(domError); + mPromise->MaybeReject(mErrorValue); mPromise = nullptr; return; } diff --git a/dom/filesystem/RemoveTask.h b/dom/filesystem/RemoveTask.h index 0f75d9f9eb..d3fbfd1ebb 100644 --- a/dom/filesystem/RemoveTask.h +++ b/dom/filesystem/RemoveTask.h @@ -17,19 +17,22 @@ namespace dom { class BlobImpl; class Promise; -class RemoveTask final - : public FileSystemTaskBase +class RemoveTask final : public FileSystemTaskBase { public: - RemoveTask(FileSystemBase* aFileSystem, - const nsAString& aDirPath, - BlobImpl* aTargetBlob, - const nsAString& aTargetPath, - bool aRecursive, - ErrorResult& aRv); - RemoveTask(FileSystemBase* aFileSystem, - const FileSystemRemoveParams& aParam, - FileSystemRequestParent* aParent); + static already_AddRefed + Create(FileSystemBase* aFileSystem, + nsIFile* aDirPath, + BlobImpl* aTargetBlob, + nsIFile* aTargetPath, + bool aRecursive, + ErrorResult& aRv); + + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemRemoveParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); virtual ~RemoveTask(); @@ -42,13 +45,15 @@ public: protected: virtual FileSystemParams - GetRequestParams(const nsString& aFileSystem) const override; + GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const override; virtual FileSystemResponseValue - GetSuccessRequestResult() const override; + GetSuccessRequestResult(ErrorResult& aRv) const override; virtual void - SetSuccessRequestResult(const FileSystemResponseValue& aValue) override; + SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) override; virtual nsresult Work() override; @@ -57,12 +62,23 @@ protected: HandlerCallback() override; private: + RemoveTask(FileSystemBase* aFileSystem, + nsIFile* aDirPath, + BlobImpl* aTargetBlob, + nsIFile* aTargetPath, + bool aRecursive); + + RemoveTask(FileSystemBase* aFileSystem, + const FileSystemRemoveParams& aParam, + FileSystemRequestParent* aParent); + RefPtr mPromise; - nsString mDirRealPath; + nsCOMPtr mDirPath; + // This cannot be a File because this object will be used on a different // thread and File is not thread-safe. Let's use the BlobImpl instead. RefPtr mTargetBlobImpl; - nsString mTargetRealPath; + nsCOMPtr mTargetPath; bool mRecursive; bool mReturnValue; }; diff --git a/dom/filesystem/moz.build b/dom/filesystem/moz.build index 06b916438c..adaf5794fe 100644 --- a/dom/filesystem/moz.build +++ b/dom/filesystem/moz.build @@ -4,6 +4,8 @@ # 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/. +TEST_DIRS += ['tests'] + EXPORTS.mozilla.dom += [ 'DeviceStorageFileSystem.h', 'Directory.h', diff --git a/dom/filesystem/tests/mochitest.ini b/dom/filesystem/tests/mochitest.ini new file mode 100644 index 0000000000..15292f2eaf --- /dev/null +++ b/dom/filesystem/tests/mochitest.ini @@ -0,0 +1,5 @@ +[DEFAULT] +support-files = + script_fileList.js + +[test_basic.html] diff --git a/dom/filesystem/tests/moz.build b/dom/filesystem/tests/moz.build new file mode 100644 index 0000000000..8421b15157 --- /dev/null +++ b/dom/filesystem/tests/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_MANIFESTS += ['mochitest.ini'] diff --git a/dom/filesystem/tests/script_fileList.js b/dom/filesystem/tests/script_fileList.js new file mode 100644 index 0000000000..81fd6b38d1 --- /dev/null +++ b/dom/filesystem/tests/script_fileList.js @@ -0,0 +1,13 @@ +var { classes: Cc, interfaces: Ci, utils: Cu } = Components; +Cu.importGlobalProperties(["File"]); + +addMessageListener("dir.open", function () { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryService) + .QueryInterface(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + + sendAsyncMessage("dir.opened", { + dir: testFile.path + }); +}); diff --git a/dom/filesystem/tests/test_basic.html b/dom/filesystem/tests/test_basic.html new file mode 100644 index 0000000000..1b9f355b81 --- /dev/null +++ b/dom/filesystem/tests/test_basic.html @@ -0,0 +1,103 @@ + + + + Test for Directory API + + + + + + + + + diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index e4b28cdc58..6bf5f7b754 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -11,8 +11,6 @@ #include "mozilla/DebugOnly.h" #include "mozilla/dom/Date.h" #include "mozilla/dom/Directory.h" -#include "mozilla/dom/FileSystemUtils.h" -#include "mozilla/dom/OSFileSystem.h" #include "nsAttrValueInlines.h" #include "nsCRTGlue.h" @@ -251,16 +249,63 @@ class HTMLInputElementState final : public nsISupports mValue = aValue; } - const nsTArray>& GetBlobImpls() + void + GetFilesOrDirectories(nsPIDOMWindow* aWindow, + nsTArray& aResult) const { - return mBlobImpls; + for (uint32_t i = 0; i < mBlobImplsOrDirectoryPaths.Length(); ++i) { + if (mBlobImplsOrDirectoryPaths[i].mType == Directory::BlobImplOrDirectoryPath::eBlobImpl) { + RefPtr file = + File::Create(aWindow, + mBlobImplsOrDirectoryPaths[i].mBlobImpl); + MOZ_ASSERT(file); + + OwningFileOrDirectory* element = aResult.AppendElement(); + element->SetAsFile() = file; + } else { + MOZ_ASSERT(mBlobImplsOrDirectoryPaths[i].mType == Directory::BlobImplOrDirectoryPath::eDirectoryPath); + + nsCOMPtr file; + NS_ConvertUTF16toUTF8 path(mBlobImplsOrDirectoryPaths[i].mDirectoryPath); + nsresult rv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + RefPtr directory = Directory::Create(aWindow, file, + Directory::eDOMRootDirectory); + MOZ_ASSERT(directory); + + OwningFileOrDirectory* element = aResult.AppendElement(); + element->SetAsDirectory() = directory; + } + } } - void SetBlobImpls(const nsTArray>& aFile) + void SetFilesOrDirectories(const nsTArray& aArray) { - mBlobImpls.Clear(); - for (uint32_t i = 0, len = aFile.Length(); i < len; ++i) { - mBlobImpls.AppendElement(aFile[i]->Impl()); + mBlobImplsOrDirectoryPaths.Clear(); + for (uint32_t i = 0; i < aArray.Length(); ++i) { + if (aArray[i].IsFile()) { + Directory::BlobImplOrDirectoryPath* data = + mBlobImplsOrDirectoryPaths.AppendElement(); + + data->mBlobImpl = aArray[i].GetAsFile()->Impl(); + data->mType = Directory::BlobImplOrDirectoryPath::eBlobImpl; + } else { + MOZ_ASSERT(aArray[i].IsDirectory()); + nsAutoString fullPath; + nsresult rv = aArray[i].GetAsDirectory()->GetFullRealPath(fullPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + Directory::BlobImplOrDirectoryPath* data = + mBlobImplsOrDirectoryPaths.AppendElement(); + + data->mDirectoryPath = fullPath; + data->mType = Directory::BlobImplOrDirectoryPath::eDirectoryPath; + } } } @@ -274,7 +319,9 @@ class HTMLInputElementState final : public nsISupports ~HTMLInputElementState() {} nsString mValue; - nsTArray> mBlobImpls; + + nsTArray mBlobImplsOrDirectoryPaths; + bool mChecked; bool mCheckedSet; }; @@ -340,33 +387,65 @@ UploadLastDir::ContentPrefCallback::HandleError(nsresult error) namespace { /** - * This may return nullptr if aDomFile's implementation of + * This may return nullptr if the DOM File's implementation of * File::mozFullPathInternal does not successfully return a non-empty * string that is a valid path. This can happen on Firefox OS, for example, * where the file picker can create Blobs. */ static already_AddRefed -DOMFileToLocalFile(File* aDomFile) +DOMFileOrDirectoryToLocalFile(const OwningFileOrDirectory& aData) { nsString path; - ErrorResult rv; - aDomFile->GetMozFullPathInternal(path, rv); - if (rv.Failed() || path.IsEmpty()) { - rv.SuppressException(); - return nullptr; + + if (aData.IsFile()) { + ErrorResult rv; + aData.GetAsFile()->GetMozFullPathInternal(path, rv); + if (rv.Failed() || path.IsEmpty()) { + rv.SuppressException(); + return nullptr; + } + } else { + MOZ_ASSERT(aData.IsDirectory()); + aData.GetAsDirectory()->GetFullRealPath(path); } nsCOMPtr localFile; - rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true, - getter_AddRefs(localFile)); - if (NS_WARN_IF(rv.Failed())) { - rv.SuppressException(); + nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true, + getter_AddRefs(localFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } return localFile.forget(); } +void +GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData, + nsAString& aName) +{ + if (aData.IsFile()) { + aData.GetAsFile()->GetName(aName); + } else { + MOZ_ASSERT(aData.IsDirectory()); + ErrorResult rv; + aData.GetAsDirectory()->GetName(aName, rv); + NS_WARN_IF(rv.Failed()); + } +} + +void +GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData, + nsAString& aPath, + ErrorResult& aRv) +{ + if (aData.IsFile()) { + aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv); + } else { + MOZ_ASSERT(aData.IsDirectory()); + aData.GetAsDirectory()->GetFullRealPath(aPath); + } +} + } // namespace @@ -383,7 +462,7 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult) mFilePicker->GetMode(&mode); // Collect new selected filenames - nsTArray> newFiles; + nsTArray newFilesOrDirectories; if (mode == static_cast(nsIFilePicker::modeOpenMultiple)) { nsCOMPtr iter; nsresult rv = @@ -402,9 +481,12 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult) nsCOMPtr domBlob = do_QueryInterface(tmp); MOZ_ASSERT(domBlob, "Null file object from FilePicker's file enumerator?"); - if (domBlob) { - newFiles.AppendElement(static_cast(domBlob.get())); + if (!domBlob) { + continue; } + + OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement(); + element->SetAsFile() = static_cast(domBlob.get()); } } else { MOZ_ASSERT(mode == static_cast(nsIFilePicker::modeOpen) || @@ -416,16 +498,25 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult) nsCOMPtr blob = do_QueryInterface(tmp); if (blob) { RefPtr file = static_cast(blob.get())->ToFile(); - newFiles.AppendElement(file); + MOZ_ASSERT(file); + + OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement(); + element->SetAsFile() = file; + } else if (tmp) { + RefPtr directory = static_cast(tmp.get()); + OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement(); + element->SetAsDirectory() = directory; } } - if (newFiles.IsEmpty()) { + if (newFilesOrDirectories.IsEmpty()) { return NS_OK; } // Store the last used directory using the content pref service: - nsCOMPtr file = DOMFileToLocalFile(newFiles[0]); + nsCOMPtr file = + DOMFileOrDirectoryToLocalFile(newFilesOrDirectories[0]); + if (file) { nsCOMPtr lastUsedDir; file->GetParent(getter_AddRefs(lastUsedDir)); @@ -436,7 +527,7 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult) // The text control frame (if there is one) isn't going to send a change // event because it will think this is done by a script. // So, we can safely send one by ourself. - mInput->SetFiles(newFiles, true); + mInput->SetFilesOrDirectories(newFilesOrDirectories, true); return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(), static_cast(mInput.get()), NS_LITERAL_STRING("change"), true, @@ -672,7 +763,8 @@ HTMLInputElement::InitFilePicker(FilePickerType aType) // Set default directry and filename nsAutoString defaultName; - const nsTArray>& oldFiles = GetFilesInternal(); + const nsTArray& oldFiles = + GetFilesOrDirectoriesInternal(); nsCOMPtr callback = new HTMLInputElement::nsFilePickerShownCallback(this, filePicker); @@ -681,18 +773,10 @@ HTMLInputElement::InitFilePicker(FilePickerType aType) aType != FILE_PICKER_DIRECTORY) { nsString path; - ErrorResult error; - oldFiles[0]->GetMozFullPathInternal(path, error); - if (NS_WARN_IF(error.Failed())) { - return error.StealNSResult(); - } - - nsCOMPtr localFile; - rv = NS_NewLocalFile(path, false, getter_AddRefs(localFile)); - - if (NS_SUCCEEDED(rv)) { + nsCOMPtr localFile = DOMFileOrDirectoryToLocalFile(oldFiles[0]); + if (localFile) { nsCOMPtr parentFile; - rv = localFile->GetParent(getter_AddRefs(parentFile)); + nsresult rv = localFile->GetParent(getter_AddRefs(parentFile)); if (NS_SUCCEEDED(rv)) { filePicker->SetDisplayDirectory(parentFile); } @@ -703,7 +787,8 @@ HTMLInputElement::InitFilePicker(FilePickerType aType) // one file was selected before. if (oldFiles.Length() == 1) { nsAutoString leafName; - oldFiles[0]->GetName(leafName); + GetDOMFileOrDirectoryName(oldFiles[0], leafName); + if (!leafName.IsEmpty()) { filePicker->SetDefaultString(leafName); } @@ -756,7 +841,7 @@ UploadLastDir::FetchDirectoryAndDisplayPicker(nsIDocument* aDoc, NS_PRECONDITION(docURI, "docURI is null"); nsCOMPtr loadContext = aDoc->GetLoadContext(); - nsCOMPtr prefCallback = + nsCOMPtr prefCallback = new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback); #ifdef MOZ_B2G @@ -936,7 +1021,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement, if (tmp->IsSingleLineTextControl(false)) { tmp->mInputData.mState->Traverse(cb); } - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesAndDirectoriesPromise) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -945,7 +1030,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement, nsGenericHTMLFormElementWithState) NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity) NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mFiles) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesAndDirectoriesPromise) if (tmp->IsSingleLineTextControl(false)) { @@ -1004,8 +1089,8 @@ HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) co // we can just grab the pretty string and use it as wallpaper GetDisplayFileName(it->mStaticDocFileList); } else { - it->mFiles.Clear(); - it->mFiles.AppendElements(mFiles); + it->mFilesOrDirectories.Clear(); + it->mFilesOrDirectories.AppendElements(mFilesOrDirectories); } break; case VALUE_MODE_DEFAULT_ON: @@ -1454,9 +1539,9 @@ HTMLInputElement::GetValueInternal(nsAString& aValue) const #else // XXX We'd love to assert that this can't happen, but some mochitests // use SpecialPowers to circumvent our more sane security model. - if (!mFiles.IsEmpty()) { + if (!mFilesOrDirectories.IsEmpty()) { ErrorResult rv; - mFiles[0]->GetMozFullPath(aValue, rv); + GetDOMFileOrDirectoryPath(mFilesOrDirectories[0], aValue, rv); if (NS_WARN_IF(rv.Failed())) { return rv.StealNSResult(); } @@ -1468,10 +1553,10 @@ HTMLInputElement::GetValueInternal(nsAString& aValue) const #endif } else { // Just return the leaf name - if (mFiles.IsEmpty()) { + if (mFilesOrDirectories.IsEmpty()) { aValue.Truncate(); } else { - mFiles[0]->GetName(aValue); + GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue); } } @@ -1506,8 +1591,8 @@ HTMLInputElement::IsValueEmpty() const void HTMLInputElement::ClearFiles(bool aSetValueChanged) { - nsTArray> files; - SetFiles(files, aSetValueChanged); + nsTArray data; + SetFilesOrDirectories(data, aSetValueChanged); } /* static */ Decimal @@ -2077,9 +2162,9 @@ void HTMLInputElement::MozGetFileNameArray(nsTArray& aArray, ErrorResult& aRv) { - for (uint32_t i = 0; i < mFiles.Length(); i++) { - nsString str; - mFiles[i]->GetMozFullPathInternal(str, aRv); + for (uint32_t i = 0; i < mFilesOrDirectories.Length(); i++) { + nsAutoString str; + GetDOMFileOrDirectoryPath(mFilesOrDirectories[i], str, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } @@ -2129,25 +2214,29 @@ HTMLInputElement::MozSetFileArray(const Sequence>& aFiles) if (!global) { return; } - nsTArray> files; + + nsTArray files; for (uint32_t i = 0; i < aFiles.Length(); ++i) { RefPtr file = File::Create(global, aFiles[i].get()->Impl()); MOZ_ASSERT(file); - files.AppendElement(file); + OwningFileOrDirectory* element = files.AppendElement(); + element->SetAsFile() = file; } - SetFiles(files, true); + + SetFilesOrDirectories(files, true); } void -HTMLInputElement::MozSetFileNameArray(const Sequence< nsString >& aFileNames, ErrorResult& aRv) +HTMLInputElement::MozSetFileNameArray(const Sequence& aFileNames, + ErrorResult& aRv) { if (XRE_IsContentProcess()) { aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } - nsTArray> files; + nsTArray files; for (uint32_t i = 0; i < aFileNames.Length(); ++i) { nsCOMPtr file; @@ -2164,21 +2253,28 @@ HTMLInputElement::MozSetFileNameArray(const Sequence< nsString >& aFileNames, Er NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file)); } - if (file) { - nsCOMPtr global = OwnerDoc()->GetScopeObject(); - RefPtr domFile = File::CreateFromFile(global, file); - files.AppendElement(domFile); - } else { + if (!file) { continue; // Not much we can do if the file doesn't exist } + nsCOMPtr global = OwnerDoc()->GetScopeObject(); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + RefPtr domFile = File::CreateFromFile(global, file); + + OwningFileOrDirectory* element = files.AppendElement(); + element->SetAsFile() = domFile; } - SetFiles(files, true); + SetFilesOrDirectories(files, true); } NS_IMETHODIMP -HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, uint32_t aLength) +HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, + uint32_t aLength) { if (!nsContentUtils::IsCallerChrome()) { // setting the value of a "FILE" input widget requires chrome privilege @@ -2200,6 +2296,34 @@ HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, uint32_t aLen return rv.StealNSResult(); } +void +HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath, + ErrorResult& aRv) +{ + nsCOMPtr file; + NS_ConvertUTF16toUTF8 path(aDirectoryPath); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + nsPIDOMWindow* window = OwnerDoc()->GetInnerWindow(); + if (NS_WARN_IF(!window)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + RefPtr directory = Directory::Create(window, file, + Directory::eDOMRootDirectory); + MOZ_ASSERT(directory); + + nsTArray array; + OwningFileOrDirectory* element = array.AppendElement(); + element->SetAsDirectory() = directory; + + SetFilesOrDirectories(array, true); +} + bool HTMLInputElement::MozIsTextField(bool aExcludePassword) { @@ -2391,14 +2515,14 @@ HTMLInputElement::GetDisplayFileName(nsAString& aValue) const return; } - if (mFiles.Length() == 1) { - mFiles[0]->GetName(aValue); + if (mFilesOrDirectories.Length() == 1) { + GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue); return; } nsXPIDLString value; - if (mFiles.IsEmpty()) { + if (mFilesOrDirectories.IsEmpty()) { if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) { nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, "NoFilesSelected", value); @@ -2408,7 +2532,7 @@ HTMLInputElement::GetDisplayFileName(nsAString& aValue) const } } else { nsString count; - count.AppendInt(int(mFiles.Length())); + count.AppendInt(int(mFilesOrDirectories.Length())); const char16_t* params[] = { count.get() }; nsContentUtils::FormatLocalizedString(nsContentUtils::eFORMS_PROPERTIES, @@ -2419,13 +2543,13 @@ HTMLInputElement::GetDisplayFileName(nsAString& aValue) const } void -HTMLInputElement::SetFiles(const nsTArray>& aFiles, - bool aSetValueChanged) +HTMLInputElement::SetFilesOrDirectories(const nsTArray& aFilesOrDirectories, + bool aSetValueChanged) { - mFiles.Clear(); - mFiles.AppendElements(aFiles); + mFilesOrDirectories.Clear(); + mFilesOrDirectories.AppendElements(aFilesOrDirectories); - AfterSetFiles(aSetValueChanged); + AfterSetFilesOrDirectories(aSetValueChanged); } void @@ -2433,22 +2557,22 @@ HTMLInputElement::SetFiles(nsIDOMFileList* aFiles, bool aSetValueChanged) { RefPtr files = static_cast(aFiles); - mFiles.Clear(); + mFilesOrDirectories.Clear(); if (aFiles) { uint32_t listLength; aFiles->GetLength(&listLength); for (uint32_t i = 0; i < listLength; i++) { - RefPtr file = files->Item(i); - mFiles.AppendElement(file); + OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement(); + *element = files->UnsafeItem(i); } } - AfterSetFiles(aSetValueChanged); + AfterSetFilesOrDirectories(aSetValueChanged); } void -HTMLInputElement::AfterSetFiles(bool aSetValueChanged) +HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged) { // No need to flush here, if there's no frame at this point we // don't need to force creation of one just to tell it about this @@ -2466,11 +2590,11 @@ HTMLInputElement::AfterSetFiles(bool aSetValueChanged) // call under GetMozFullPath won't be rejected for not being urgent. // XXX Protected by the ifndef because the blob code doesn't allow us to send // this message in b2g. - if (mFiles.IsEmpty()) { + if (mFilesOrDirectories.IsEmpty()) { mFirstFilePath.Truncate(); } else { ErrorResult rv; - mFiles[0]->GetMozFullPath(mFirstFilePath, rv); + GetDOMFileOrDirectoryPath(mFilesOrDirectories[0], mFirstFilePath, rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); } @@ -2544,24 +2668,27 @@ HTMLInputElement::HandleNumberControlSpin(void* aData) } } -nsresult +void HTMLInputElement::UpdateFileList() { if (mFileList) { mFileList->Clear(); - const nsTArray>& files = GetFilesInternal(); - for (uint32_t i = 0; i < files.Length(); ++i) { - if (!mFileList->Append(files[i])) { - return NS_ERROR_FAILURE; + const nsTArray& array = + GetFilesOrDirectoriesInternal(); + + for (uint32_t i = 0; i < array.Length(); ++i) { + if (array[i].IsFile()) { + mFileList->Append(array[i].GetAsFile()); + } else { + MOZ_ASSERT(array[i].IsDirectory()); + mFileList->Append(array[i].GetAsDirectory()); } } } // Make sure we (lazily) create a new Promise for GetFilesAndDirectories: mFilesAndDirectoriesPromise = nullptr; - - return NS_OK; } nsresult @@ -3990,7 +4117,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame()); if (numberControlFrame) { - if (aVisitor.mEvent->mMessage == eMouseDown && + if (aVisitor.mEvent->mMessage == eMouseDown && IsMutable()) { switch (numberControlFrame->GetSpinButtonForPointerEvent( aVisitor.mEvent->AsMouseEvent())) { @@ -4359,8 +4486,8 @@ HTMLInputElement::HandleTypeChange(uint8_t aNewType) // previous type does, we should clear out mFocusedValue. if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) { GetValue(mFocusedValue); - } else if (!IsSingleLineTextControl(mType, false) && - IsSingleLineTextControl(oldType, false)) { + } else if (!IsSingleLineTextControl(false, mType) && + IsSingleLineTextControl(false, oldType)) { mFocusedValue.Truncate(); } @@ -4541,7 +4668,7 @@ HTMLInputElement::GetValueAsDate(const nsAString& aValue, } uint32_t endOfYearOffset = aValue.Length() - 6; - + if (aValue[endOfYearOffset] != '-' || aValue[endOfYearOffset + 3] != '-') { return false; @@ -4909,45 +5036,38 @@ HTMLInputElement::GetFilesAndDirectories(ErrorResult& aRv) return nullptr; } - const nsTArray>& filesAndDirs = GetFilesInternal(); + const nsTArray& filesAndDirs = + GetFilesOrDirectoriesInternal(); Sequence filesAndDirsSeq; - if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(), mozilla::fallible_t())) { + if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(), + mozilla::fallible_t())) { p->MaybeReject(NS_ERROR_OUT_OF_MEMORY); return p.forget(); } for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) { - if (filesAndDirs[i]->IsDirectory()) { + if (filesAndDirs[i].IsDirectory()) { #if defined(ANDROID) || defined(MOZ_B2G) MOZ_ASSERT(false, "Directory picking should have been redirected to normal " "file picking for platforms that don't have a directory " "picker"); #endif - nsAutoString path; - filesAndDirs[i]->GetMozFullPathInternal(path, aRv); - if (aRv.Failed()) { - return nullptr; - } - int32_t leafSeparatorIndex = path.RFind(FILE_PATH_SEPARATOR); - nsDependentSubstring dirname = Substring(path, 0, leafSeparatorIndex); - RefPtr fs = new OSFileSystem(dirname); - fs->Init(OwnerDoc()->GetInnerWindow()); + RefPtr directory = filesAndDirs[i].GetAsDirectory(); - nsAutoString dompath(NS_LITERAL_STRING(FILESYSTEM_DOM_PATH_SEPARATOR)); - dompath.Append(Substring(path, leafSeparatorIndex + 1)); - RefPtr directory = new Directory(fs, dompath); // In future we could refactor SetFilePickerFiltersFromAccept to return a // semicolon separated list of file extensions and include that in the // filter string passed here. directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive")); filesAndDirsSeq[i].SetAsDirectory() = directory; } else { + MOZ_ASSERT(filesAndDirs[i].IsFile()); + // This file was directly selected by the user, so don't filter it. - filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i]; + filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile(); } } @@ -5581,17 +5701,19 @@ HTMLInputElement::SubmitNamesValues(nsFormSubmission* aFormSubmission) if (mType == NS_FORM_INPUT_FILE) { // Submit files - const nsTArray>& files = GetFilesInternal(); + const nsTArray& files = + GetFilesOrDirectoriesInternal(); + bool hasBlobs = false; for (uint32_t i = 0; i < files.Length(); ++i) { - aFormSubmission->AddNameBlobPair(name, files[i]); + if (files[i].IsFile()) { + hasBlobs = true; + aFormSubmission->AddNameBlobOrNullPair(name, files[i].GetAsFile()); + } } - if (files.IsEmpty()) { - RefPtr blobImpl = - new EmptyBlobImpl(NS_LITERAL_STRING("application/octet-stream")); - RefPtr blob = Blob::Create(OwnerDoc()->GetInnerWindow(), blobImpl); - aFormSubmission->AddNameBlobPair(name, blob); + if (!hasBlobs) { + aFormSubmission->AddNameBlobOrNullPair(name, nullptr); } return NS_OK; @@ -5624,9 +5746,9 @@ HTMLInputElement::SaveState() } break; case VALUE_MODE_FILENAME: - if (!mFiles.IsEmpty()) { + if (!mFilesOrDirectories.IsEmpty()) { inputState = new HTMLInputElementState(); - inputState->SetBlobImpls(mFiles); + inputState->SetFilesOrDirectories(mFilesOrDirectories); } break; case VALUE_MODE_VALUE: @@ -5795,7 +5917,7 @@ HTMLInputElement::AddStates(EventStates aStates) } } } - nsGenericHTMLFormElementWithState::AddStates(aStates); + nsGenericHTMLFormElementWithState::AddStates(aStates); } void @@ -5832,20 +5954,13 @@ HTMLInputElement::RestoreState(nsPresState* aState) break; case VALUE_MODE_FILENAME: { - const nsTArray>& blobImpls = inputState->GetBlobImpls(); + nsPIDOMWindow* window = OwnerDoc()->GetInnerWindow(); + if (window) { + nsTArray array; + inputState->GetFilesOrDirectories(window, array); - nsCOMPtr global = OwnerDoc()->GetScopeObject(); - MOZ_ASSERT(global); - - nsTArray> files; - for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) { - RefPtr file = File::Create(global, blobImpls[i]); - MOZ_ASSERT(file); - - files.AppendElement(file); + SetFilesOrDirectories(array, true); } - - SetFiles(files, true); } break; case VALUE_MODE_VALUE: @@ -6355,15 +6470,15 @@ HTMLInputElement::IsValueMissing() const switch (GetValueMode()) { case VALUE_MODE_VALUE: return IsValueEmpty(); + case VALUE_MODE_FILENAME: - { - const nsTArray>& files = GetFilesInternal(); - return files.IsEmpty(); - } + return GetFilesOrDirectoriesInternal().IsEmpty(); + case VALUE_MODE_DEFAULT_ON: // This should not be used for type radio. // See the MOZ_ASSERT at the beginning of the method. return !mChecked; + case VALUE_MODE_DEFAULT: default: return false; diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 7a297ada0b..ca8cc4f7c8 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -20,6 +20,7 @@ #include "mozilla/dom/HTMLFormElement.h" // for HasEverTriedInvalidSubmit() #include "mozilla/dom/HTMLInputElementBinding.h" #include "mozilla/dom/Promise.h" +#include "mozilla/dom/UnionTypes.h" #include "nsIFilePicker.h" #include "nsIContentPrefService2.h" #include "mozilla/Decimal.h" @@ -220,12 +221,13 @@ public: void GetDisplayFileName(nsAString& aFileName) const; - const nsTArray>& GetFilesInternal() const + const nsTArray& GetFilesOrDirectoriesInternal() const { - return mFiles; + return mFilesOrDirectories; } - void SetFiles(const nsTArray>& aFiles, bool aSetValueChanged); + void SetFilesOrDirectories(const nsTArray& aFilesOrDirectories, + bool aSetValueChanged); void SetFiles(nsIDOMFileList* aFiles, bool aSetValueChanged); // Called when a nsIFilePicker or a nsIColorPicker terminate. @@ -727,6 +729,7 @@ public: void MozSetFileNameArray(const Sequence< nsString >& aFileNames, ErrorResult& aRv); void MozSetFileArray(const Sequence>& aFiles); + void MozSetDirectory(const nsAString& aDirectoryPath, ErrorResult& aRv); HTMLInputElement* GetOwnerNumberControl(); @@ -932,12 +935,12 @@ protected: /** * Update mFileList with the currently selected file. */ - nsresult UpdateFileList(); + void UpdateFileList(); /** - * Called after calling one of the SetFiles() functions. + * Called after calling one of the SetFilesOrDirectories() functions. */ - void AfterSetFiles(bool aSetValueChanged); + void AfterSetFilesOrDirectories(bool aSetValueChanged); /** * Determine whether the editor needs to be initialized explicitly for @@ -1281,16 +1284,16 @@ protected: } mInputData; /** - * The value of the input if it is a file input. This is the list of filenames - * used when uploading a file. It is vital that this is kept separate from - * mValue so that it won't be possible to 'leak' the value from a text-input - * to a file-input. Additionally, the logic for this value is kept as simple - * as possible to avoid accidental errors where the wrong filename is used. - * Therefor the list of filenames is always owned by this member, never by - * the frame. Whenever the frame wants to change the filename it has to call - * SetFileNames to update this member. + * The value of the input if it is a file input. This is the list of files or + * directories DOM objects used when uploading a file. It is vital that this + * is kept separate from mValue so that it won't be possible to 'leak' the + * value from a text-input to a file-input. Additionally, the logic for this + * value is kept as simple as possible to avoid accidental errors where the + * wrong filename is used. Therefor the list of filenames is always owned by + * this member, never by the frame. Whenever the frame wants to change the + * filename it has to call SetFilesOrDirectories to update this member. */ - nsTArray> mFiles; + nsTArray mFilesOrDirectories; #ifndef MOZ_CHILD_PERMISSIONS /** diff --git a/dom/html/nsFormSubmission.cpp b/dom/html/nsFormSubmission.cpp index cda4979cec..8c1b8c815c 100644 --- a/dom/html/nsFormSubmission.cpp +++ b/dom/html/nsFormSubmission.cpp @@ -57,7 +57,9 @@ SendJSWarning(nsIDocument* aDocument, static void RetrieveFileName(Blob* aBlob, nsAString& aFilename) { - MOZ_ASSERT(aBlob); + if (!aBlob) { + return; + } RefPtr file = aBlob->ToFile(); if (file) { @@ -88,8 +90,8 @@ public: virtual nsresult AddNameValuePair(const nsAString& aName, const nsAString& aValue) override; - virtual nsresult AddNameBlobPair(const nsAString& aName, - Blob* aBlob) override; + virtual nsresult AddNameBlobOrNullPair(const nsAString& aName, + Blob* aBlob) override; virtual nsresult GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override; @@ -175,11 +177,9 @@ nsFSURLEncoded::AddIsindex(const nsAString& aValue) } nsresult -nsFSURLEncoded::AddNameBlobPair(const nsAString& aName, - Blob* aBlob) +nsFSURLEncoded::AddNameBlobOrNullPair(const nsAString& aName, + Blob* aBlob) { - MOZ_ASSERT(aBlob); - if (!mWarnedFileControl) { SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nullptr, 0); mWarnedFileControl = true; @@ -436,7 +436,7 @@ nsFSMultipartFormData::AddNameValuePair(const nsAString& aName, // Make MIME block for name/value pair // XXX: name parameter should be encoded per RFC 2231 - // RFC 2388 specifies that RFC 2047 be used, but I think it's not + // RFC 2388 specifies that RFC 2047 be used, but I think it's not // consistent with MIME standard. mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary + NS_LITERAL_CSTRING(CRLF) @@ -448,66 +448,69 @@ nsFSMultipartFormData::AddNameValuePair(const nsAString& aName, } nsresult -nsFSMultipartFormData::AddNameBlobPair(const nsAString& aName, - Blob* aBlob) +nsFSMultipartFormData::AddNameBlobOrNullPair(const nsAString& aName, + Blob* aBlob) { - MOZ_ASSERT(aBlob); - // Encode the control name nsAutoCString nameStr; nsresult rv = EncodeVal(aName, nameStr, true); NS_ENSURE_SUCCESS(rv, rv); - nsAutoString filename16; - RetrieveFileName(aBlob, filename16); + nsAutoCString filename; + nsAutoCString contentType; + nsCOMPtr fileStream; - ErrorResult error; - nsAutoString filepath16; - RefPtr file = aBlob->ToFile(); - if (file) { - file->GetPath(filepath16, error); + if (aBlob) { + nsAutoString filename16; + RetrieveFileName(aBlob, filename16); + + ErrorResult error; + nsAutoString filepath16; + RefPtr file = aBlob->ToFile(); + if (file) { + file->GetPath(filepath16, error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + } + + if (!filepath16.IsEmpty()) { + // File.path includes trailing "/" + filename16 = filepath16 + filename16; + } + + rv = EncodeVal(filename16, filename, true); + NS_ENSURE_SUCCESS(rv, rv); + + // Get content type + nsAutoString contentType16; + aBlob->GetType(contentType16); + if (contentType16.IsEmpty()) { + contentType16.AssignLiteral("application/octet-stream"); + } + + contentType.Adopt(nsLinebreakConverter:: + ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(), + nsLinebreakConverter::eLinebreakAny, + nsLinebreakConverter::eLinebreakSpace)); + + // Get input stream + aBlob->GetInternalStream(getter_AddRefs(fileStream), error); if (NS_WARN_IF(error.Failed())) { return error.StealNSResult(); } - } - if (!filepath16.IsEmpty()) { - // File.path includes trailing "/" - filename16 = filepath16 + filename16; - } + if (fileStream) { + // Create buffered stream (for efficiency) + nsCOMPtr bufferedStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), + fileStream, 8192); + NS_ENSURE_SUCCESS(rv, rv); - nsAutoCString filename; - rv = EncodeVal(filename16, filename, true); - NS_ENSURE_SUCCESS(rv, rv); - - // Get content type - nsAutoString contentType16; - aBlob->GetType(contentType16); - if (contentType16.IsEmpty()) { - contentType16.AssignLiteral("application/octet-stream"); - } - - nsAutoCString contentType; - contentType.Adopt(nsLinebreakConverter:: - ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(), - nsLinebreakConverter::eLinebreakAny, - nsLinebreakConverter::eLinebreakSpace)); - - // Get input stream - nsCOMPtr fileStream; - aBlob->GetInternalStream(getter_AddRefs(fileStream), error); - if (NS_WARN_IF(error.Failed())) { - return error.StealNSResult(); - } - - if (fileStream) { - // Create buffered stream (for efficiency) - nsCOMPtr bufferedStream; - rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), - fileStream, 8192); - NS_ENSURE_SUCCESS(rv, rv); - - fileStream = bufferedStream; + fileStream = bufferedStream; + } + } else { + contentType.AssignLiteral("application/octet-stream"); } // @@ -517,7 +520,7 @@ nsFSMultipartFormData::AddNameBlobPair(const nsAString& aName, mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary + NS_LITERAL_CSTRING(CRLF); // XXX: name/filename parameter should be encoded per RFC 2231 - // RFC 2388 specifies that RFC 2047 be used, but I think it's not + // RFC 2388 specifies that RFC 2047 be used, but I think it's not // consistent with the MIME standard. mPostDataChunk += NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"") @@ -576,7 +579,7 @@ nsresult nsFSMultipartFormData::AddPostDataStream() { nsresult rv = NS_OK; - + nsCOMPtr postDataChunkStream; rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream), mPostDataChunk); @@ -603,8 +606,8 @@ public: virtual nsresult AddNameValuePair(const nsAString& aName, const nsAString& aValue) override; - virtual nsresult AddNameBlobPair(const nsAString& aName, - Blob* aBlob) override; + virtual nsresult AddNameBlobOrNullPair(const nsAString& aName, + Blob* aBlob) override; virtual nsresult GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override; @@ -627,11 +630,9 @@ nsFSTextPlain::AddNameValuePair(const nsAString& aName, } nsresult -nsFSTextPlain::AddNameBlobPair(const nsAString& aName, - Blob* aBlob) +nsFSTextPlain::AddNameBlobOrNullPair(const nsAString& aName, + Blob* aBlob) { - MOZ_ASSERT(aBlob); - nsAutoString filename; RetrieveFileName(aBlob, filename); AddNameValuePair(aName, filename); diff --git a/dom/html/nsFormSubmission.h b/dom/html/nsFormSubmission.h index 700efad22d..98e38842db 100644 --- a/dom/html/nsFormSubmission.h +++ b/dom/html/nsFormSubmission.h @@ -49,10 +49,11 @@ public: * * @param aName the name of the parameter * @param aBlob the blob to submit. The file's name will be used if the Blob - * is actually a File, otherwise 'blob' string is used instead. + * is actually a File, otherwise 'blob' string is used instead if the aBlob is + * not null. */ - virtual nsresult AddNameBlobPair(const nsAString& aName, - mozilla::dom::Blob* aBlob) = 0; + virtual nsresult AddNameBlobOrNullPair(const nsAString& aName, + mozilla::dom::Blob* aBlob) = 0; /** * Reports whether the instance supports AddIsindex(). @@ -160,8 +161,8 @@ public: virtual nsresult AddNameValuePair(const nsAString& aName, const nsAString& aValue) override; - virtual nsresult AddNameBlobPair(const nsAString& aName, - mozilla::dom::Blob* aBlob) override; + virtual nsresult AddNameBlobOrNullPair(const nsAString& aName, + mozilla::dom::Blob* aBlob) override; virtual nsresult GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override; diff --git a/dom/html/test/test_formSubmission.html b/dom/html/test/test_formSubmission.html index 52900d3a19..0edfeaeb85 100644 --- a/dom/html/test/test_formSubmission.html +++ b/dom/html/test/test_formSubmission.html @@ -590,7 +590,7 @@ var expectedAugment = [ //{ name: "aNameUndef", value: "undefined" }, ]; -function checkMPSubmission(sub, expected, test, isFormData = false) { +function checkMPSubmission(sub, expected, test) { function getPropCount(o) { var x, l = 0; for (x in o) ++l; @@ -625,7 +625,7 @@ function checkMPSubmission(sub, expected, test, isFormData = false) { else { is(sub[i].headers["Content-Disposition"], "form-data; name=\"" + mpquote(expected[i].name) + "\"; filename=\"" + - mpquote(expected[i].fileName != "" || !isFormData ? expected[i].fileName : "blob") + "\"", + mpquote(expected[i].fileName) + "\"", "Correct name in " + test); is(sub[i].headers["Content-Type"], expected[i].contentType, @@ -782,14 +782,14 @@ function runTest() { xhr.open("POST", "form_submit_server.sjs"); xhr.send(new FormData(form)); yield undefined; // Wait for XHR load - checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData", true); + checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData"); // Send disabled form using XHR and FormData setDisabled(document.querySelectorAll("input, select, textarea"), true); xhr.open("POST", "form_submit_server.sjs"); xhr.send(new FormData(form)); yield undefined; - checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData", true); + checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData"); setDisabled(document.querySelectorAll("input, select, textarea"), false); // Send FormData @@ -804,7 +804,7 @@ function runTest() { xhr.open("POST", "form_submit_server.sjs"); xhr.send(fd); yield undefined; - checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData", true); + checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData"); // Augment
using FormData fd = new FormData(form); @@ -813,7 +813,7 @@ function runTest() { xhr.send(fd); yield undefined; checkMPSubmission(JSON.parse(xhr.responseText), - expectedSub.concat(expectedAugment), "send augmented FormData", true); + expectedSub.concat(expectedAugment), "send augmented FormData"); SimpleTest.finish(); yield undefined; diff --git a/dom/indexedDB/FileSnapshot.h b/dom/indexedDB/FileSnapshot.h index d6a3dad001..a5351d8d2b 100644 --- a/dom/indexedDB/FileSnapshot.h +++ b/dom/indexedDB/FileSnapshot.h @@ -187,30 +187,6 @@ private: return mBlobImpl->IsFile(); } - virtual void - LookupAndCacheIsDirectory() override - { - mBlobImpl->LookupAndCacheIsDirectory(); - } - - virtual void - SetIsDirectory(bool aIsDir) override - { - return mBlobImpl->SetIsDirectory(aIsDir); - } - - virtual bool - IsDirectory() const override - { - return mBlobImpl->IsDirectory(); - } - - virtual BlobDirState - GetDirState() const override - { - return mBlobImpl->GetDirState(); - } - virtual bool MayBeClonedToOtherThreads() const override { diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 5f294dc0e7..24bcb606ad 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -444,8 +444,7 @@ ResolveMysteryFile(BlobImpl* aImpl, BlobChild* actor = ActorFromRemoteBlobImpl(aImpl); if (actor) { return actor->SetMysteryBlobInfo(aName, aContentType, - aSize, aLastModifiedDate, - BlobDirState::eUnknownIfDir); + aSize, aLastModifiedDate); } return true; } diff --git a/dom/ipc/Blob.cpp b/dom/ipc/Blob.cpp index 1abeaf8c8b..c0a9bdb877 100644 --- a/dom/ipc/Blob.cpp +++ b/dom/ipc/Blob.cpp @@ -670,8 +670,7 @@ public: EmptyBlobImpl(const nsAString& aName, const nsAString& aContentType, int64_t aLastModifiedDate) - : BlobImplBase(aName, aContentType, 0, aLastModifiedDate, - BlobDirState::eIsNotDir) + : BlobImplBase(aName, aContentType, 0, aLastModifiedDate) { mImmutable = true; } @@ -716,14 +715,12 @@ struct MOZ_STACK_CLASS CreateBlobImplMetadata final nsString mName; uint64_t mLength; int64_t mLastModifiedDate; - BlobDirState mDirState; bool mHasRecursed; const bool mIsSameProcessActor; explicit CreateBlobImplMetadata(bool aIsSameProcessActor) : mLength(0) , mLastModifiedDate(0) - , mDirState(BlobDirState::eUnknownIfDir) , mHasRecursed(false) , mIsSameProcessActor(aIsSameProcessActor) { @@ -965,7 +962,6 @@ CreateBlobImpl(const ParentBlobConstructorParams& aParams, metadata.mName = params.name(); metadata.mLength = params.length(); metadata.mLastModifiedDate = params.modDate(); - metadata.mDirState = BlobDirState(params.dirState()); } RefPtr blobImpl = @@ -1775,7 +1771,6 @@ public: const nsAString& aContentType, uint64_t aLength, int64_t aModDate, - BlobDirState aDirState, bool aIsSameProcessBlob); // For Blob. @@ -2053,18 +2048,6 @@ public: virtual bool IsFile() const override; - virtual void - LookupAndCacheIsDirectory() override; - - virtual void - SetIsDirectory(bool aIsDir) override; - - virtual bool - IsDirectory() const override; - - virtual BlobDirState - GetDirState() const override; - virtual bool MayBeClonedToOtherThreads() const override; @@ -2096,9 +2079,8 @@ RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor, const nsAString& aContentType, uint64_t aLength, int64_t aModDate, - BlobDirState aDirState, bool aIsSameProcessBlob) - : BlobImplBase(aName, aContentType, aLength, aModDate, aDirState) + : BlobImplBase(aName, aContentType, aLength, aModDate) , mIsSlice(false) { if (aIsSameProcessBlob) { @@ -2814,34 +2796,6 @@ RemoteBlobImpl::IsFile() const return mBlobImpl->IsFile(); } -void -BlobParent:: -RemoteBlobImpl::LookupAndCacheIsDirectory() -{ - return mBlobImpl->LookupAndCacheIsDirectory(); -} - -void -BlobParent:: -RemoteBlobImpl::SetIsDirectory(bool aIsDir) -{ - return mBlobImpl->SetIsDirectory(aIsDir); -} - -bool -BlobParent:: -RemoteBlobImpl::IsDirectory() const -{ - return mBlobImpl->IsDirectory(); -} - -BlobDirState -BlobParent:: -RemoteBlobImpl::GetDirState() const -{ - return mBlobImpl->GetDirState(); -} - bool BlobParent:: RemoteBlobImpl::MayBeClonedToOtherThreads() const @@ -3033,8 +2987,7 @@ BlobChild::CommonInit(BlobChild* aOther, BlobImpl* aBlobImpl) MOZ_ASSERT(!rv.Failed()); remoteBlob = new RemoteBlobImpl(this, otherImpl, name, contentType, length, - modDate, otherImpl->GetDirState(), - false /* SameProcessBlobImpl */); + modDate, false /* SameProcessBlobImpl */); } else { remoteBlob = new RemoteBlobImpl(this, otherImpl, contentType, length, false /* SameProcessBlobImpl */); @@ -3086,7 +3039,6 @@ BlobChild::CommonInit(const ChildBlobConstructorParams& aParams) params.contentType(), params.length(), params.modDate(), - BlobDirState(params.dirState()), false /* SameProcessBlobImpl */); break; } @@ -3122,7 +3074,6 @@ BlobChild::CommonInit(const ChildBlobConstructorParams& aParams) contentType, size, lastModifiedDate, - blobImpl->GetDirState(), true /* SameProcessBlobImpl */); } else { remoteBlob = new RemoteBlobImpl(this, blobImpl, contentType, size, @@ -3303,8 +3254,7 @@ BlobChild::GetOrCreateFromImpl(ChildManagerType* aManager, MOZ_ASSERT(!rv.Failed()); blobParams = - FileBlobConstructorParams(name, contentType, length, modDate, - aBlobImpl->GetDirState(), blobData); + FileBlobConstructorParams(name, contentType, length, modDate, blobData); } else { blobParams = NormalBlobConstructorParams(contentType, length, blobData); } @@ -3477,8 +3427,7 @@ bool BlobChild::SetMysteryBlobInfo(const nsString& aName, const nsString& aContentType, uint64_t aLength, - int64_t aLastModifiedDate, - BlobDirState aDirState) + int64_t aLastModifiedDate) { AssertIsOnOwningThread(); MOZ_ASSERT(mBlobImpl); @@ -3491,7 +3440,6 @@ BlobChild::SetMysteryBlobInfo(const nsString& aName, aContentType, aLength, aLastModifiedDate, - aDirState, void_t() /* optionalBlobData */); return SendResolveMystery(params); } @@ -3854,7 +3802,7 @@ BlobParent::GetOrCreateFromImpl(ParentManagerType* aManager, blobParams = FileBlobConstructorParams(name, contentType, length, modDate, - aBlobImpl->GetDirState(), void_t()); + void_t()); } else { blobParams = NormalBlobConstructorParams(contentType, length, void_t()); } diff --git a/dom/ipc/BlobChild.h b/dom/ipc/BlobChild.h index 35853f307d..affa9934aa 100644 --- a/dom/ipc/BlobChild.h +++ b/dom/ipc/BlobChild.h @@ -31,8 +31,6 @@ class ContentChild; class nsIContentChild; class PBlobStreamChild; -enum BlobDirState : uint32_t; - class BlobChild final : public PBlobChild { @@ -117,8 +115,7 @@ public: SetMysteryBlobInfo(const nsString& aName, const nsString& aContentType, uint64_t aLength, - int64_t aLastModifiedDate, - BlobDirState aDirState); + int64_t aLastModifiedDate); // Use this for non-file blobs. bool diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh index 49b4f3f242..2778642ef9 100644 --- a/dom/ipc/DOMTypes.ipdlh +++ b/dom/ipc/DOMTypes.ipdlh @@ -73,7 +73,6 @@ struct FileBlobConstructorParams nsString contentType; uint64_t length; int64_t modDate; - uint32_t dirState; // This must be of type BlobData in a child->parent message, and will always // be of type void_t in a parent->child message. diff --git a/dom/ipc/FilePickerParent.cpp b/dom/ipc/FilePickerParent.cpp index 0883269f57..f6e51255da 100644 --- a/dom/ipc/FilePickerParent.cpp +++ b/dom/ipc/FilePickerParent.cpp @@ -53,15 +53,18 @@ FilePickerParent::~FilePickerParent() // 2. The stream transport thread stat()s the file in Run() and then dispatches // the same runnable on the main thread. // 3. The main thread sends the results over IPC. -FilePickerParent::FileSizeAndDateRunnable::FileSizeAndDateRunnable(FilePickerParent *aFPParent, - nsTArray>& aBlobs) +FilePickerParent::IORunnable::IORunnable(FilePickerParent *aFPParent, + nsTArray>& aFiles, + bool aIsDirectory) : mFilePickerParent(aFPParent) + , mIsDirectory(aIsDirectory) { - mBlobs.SwapElements(aBlobs); + mFiles.SwapElements(aFiles); + MOZ_ASSERT_IF(aIsDirectory, mFiles.Length() == 1); } bool -FilePickerParent::FileSizeAndDateRunnable::Dispatch() +FilePickerParent::IORunnable::Dispatch() { MOZ_ASSERT(NS_IsMainThread()); @@ -75,23 +78,49 @@ FilePickerParent::FileSizeAndDateRunnable::Dispatch() } NS_IMETHODIMP -FilePickerParent::FileSizeAndDateRunnable::Run() +FilePickerParent::IORunnable::Run() { // If we're on the main thread, then that means we're done. Just send the // results. if (NS_IsMainThread()) { if (mFilePickerParent) { - mFilePickerParent->SendFiles(mBlobs); + mFilePickerParent->SendFilesOrDirectories(mResults); } return NS_OK; } - // We're not on the main thread, so do the stat(). - for (unsigned i = 0; i < mBlobs.Length(); i++) { - ErrorResult rv; - mBlobs[i]->GetSize(rv); - mBlobs[i]->GetLastModified(rv); - mBlobs[i]->LookupAndCacheIsDirectory(); + // We're not on the main thread, so do the IO. + + for (uint32_t i = 0; i < mFiles.Length(); ++i) { + if (mIsDirectory) { + nsAutoString path; + nsresult rv = mFiles[i]->GetPath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + BlobImplOrString* data = mResults.AppendElement(); + data->mType = BlobImplOrString::eDirectoryPath; + data->mDirectoryPath = path; + continue; + } + + RefPtr blobImpl = new BlobImplFile(mFiles[i]); + + ErrorResult error; + blobImpl->GetSize(error); + if (NS_WARN_IF(error.Failed())) { + continue; + } + + blobImpl->GetLastModified(error); + if (NS_WARN_IF(error.Failed())) { + continue; + } + + BlobImplOrString* data = mResults.AppendElement(); + data->mType = BlobImplOrString::eBlobImpl; + data->mBlobImpl = blobImpl; } // Dispatch ourselves back on the main thread. @@ -101,29 +130,46 @@ FilePickerParent::FileSizeAndDateRunnable::Run() // thread. MOZ_CRASH(); } + return NS_OK; } void -FilePickerParent::FileSizeAndDateRunnable::Destroy() +FilePickerParent::IORunnable::Destroy() { mFilePickerParent = nullptr; } void -FilePickerParent::SendFiles(const nsTArray>& aBlobs) +FilePickerParent::SendFilesOrDirectories(const nsTArray& aData) { + if (mMode == nsIFilePicker::modeGetFolder) { + MOZ_ASSERT(aData.Length() <= 1); + if (aData.IsEmpty()) { + Unused << Send__delete__(this, void_t(), mResult); + return; + } + + MOZ_ASSERT(aData[0].mType == BlobImplOrString::eDirectoryPath); + + InputDirectory input; + input.directoryPath() = aData[0].mDirectoryPath; + Unused << Send__delete__(this, input, mResult); + return; + } + nsIContentParent* parent = TabParent::GetFrom(Manager())->Manager(); InfallibleTArray blobs; - for (unsigned i = 0; i < aBlobs.Length(); i++) { - BlobParent* blobParent = parent->GetOrCreateActorForBlobImpl(aBlobs[i]); + for (unsigned i = 0; i < aData.Length(); i++) { + MOZ_ASSERT(aData[i].mType == BlobImplOrString::eBlobImpl); + BlobParent* blobParent = parent->GetOrCreateActorForBlobImpl(aData[i].mBlobImpl); if (blobParent) { blobs.AppendElement(blobParent); } } - InputFiles inblobs; + InputBlobs inblobs; inblobs.blobsParent().SwapElements(blobs); Unused << Send__delete__(this, inblobs, mResult); } @@ -138,7 +184,7 @@ FilePickerParent::Done(int16_t aResult) return; } - nsTArray> blobs; + nsTArray> files; if (mMode == nsIFilePicker::modeOpenMultiple) { nsCOMPtr iter; NS_ENSURE_SUCCESS_VOID(mFilePicker->GetFiles(getter_AddRefs(iter))); @@ -149,22 +195,26 @@ FilePickerParent::Done(int16_t aResult) iter->GetNext(getter_AddRefs(supports)); if (supports) { nsCOMPtr file = do_QueryInterface(supports); - - RefPtr blobimpl = new BlobImplFile(file); - blobs.AppendElement(blobimpl); + MOZ_ASSERT(file); + files.AppendElement(file); } } } else { nsCOMPtr file; mFilePicker->GetFile(getter_AddRefs(file)); if (file) { - RefPtr blobimpl = new BlobImplFile(file); - blobs.AppendElement(blobimpl); + files.AppendElement(file); } } + if (files.IsEmpty()) { + Unused << Send__delete__(this, void_t(), mResult); + return; + } + MOZ_ASSERT(!mRunnable); - mRunnable = new FileSizeAndDateRunnable(this, blobs); + mRunnable = new IORunnable(this, files, mMode == nsIFilePicker::modeGetFolder); + // Dispatch to background thread to do I/O: if (!mRunnable->Dispatch()) { Unused << Send__delete__(this, void_t(), nsIFilePicker::returnCancel); diff --git a/dom/ipc/FilePickerParent.h b/dom/ipc/FilePickerParent.h index b27fa3822c..a11b89c7a3 100644 --- a/dom/ipc/FilePickerParent.h +++ b/dom/ipc/FilePickerParent.h @@ -14,6 +14,8 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/PFilePickerParent.h" +class nsIFile; + namespace mozilla { namespace dom { @@ -29,7 +31,19 @@ class FilePickerParent : public PFilePickerParent virtual ~FilePickerParent(); void Done(int16_t aResult); - void SendFiles(const nsTArray>& aDomBlobs); + + struct BlobImplOrString + { + RefPtr mBlobImpl; + nsString mDirectoryPath; + + enum { + eBlobImpl, + eDirectoryPath + } mType; + }; + + void SendFilesOrDirectories(const nsTArray& aData); virtual bool RecvOpen(const int16_t& aSelectedType, const bool& aAddToRecentDocs, @@ -61,21 +75,26 @@ class FilePickerParent : public PFilePickerParent private: bool CreateFilePicker(); - class FileSizeAndDateRunnable : public nsRunnable + // This runnable is used to do some I/O operation on a separate thread. + class IORunnable : public nsRunnable { FilePickerParent* mFilePickerParent; - nsTArray> mBlobs; + nsTArray> mFiles; + nsTArray mResults; nsCOMPtr mEventTarget; + bool mIsDirectory; public: - FileSizeAndDateRunnable(FilePickerParent *aFPParent, - nsTArray>& aBlobs); + IORunnable(FilePickerParent *aFPParent, + nsTArray>& aFiles, + bool aIsDirectory); + bool Dispatch(); NS_IMETHOD Run(); void Destroy(); }; - RefPtr mRunnable; + RefPtr mRunnable; RefPtr mCallback; nsCOMPtr mFilePicker; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 95770f31df..c57d835124 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -290,6 +290,7 @@ struct FileSystemGetDirectoryListingParams { nsString filesystem; nsString realPath; + bool isRoot; // 'filters' could be an array rather than a semicolon separated string // (we'd then use InfallibleTArray internally), but that is // wasteful. E10s requires us to pass the filters over as a string anyway, @@ -306,6 +307,7 @@ struct FileSystemGetFileOrDirectoryParams { nsString filesystem; nsString realPath; + bool isRoot; }; union FileSystemPathOrFileValue diff --git a/dom/ipc/PFilePicker.ipdl b/dom/ipc/PFilePicker.ipdl index aff58b011d..6184c5b9d5 100644 --- a/dom/ipc/PFilePicker.ipdl +++ b/dom/ipc/PFilePicker.ipdl @@ -12,14 +12,20 @@ using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; namespace mozilla { namespace dom { -struct InputFiles +struct InputBlobs { PBlob[] blobs; }; -union MaybeInputFiles +struct InputDirectory { - InputFiles; + nsString directoryPath; +}; + +union MaybeInputData +{ + InputBlobs; + InputDirectory; void_t; }; @@ -33,7 +39,7 @@ parent: nsString displayDirectory); child: - async __delete__(MaybeInputFiles files, int16_t result); + async __delete__(MaybeInputData data, int16_t result); }; } // namespace dom diff --git a/dom/media/test/external/README.md b/dom/media/test/external/README.md index b174524bad..9b8499060a 100644 --- a/dom/media/test/external/README.md +++ b/dom/media/test/external/README.md @@ -173,7 +173,7 @@ Crash address: 0x104616900 --browsermob-script /bin/browsermob-proxy --browsermob-port 999 --profile -You can then call browsermob to shape the network. You can find an example in external_media_tests/playback/test_playback_limiting_bandwidth.py. Another example can be found at https://dxr.mozilla.org/mozilla-central/source/testing/marionette/client/marionette/tests/unit/test_browsermobproxy.py. +You can then call browsermob to shape the network. You can find an example in external_media_tests/playback/test_playback_limiting_bandwidth.py. Another example can be found at https://dxr.mozilla.org/mozilla-central/source/testing/marionette/harness/marionette/tests/unit/test_browsermobproxy.py. ### A warning about video URLs The ini files in `external_media_tests/urls` may contain URLs pulled from Firefox crash or bug data. Automated tests don't care about video content, but you might: visit these at your own risk and be aware that they may be NSFW. We do not intend to ever moderate or filter these URLs. diff --git a/dom/webidl/AnimationEffectTiming.webidl b/dom/webidl/AnimationEffectTiming.webidl index 0aa8c968f8..f6094283d1 100644 --- a/dom/webidl/AnimationEffectTiming.webidl +++ b/dom/webidl/AnimationEffectTiming.webidl @@ -21,7 +21,8 @@ interface AnimationEffectTiming : AnimationEffectTimingReadOnly { //inherit attribute double iterationStart; //Bug 1244640 - implement AnimationEffectTiming iterations //inherit attribute unrestricted double iterations; - inherit attribute (unrestricted double or DOMString) duration; + [SetterThrows] + inherit attribute (unrestricted double or DOMString) duration; //Bug 1244642 - implement AnimationEffectTiming direction //inherit attribute PlaybackDirection direction; //Bug 1244643 - implement AnimationEffectTiming easing diff --git a/dom/webidl/Directory.webidl b/dom/webidl/Directory.webidl index 7ea8e02693..6504dd02e0 100644 --- a/dom/webidl/Directory.webidl +++ b/dom/webidl/Directory.webidl @@ -20,6 +20,7 @@ interface Directory { /* * The leaf name of the directory. */ + [Throws] readonly attribute DOMString name; /* @@ -105,11 +106,13 @@ partial interface Directory { * to obtain this Directory. Full filesystem paths are not exposed to * unprivilaged content. */ + [Throws] readonly attribute DOMString path; /* * Getter for the immediate children of this directory. */ + [Throws] Promise> getFilesAndDirectories(); }; diff --git a/dom/webidl/FileList.webidl b/dom/webidl/FileList.webidl index 84a56be1bf..0dc10401c3 100644 --- a/dom/webidl/FileList.webidl +++ b/dom/webidl/FileList.webidl @@ -11,6 +11,8 @@ */ interface FileList { - getter File? item(unsigned long index); + [Throws] + getter (File or Directory)? item(unsigned long index); + readonly attribute unsigned long length; }; diff --git a/dom/webidl/HTMLInputElement.webidl b/dom/webidl/HTMLInputElement.webidl index 13f9f88d9b..592953d499 100644 --- a/dom/webidl/HTMLInputElement.webidl +++ b/dom/webidl/HTMLInputElement.webidl @@ -155,6 +155,10 @@ partial interface HTMLInputElement { [ChromeOnly] void mozSetFileArray(sequence files); + // This method is meant to use for testing only. + [ChromeOnly, Throws] + void mozSetDirectory(DOMString directoryPath); + // Number controls () have an anonymous text control // () in the anonymous shadow tree that they contain. On // such an anonymous text control this property provides access to the diff --git a/embedding/browser/nsDocShellTreeOwner.cpp b/embedding/browser/nsDocShellTreeOwner.cpp index dbf652a35d..39eed2029e 100644 --- a/embedding/browser/nsDocShellTreeOwner.cpp +++ b/embedding/browser/nsDocShellTreeOwner.cpp @@ -35,6 +35,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/SVGTitleElement.h" #include "nsIDOMEvent.h" +#include "nsIDOMFileList.h" #include "nsIDOMMouseEvent.h" #include "nsIFormControl.h" #include "nsIDOMHTMLInputElement.h" @@ -48,6 +49,7 @@ #include "nsIWebNavigation.h" #include "nsIDOMHTMLElement.h" #include "nsIPresShell.h" +#include "nsIStringBundle.h" #include "nsPIDOMWindow.h" #include "nsPIWindowRoot.h" #include "nsIDOMWindowCollection.h" @@ -68,6 +70,8 @@ #include "mozilla/Attributes.h" #include "mozilla/EventListenerManager.h" #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() +#include "mozilla/dom/File.h" // for input type=file +#include "mozilla/dom/FileList.h" // for input type=file using namespace mozilla; using namespace mozilla::dom; @@ -326,6 +330,24 @@ nsDocShellTreeOwner::RemoveFromWatcher() } } +void +nsDocShellTreeOwner::EnsureContentTreeOwner() +{ + if (mContentTreeOwner) { + return; + } + + mContentTreeOwner = new nsDocShellTreeOwner(); + nsCOMPtr browserChrome = GetWebBrowserChrome(); + if (browserChrome) { + mContentTreeOwner->SetWebBrowserChrome(browserChrome); + } + + if (mWebBrowser) { + mContentTreeOwner->WebBrowser(mWebBrowser); + } +} + NS_IMETHODIMP nsDocShellTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell, bool aPrimary, bool aTargetable, @@ -335,6 +357,9 @@ nsDocShellTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell, return mTreeOwner->ContentShellAdded(aContentShell, aPrimary, aTargetable, aID); + EnsureContentTreeOwner(); + aContentShell->SetTreeOwner(mContentTreeOwner); + if (aPrimary) { mPrimaryContentShell = aContentShell; mPrimaryTabParent = nullptr; @@ -553,10 +578,13 @@ nsDocShellTreeOwner::GetDevicePixelsPerDesktopPixel(double* aScale) NS_IMETHODIMP nsDocShellTreeOwner::SetPositionDesktopPix(int32_t aX, int32_t aY) { - // Added to nsIBaseWindow in bug 1247335; - // implement if a use-case is found. - NS_ASSERTION(false, "implement me!"); - return NS_ERROR_NOT_IMPLEMENTED; + if (mWebBrowser) { + return mWebBrowser->SetPositionDesktopPix(aX, aY); + } + + double scale = 1.0; + GetDevicePixelsPerDesktopPixel(&scale); + return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); } NS_IMETHODIMP @@ -811,6 +839,13 @@ nsDocShellTreeOwner::WebBrowser(nsWebBrowser* aWebBrowser) } mWebBrowser = aWebBrowser; + + if (mContentTreeOwner) { + mContentTreeOwner->WebBrowser(aWebBrowser); + if (!aWebBrowser) { + mContentTreeOwner = nullptr; + } + } } nsWebBrowser* @@ -864,6 +899,11 @@ nsDocShellTreeOwner::SetWebBrowserChrome(nsIWebBrowserChrome* aWebBrowserChrome) mOwnerRequestor = requestor; } } + + if (mContentTreeOwner) { + mContentTreeOwner->SetWebBrowserChrome(aWebBrowserChrome); + } + return NS_OK; } @@ -1036,9 +1076,7 @@ public: protected: ~DefaultTooltipTextProvider() {} - nsCOMPtr mTag_dialog; - nsCOMPtr mTag_dialogheader; - nsCOMPtr mTag_window; + nsCOMPtr mTag_dialogHeader; }; NS_IMPL_ISUPPORTS(DefaultTooltipTextProvider, nsITooltipTextProvider) @@ -1047,9 +1085,7 @@ DefaultTooltipTextProvider::DefaultTooltipTextProvider() { // There are certain element types which we don't want to use // as tool tip text. - mTag_dialog = do_GetAtom("dialog"); - mTag_dialogheader = do_GetAtom("dialogheader"); - mTag_window = do_GetAtom("window"); + mTag_dialogHeader = do_GetAtom("dialogheader"); } // A helper routine that determines whether we're still interested in SVG @@ -1107,14 +1143,70 @@ DefaultTooltipTextProvider::GetNodeText(nsIDOMNode* aNode, char16_t** aText, if (currElement) { nsCOMPtr content(do_QueryInterface(currElement)); if (content) { - if (!content->IsAnyOfXULElements(mTag_dialog, - mTag_dialogheader, - mTag_window)) { + if (!content->IsAnyOfXULElements(nsGkAtoms::dialog, + mTag_dialogHeader, + nsGkAtoms::window)) { // first try the normal title attribute... - currElement->GetAttribute(NS_LITERAL_STRING("title"), outText); - if (outText.Length()) { - found = true; - } else { + if (!content->IsSVGElement()) { + // If the element is an without a title, + // we should show the current file selection. + if (content->IsHTMLElement(nsGkAtoms::input) && + content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + NS_LITERAL_STRING("file"), eIgnoreCase) && + !content->HasAttr(kNameSpaceID_None, nsGkAtoms::title)) { + found = true; + nsCOMPtr fileList; + nsCOMPtr currInputElement(do_QueryInterface(currElement)); + nsresult rv = currInputElement->GetFiles(getter_AddRefs(fileList)); + NS_ENSURE_SUCCESS(rv, rv); + if (!fileList) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr bundle; + rv = bundleService->CreateBundle("chrome://global/locale/layout/HtmlForm.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t listLength = 0; + rv = fileList->GetLength(&listLength); + NS_ENSURE_SUCCESS(rv, rv); + if (listLength == 0) { + if (content->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) { + rv = bundle->GetStringFromName(MOZ_UTF16("NoFilesSelected"), + getter_Copies(outText)); + } else { + rv = bundle->GetStringFromName(MOZ_UTF16("NoFileSelected"), + getter_Copies(outText)); + } + NS_ENSURE_SUCCESS(rv, rv); + } else { + FileList* fl = static_cast(fileList.get()); + fl->UnsafeItem(0).GetAsFile()->GetName(outText); + + // For UX and performance (jank) reasons we cap the number of + // files that we list in the tooltip to 20 plus a "and xxx more" + // line, or to 21 if exactly 21 files were picked. + const uint32_t TRUNCATED_FILE_COUNT = 20; + uint32_t count = std::min(listLength, TRUNCATED_FILE_COUNT); + for (uint32_t i = 1; i < count; ++i) { + nsString fileName; + fl->UnsafeItem(i).GetAsFile()->GetName(fileName); + outText.Append(NS_LITERAL_STRING("\n")); + outText.Append(fileName); + } + } + } else if (NS_SUCCEEDED(currElement->GetAttribute(NS_LITERAL_STRING("title"), outText)) && + outText.Length()) { + found = true; + } + } + if (!found) { // ...ok, that didn't work, try it in the XLink namespace NS_NAMED_LITERAL_STRING(xlinkNS, "http://www.w3.org/1999/xlink"); nsCOMPtr linkContent( @@ -1123,8 +1215,7 @@ DefaultTooltipTextProvider::GetNodeText(nsIDOMNode* aNode, char16_t** aText, nsCOMPtr uri(linkContent->GetURIExternal()); if (uri) { currElement->GetAttributeNS( - NS_LITERAL_STRING("http://www.w3.org/1999/xlink"), - NS_LITERAL_STRING("title"), outText); + xlinkNS, NS_LITERAL_STRING("title"), outText); if (outText.Length()) { found = true; } @@ -1134,7 +1225,7 @@ DefaultTooltipTextProvider::GetNodeText(nsIDOMNode* aNode, char16_t** aText, lookingForSVGTitle = UseSVGTitle(currElement); } if (lookingForSVGTitle) { - nsINodeList* childNodes = node->ChildNodes(); + nsINodeList* childNodes = content->ChildNodes(); uint32_t childNodeCount = childNodes->Length(); for (uint32_t i = 0; i < childNodeCount; i++) { nsIContent* child = childNodes->Item(i); diff --git a/embedding/browser/nsDocShellTreeOwner.h b/embedding/browser/nsDocShellTreeOwner.h index 4ba6e52bcb..85f746fde7 100644 --- a/embedding/browser/nsDocShellTreeOwner.h +++ b/embedding/browser/nsDocShellTreeOwner.h @@ -101,6 +101,8 @@ protected: void AddToWatcher(); void RemoveFromWatcher(); + void EnsureContentTreeOwner(); + // These helper functions return the correct instances of the requested // interfaces. If the object passed to SetWebBrowserChrome() implements // nsISupportsWeakReference, then these functions call QueryReferent on @@ -128,6 +130,8 @@ protected: RefPtr mChromeTooltipListener; RefPtr mChromeContextMenuListener; + RefPtr mContentTreeOwner; + nsCOMPtr mPrompter; nsCOMPtr mAuthPrompter; nsCOMPtr mPrimaryTabParent; diff --git a/embedding/browser/nsWebBrowser.cpp b/embedding/browser/nsWebBrowser.cpp index a749e1f1c9..17b445cb4b 100644 --- a/embedding/browser/nsWebBrowser.cpp +++ b/embedding/browser/nsWebBrowser.cpp @@ -1303,10 +1303,16 @@ nsWebBrowser::GetDevicePixelsPerDesktopPixel(double* aScale) NS_IMETHODIMP nsWebBrowser::SetPositionDesktopPix(int32_t aX, int32_t aY) { - // Added to nsIBaseWindow in bug 1247335; - // implement if a use-case is found. - NS_ASSERTION(false, "implement me!"); - return NS_ERROR_NOT_IMPLEMENTED; + // XXX jfkthame + // It's not clear to me whether this will be fully correct across + // potential multi-screen, mixed-DPI configurations for all platforms; + // we might need to add code paths that make it possible to pass the + // desktop-pix parameters all the way through to the native widget, + // to avoid the risk of device-pixel coords mapping to the wrong + // display on OS X with mixed retina/non-retina screens. + double scale = 1.0; + GetDevicePixelsPerDesktopPixel(&scale); + return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); } NS_IMETHODIMP diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp index 1016df3969..6bbdb9436f 100644 --- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -583,8 +583,7 @@ SampleAnimations(Layer* aLayer, TimeStamp aPoint) } TimingParams timing; - timing.mDuration.SetAsUnrestrictedDouble() = - animation.duration().ToMilliseconds(); + timing.mDuration.emplace(animation.duration()); // Currently animations run on the compositor have their delay factored // into their start time, hence the delay is effectively zero. timing.mDelay = TimeDuration(0); @@ -833,7 +832,7 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer, // async transformed for async scrolls of this scroll frame's ancestor // scroll frames, not for async scrolls of this scroll frame itself. // In the loop below, we iterate over scroll frames from inside to outside. - // At each iteration, this array contains the layer's ancestor mask layers + // At each iteration, this array contains the layer's ancestor mask layers // of all scroll frames inside the current one. nsTArray ancestorMaskLayers; diff --git a/ipc/chromium/moz.build b/ipc/chromium/moz.build index 8b8e4ccc1f..88aaafe859 100644 --- a/ipc/chromium/moz.build +++ b/ipc/chromium/moz.build @@ -162,7 +162,7 @@ if os_bsd or os_linux: ] ost = CONFIG['OS_TEST'] -if '86' not in ost and 'arm' not in ost and 'mips' not in ost: +if '86' not in ost and 'arm' not in ost and 'aarch64' != ost and 'mips' not in ost: SOURCES += [ 'src/base/atomicops_internals_mutex.cc', ] diff --git a/ipc/chromium/src/base/atomicops.h b/ipc/chromium/src/base/atomicops.h index a167541beb..714e089280 100644 --- a/ipc/chromium/src/base/atomicops.h +++ b/ipc/chromium/src/base/atomicops.h @@ -138,10 +138,14 @@ Atomic64 Release_Load(volatile const Atomic64* ptr); #include "base/atomicops_internals_x86_macosx.h" #elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY) #include "base/atomicops_internals_x86_gcc.h" -#elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARM_FAMILY) +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARMEL) #include "base/atomicops_internals_arm_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARM64) +#include "base/atomicops_internals_arm64_gcc.h" #elif defined(COMPILER_GCC) && defined(ARCH_CPU_MIPS) #include "base/atomicops_internals_mips_gcc.h" +#elif defined(COMPILER_GCC) && defined(ARCH_CPU_PPC_FAMILY) +#include "base/atomicops_internals_ppc_gcc.h" #else #include "base/atomicops_internals_mutex.h" #endif diff --git a/ipc/chromium/src/base/atomicops_internals_arm64_gcc.h b/ipc/chromium/src/base/atomicops_internals_arm64_gcc.h new file mode 100644 index 0000000000..a2b0abc1a4 --- /dev/null +++ b/ipc/chromium/src/base/atomicops_internals_arm64_gcc.h @@ -0,0 +1,360 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is an internal atomic implementation, use base/atomicops.h instead. + +// TODO(rmcilroy): Investigate whether we can use __sync__ intrinsics instead of +// the hand coded assembly without introducing perf regressions. +// TODO(rmcilroy): Investigate whether we can use acquire / release versions of +// exclusive load / store assembly instructions and do away with +// the barriers. + +#ifndef BASE_ATOMICOPS_INTERNALS_ARM64_GCC_H_ +#define BASE_ATOMICOPS_INTERNALS_ARM64_GCC_H_ + +#if defined(OS_QNX) +#include +#endif + +namespace base { +namespace subtle { + +inline void MemoryBarrier() { + __asm__ __volatile__ ( // NOLINT + "dmb ish \n\t" // Data memory barrier. + ::: "memory" + ); // NOLINT +} + + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %w[prev], %[ptr] \n\t" // Load the previous value. + "cmp %w[prev], %w[old_value] \n\t" + "bne 1f \n\t" + "stxr %w[temp], %w[new_value], %[ptr] \n\t" // Try to store the new value. + "cbnz %w[temp], 0b \n\t" // Retry if it did not work. + "1: \n\t" + "clrex \n\t" // In case we didn't swap. + : [prev]"=&r" (prev), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [old_value]"r" (old_value), + [new_value]"r" (new_value) + : "memory", "cc" + ); // NOLINT + + return prev; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 result; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %w[result], %[ptr] \n\t" // Load the previous value. + "stxr %w[temp], %w[new_value], %[ptr] \n\t" // Try to store the new value. + "cbnz %w[temp], 0b \n\t" // Retry if it did not work. + : [result]"=&r" (result), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [new_value]"r" (new_value) + : "memory" + ); // NOLINT + + return result; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 result; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %w[result], %[ptr] \n\t" // Load the previous value. + "add %w[result], %w[result], %w[increment]\n\t" + "stxr %w[temp], %w[result], %[ptr] \n\t" // Try to store the result. + "cbnz %w[temp], 0b \n\t" // Retry on failure. + : [result]"=&r" (result), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [increment]"r" (increment) + : "memory" + ); // NOLINT + + return result; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + MemoryBarrier(); + Atomic32 result = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + + return result; +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %w[prev], %[ptr] \n\t" // Load the previous value. + "cmp %w[prev], %w[old_value] \n\t" + "bne 1f \n\t" + "stxr %w[temp], %w[new_value], %[ptr] \n\t" // Try to store the new value. + "cbnz %w[temp], 0b \n\t" // Retry if it did not work. + "dmb ish \n\t" // Data memory barrier. + "1: \n\t" + // If the compare failed the 'dmb' is unnecessary, but we still need a + // 'clrex'. + "clrex \n\t" + : [prev]"=&r" (prev), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [old_value]"r" (old_value), + [new_value]"r" (new_value) + : "memory", "cc" + ); // NOLINT + + return prev; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev; + int32_t temp; + + MemoryBarrier(); + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %w[prev], %[ptr] \n\t" // Load the previous value. + "cmp %w[prev], %w[old_value] \n\t" + "bne 1f \n\t" + "stxr %w[temp], %w[new_value], %[ptr] \n\t" // Try to store the new value. + "cbnz %w[temp], 0b \n\t" // Retry if it did not work. + "1: \n\t" + // If the compare failed the we still need a 'clrex'. + "clrex \n\t" + : [prev]"=&r" (prev), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [old_value]"r" (old_value), + [new_value]"r" (new_value) + : "memory", "cc" + ); // NOLINT + + return prev; +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +// 64-bit versions of the operations. +// See the 32-bit versions for comments. + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %[prev], %[ptr] \n\t" + "cmp %[prev], %[old_value] \n\t" + "bne 1f \n\t" + "stxr %w[temp], %[new_value], %[ptr] \n\t" + "cbnz %w[temp], 0b \n\t" + "1: \n\t" + "clrex \n\t" + : [prev]"=&r" (prev), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [old_value]"r" (old_value), + [new_value]"r" (new_value) + : "memory", "cc" + ); // NOLINT + + return prev; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + Atomic64 result; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %[result], %[ptr] \n\t" + "stxr %w[temp], %[new_value], %[ptr] \n\t" + "cbnz %w[temp], 0b \n\t" + : [result]"=&r" (result), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [new_value]"r" (new_value) + : "memory" + ); // NOLINT + + return result; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 result; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %[result], %[ptr] \n\t" + "add %[result], %[result], %[increment] \n\t" + "stxr %w[temp], %[result], %[ptr] \n\t" + "cbnz %w[temp], 0b \n\t" + : [result]"=&r" (result), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [increment]"r" (increment) + : "memory" + ); // NOLINT + + return result; +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + MemoryBarrier(); + Atomic64 result = NoBarrier_AtomicIncrement(ptr, increment); + MemoryBarrier(); + + return result; +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev; + int32_t temp; + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %[prev], %[ptr] \n\t" + "cmp %[prev], %[old_value] \n\t" + "bne 1f \n\t" + "stxr %w[temp], %[new_value], %[ptr] \n\t" + "cbnz %w[temp], 0b \n\t" + "dmb ish \n\t" + "1: \n\t" + "clrex \n\t" + : [prev]"=&r" (prev), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [old_value]"r" (old_value), + [new_value]"r" (new_value) + : "memory", "cc" + ); // NOLINT + + return prev; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev; + int32_t temp; + + MemoryBarrier(); + + __asm__ __volatile__ ( // NOLINT + "0: \n\t" + "ldxr %[prev], %[ptr] \n\t" + "cmp %[prev], %[old_value] \n\t" + "bne 1f \n\t" + "stxr %w[temp], %[new_value], %[ptr] \n\t" + "cbnz %w[temp], 0b \n\t" + "1: \n\t" + "clrex \n\t" + : [prev]"=&r" (prev), + [temp]"=&r" (temp), + [ptr]"+Q" (*ptr) + : [old_value]"r" (old_value), + [new_value]"r" (new_value) + : "memory", "cc" + ); // NOLINT + + return prev; +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +} // namespace base::subtle +} // namespace base + +#endif // BASE_ATOMICOPS_INTERNALS_ARM64_GCC_H_ diff --git a/ipc/chromium/src/base/atomicops_internals_ppc_gcc.h b/ipc/chromium/src/base/atomicops_internals_ppc_gcc.h new file mode 100644 index 0000000000..d5c4231af1 --- /dev/null +++ b/ipc/chromium/src/base/atomicops_internals_ppc_gcc.h @@ -0,0 +1,132 @@ +// Copyright 2012 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// This file is an internal atomic implementation, use atomicops.h instead. +// +#ifndef BASE_ATOMICOPS_INTERNALS_PPC_H_ +#define BASE_ATOMICOPS_INTERNALS_PPC_H_ +namespace base { +namespace subtle { +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return (__sync_val_compare_and_swap(ptr, old_value, new_value)); +} +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (__sync_bool_compare_and_swap(ptr, old_value, new_value) == false); + return old_value; +} +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + for (;;) { + Atomic32 old_value = *ptr; + Atomic32 new_value = old_value + increment; + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) { + return new_value; + // The exchange took place as expected. + } + // Otherwise, *ptr changed mid-loop and we need to retry. + } +} +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} +inline void MemoryBarrier() { + __asm__ __volatile__("sync" : : : "memory"); } +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { return *ptr; } +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} +#ifdef ARCH_CPU_PPC64 +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return (__sync_val_compare_and_swap(ptr, old_value, new_value)); +} +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + Atomic64 old_value; + do { + old_value = *ptr; + } while (__sync_bool_compare_and_swap(ptr, old_value, new_value) == false); + return old_value; +} +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + for (;;) { + Atomic64 old_value = *ptr; + Atomic64 new_value = old_value + increment; + if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) { + return new_value; + // The exchange took place as expected. + } + // Otherwise, *ptr changed mid-loop and we need to retry. + } +} +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + MemoryBarrier(); + *ptr = value; +} +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { return *ptr; } +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; + MemoryBarrier(); + return value; +} +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} +#endif +} // namespace base::subtle +} // namespace base +#endif // BASE_ATOMICOPS_INTERNALS_PPC_GCC_H_ diff --git a/ipc/chromium/src/base/thread_local.h b/ipc/chromium/src/base/thread_local.h index be387cfb67..f8fbef7df7 100644 --- a/ipc/chromium/src/base/thread_local.h +++ b/ipc/chromium/src/base/thread_local.h @@ -107,7 +107,8 @@ class ThreadLocalBoolean { } void Set(bool val) { - tlp_.Set(reinterpret_cast(val ? 1 : 0)); + uintptr_t intVal = val ? 1 : 0; + tlp_.Set(reinterpret_cast(intVal)); } private: diff --git a/ipc/chromium/src/build/build_config.h b/ipc/chromium/src/build/build_config.h index 7a4938e06c..dcec00db49 100644 --- a/ipc/chromium/src/build/build_config.h +++ b/ipc/chromium/src/build/build_config.h @@ -109,7 +109,8 @@ #define ARCH_CPU_ALPHA 1 #define ARCH_CPU_64_BITS 1 #elif defined(__aarch64__) -#define ARCH_CPU_AARCH64 1 +#define ARCH_CPU_ARM_FAMILY 1 +#define ARCH_CPU_ARM64 1 #define ARCH_CPU_64_BITS 1 #else #error Please add support for your architecture in build/build_config.h diff --git a/js/public/Class.h b/js/public/Class.h index 83b200faf6..f9cd2882c0 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -665,7 +665,19 @@ struct JSClass { // SetNewObjectMetadata itself #define JSCLASS_PRIVATE_IS_NSISUPPORTS (1<<3) // private is (nsISupports*) #define JSCLASS_IS_DOMJSCLASS (1<<4) // objects are DOM -// Bit 5 is unused. +#define JSCLASS_HAS_XRAYED_CONSTRUCTOR (1<<5) // if wrapped by an xray + // wrapper, the builtin + // class's constructor won't + // be unwrapped and invoked. + // Instead, the constructor is + // resolved in the caller's + // compartment and invoked + // with a wrapped newTarget. + // The constructor has to + // detect and handle this + // situation. + // See PromiseConstructor for + // details. #define JSCLASS_EMULATES_UNDEFINED (1<<6) // objects of this class act // like the value undefined, // in some contexts diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 16a554c235..f9119a6da4 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -679,7 +679,109 @@ js::obj_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp) FromPropertyDescriptor(cx, desc, args.rval()); } -// ES6 draft rev27 (2014/08/24) 19.1.2.14 Object.keys(O) +enum EnumerableOwnPropertiesKind { + Keys, + Values, + KeysAndValues +}; + +// ES7 proposal 2015-12-14 +// http://tc39.github.io/proposal-object-values-entries/#EnumerableOwnProperties +static bool +EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPropertiesKind kind) +{ + // Step 1. (Step 1 of Object.{keys,values,entries}, really.) + RootedObject obj(cx, ToObject(cx, args.get(0))); + if (!obj) + return false; + + // Step 2. + AutoIdVector ids(cx); + if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &ids)) + return false; + + // Step 3. + AutoValueVector properties(cx); + size_t len = ids.length(); + if (!properties.resize(len)) + return false; + + RootedId id(cx); + RootedValue key(cx); + RootedValue value(cx); + RootedNativeObject nobj(cx); + if (obj->is()) + nobj = &obj->as(); + RootedShape shape(cx); + Rooted desc(cx); + + // Step 4. + size_t out = 0; + for (size_t i = 0; i < len; i++) { + id = ids[i]; + + // Step 4.a. (Symbols were filtered out in step 2.) + MOZ_ASSERT(!JSID_IS_SYMBOL(id)); + + if (kind != Values) { + if (!IdToStringOrSymbol(cx, id, &key)) + return false; + } + + // Step 4.a.i. + if (nobj) { + if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) { + value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)); + } else { + shape = nobj->lookup(cx, id); + if (!shape || !(GetShapeAttributes(nobj, shape) & JSPROP_ENUMERATE)) + continue; + if (!shape->isAccessorShape()) { + if (!NativeGetExistingProperty(cx, nobj, nobj, shape, &value)) + return false; + } else if (!GetProperty(cx, obj, obj, id, &value)) { + return false; + } + } + } else { + if (!GetOwnPropertyDescriptor(cx, obj, id, &desc)) + return false; + + // Step 4.a.ii. (inverted.) + if (!desc.object() || !desc.enumerable()) + continue; + + // Step 4.a.ii.1. + // (Omitted because Object.keys doesn't use this implementation.) + + // Step 4.a.ii.2.a. + if (obj->isNative() && desc.hasValue()) + value = desc.value(); + else if (!GetProperty(cx, obj, obj, id, &value)) + return false; + } + + // Steps 4.a.ii.2.b-c. + if (kind == Values) + properties[out++].set(value); + else if (!NewValuePair(cx, key, value, properties[out++])) + return false; + } + + // Step 5. + // (Implemented in step 2.) + + // Step 3 of Object.{keys,values,entries} + JSObject* aobj = NewDenseCopiedArray(cx, out, properties.begin()); + if (!aobj) + return false; + + args.rval().setObject(*aobj); + return true; +} + +// ES7 proposal 2015-12-14 +// http://tc39.github.io/proposal-object-values-entries/#Object.keys static bool obj_keys(JSContext* cx, unsigned argc, Value* vp) { @@ -687,6 +789,24 @@ obj_keys(JSContext* cx, unsigned argc, Value* vp) return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY); } +// ES7 proposal 2015-12-14 +// http://tc39.github.io/proposal-object-values-entries/#Object.values +static bool +obj_values(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return EnumerableOwnProperties(cx, args, Values); +} + +// ES7 proposal 2015-12-14 +// http://tc39.github.io/proposal-object-values-entries/#Object.entries +static bool +obj_entries(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return EnumerableOwnProperties(cx, args, KeysAndValues); +} + /* ES6 draft 15.2.3.16 */ static bool obj_is(JSContext* cx, unsigned argc, Value* vp) @@ -1008,10 +1128,8 @@ static const JSFunctionSpec object_static_methods[] = { JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0), JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2, 0), JS_FN("keys", obj_keys, 1, 0), -#ifndef RELEASE_BUILD - JS_SELF_HOSTED_FN("values", "ObjectValues", 1, JSPROP_DEFINE_LATE), - JS_SELF_HOSTED_FN("entries", "ObjectEntries", 1, JSPROP_DEFINE_LATE), -#endif + JS_FN("values", obj_values, 1, 0), + JS_FN("entries", obj_entries, 1, 0), JS_FN("is", obj_is, 2, 0), JS_FN("defineProperty", obj_defineProperty, 3, 0), JS_FN("defineProperties", obj_defineProperties, 2, 0), diff --git a/js/src/builtin/Object.js b/js/src/builtin/Object.js index 5bde011db9..bfdcdbed8d 100644 --- a/js/src/builtin/Object.js +++ b/js/src/builtin/Object.js @@ -60,18 +60,21 @@ function Object_toLocaleString() { return callContentFunction(O.toString, O); } +// ES7 draft (2016 March 8) B.2.2.3 function ObjectDefineSetter(name, setter) { - var object; if (this === null || this === undefined) - object = global; + AddContentTelemetry(TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, 1); else - object = ToObject(this); + AddContentTelemetry(TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, 0); + // Step 1. + var object = ToObject(this); + + // Step 2. if (!IsCallable(setter)) ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter"); - var key = ToPropertyKey(name); - + // Step 3. var desc = { __proto__: null, enumerable: true, @@ -79,21 +82,30 @@ function ObjectDefineSetter(name, setter) { set: setter }; + // Step 4. + var key = ToPropertyKey(name); + + // Step 5. std_Object_defineProperty(object, key, desc); + + // Step 6. (implicit) } +// ES7 draft (2016 March 8) B.2.2.2 function ObjectDefineGetter(name, getter) { - var object; if (this === null || this === undefined) - object = global; + AddContentTelemetry(TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, 1); else - object = ToObject(this); + AddContentTelemetry(TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED, 0); + // Step 1. + var object = ToObject(this); + + // Step 2. if (!IsCallable(getter)) ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter"); - var key = ToPropertyKey(name); - + // Step 3. var desc = { __proto__: null, enumerable: true, @@ -101,81 +113,69 @@ function ObjectDefineGetter(name, getter) { get: getter }; + // Step 4. + var key = ToPropertyKey(name); + + // Step 5. std_Object_defineProperty(object, key, desc); + + // Step 6. (implicit) } +// ES7 draft (2016 March 8) B.2.2.5 function ObjectLookupSetter(name) { - var key = ToPropertyKey(name); + // Step 1. var object = ToObject(this); + // Step 2. + var key = ToPropertyKey(name) + do { + // Step 3.a. var desc = std_Object_getOwnPropertyDescriptor(object, key); + + // Step 3.b. if (desc) { + // Step.b.i. if (callFunction(std_Object_hasOwnProperty, desc, "set")) return desc.set; + + // Step.b.ii. return undefined; } + + // Step 3.c. object = std_Reflect_getPrototypeOf(object); } while (object !== null); + + // Step 3.d. (implicit) } +// ES7 draft (2016 March 8) B.2.2.4 function ObjectLookupGetter(name) { - var key = ToPropertyKey(name); + // Step 1. var object = ToObject(this); + // Step 2. + var key = ToPropertyKey(name) + do { + // Step 3.a. var desc = std_Object_getOwnPropertyDescriptor(object, key); + + // Step 3.b. if (desc) { + // Step.b.i. if (callFunction(std_Object_hasOwnProperty, desc, "get")) return desc.get; + + // Step.b.ii. return undefined; } + + // Step 3.c. object = std_Reflect_getPrototypeOf(object); } while (object !== null); -} - -// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.values -function ObjectValues(O) { - // Steps 1-2. - var object = ToObject(O); - - // Steps 3-4. - // EnumerableOwnProperties is inlined here. - var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN); - var values = []; - var valuesCount = 0; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (!callFunction(std_Object_propertyIsEnumerable, object, key)) - continue; - - var value = object[key]; - _DefineDataProperty(values, valuesCount++, value); - } - - // Step 5. - return values; -} - -// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.entries -function ObjectEntries(O) { - // Steps 1-2. - var object = ToObject(O); - - // Steps 3-4. - // EnumerableOwnProperties is inlined here. - var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN); - var entries = []; - var entriesCount = 0; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (!callFunction(std_Object_propertyIsEnumerable, object, key)) - continue; - - var value = object[key]; - _DefineDataProperty(entries, entriesCount++, [key, value]); - } - - // Step 5. - return entries; + + // Step 3.d. (implicit) } diff --git a/js/src/builtin/SelfHostingDefines.h b/js/src/builtin/SelfHostingDefines.h index 880dc81d08..b990255cb7 100644 --- a/js/src/builtin/SelfHostingDefines.h +++ b/js/src/builtin/SelfHostingDefines.h @@ -63,4 +63,6 @@ #define JSITER_SYMBOLS 0x20 /* also include symbol property keys */ #define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */ +#define TELEMETRY_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED 25 + #endif diff --git a/js/src/jit-test/tests/auto-regress/bug488421.js b/js/src/jit-test/tests/auto-regress/bug488421.js index a1c939a23b..1fcad0c186 100644 --- a/js/src/jit-test/tests/auto-regress/bug488421.js +++ b/js/src/jit-test/tests/auto-regress/bug488421.js @@ -4,7 +4,7 @@ function f(foo) { var x; - eval("__defineGetter__(\"y\", function ()x)"); + eval("this.__defineGetter__(\"y\", function ()x)"); } f(""); try { diff --git a/js/src/jit-test/tests/auto-regress/bug490191.js b/js/src/jit-test/tests/auto-regress/bug490191.js index 46a13fa939..b56ee11db5 100644 --- a/js/src/jit-test/tests/auto-regress/bug490191.js +++ b/js/src/jit-test/tests/auto-regress/bug490191.js @@ -5,7 +5,7 @@ function f(param) { var w; return eval("\ (function(){\ - __defineGetter__(\"y\", function()({\ + this.__defineGetter__(\"y\", function()({\ x: function(){ return w }()\ }))\ });\ diff --git a/js/src/jit-test/tests/auto-regress/bug521163.js b/js/src/jit-test/tests/auto-regress/bug521163.js index 1c018645d6..f25648f0df 100644 --- a/js/src/jit-test/tests/auto-regress/bug521163.js +++ b/js/src/jit-test/tests/auto-regress/bug521163.js @@ -5,7 +5,7 @@ // for (c in [0]) { { - let c = __defineGetter__("", function(){}); + let c = this.__defineGetter__("", function(){}); eval("function w(){}")++ } } diff --git a/js/src/jit-test/tests/auto-regress/bug560796.js b/js/src/jit-test/tests/auto-regress/bug560796.js index 5121fdc561..4ab93567e4 100644 --- a/js/src/jit-test/tests/auto-regress/bug560796.js +++ b/js/src/jit-test/tests/auto-regress/bug560796.js @@ -1,7 +1,7 @@ // Binary: cache/js-dbg-64-a6d7a5677b4c-linux // Flags: // -__defineSetter__("x", function(){}) +this.__defineSetter__("x", function(){}) this.watch("x", "".localeCompare) window = x Object.defineProperty(this, "x", ({ diff --git a/js/src/jit-test/tests/auto-regress/bug580699.js b/js/src/jit-test/tests/auto-regress/bug580699.js index a53f39fc4b..234c6b5544 100644 --- a/js/src/jit-test/tests/auto-regress/bug580699.js +++ b/js/src/jit-test/tests/auto-regress/bug580699.js @@ -8,5 +8,5 @@ function f() { print(a) })() } -__defineGetter__("x", gc) +this.__defineGetter__("x", gc) f() diff --git a/js/src/jit-test/tests/auto-regress/bug600128.js b/js/src/jit-test/tests/auto-regress/bug600128.js index e51a99bd3d..4905532134 100644 --- a/js/src/jit-test/tests/auto-regress/bug600128.js +++ b/js/src/jit-test/tests/auto-regress/bug600128.js @@ -1,7 +1,7 @@ // Binary: cache/js-dbg-64-54700fad8cf9-linux // Flags: -j // -__defineSetter__("x", Object.freeze) +this.__defineSetter__("x", Object.freeze) x = this; for (let z = 0; z < 5; z++) { for each(let y in [0, 0, 0]) { diff --git a/js/src/jit-test/tests/auto-regress/bug653789.js b/js/src/jit-test/tests/auto-regress/bug653789.js index dd260f7bb7..68816a57f6 100644 --- a/js/src/jit-test/tests/auto-regress/bug653789.js +++ b/js/src/jit-test/tests/auto-regress/bug653789.js @@ -3,6 +3,6 @@ // Binary: cache/js-dbg-64-3dd6ec45084c-linux // Flags: // -__defineGetter__("x", eval); +this.__defineGetter__("x", eval); eval.toString = toLocaleString eval < x diff --git a/js/src/jit-test/tests/baseline/bug1095870.js b/js/src/jit-test/tests/baseline/bug1095870.js index e75cbbe5dc..97a93bda1c 100644 --- a/js/src/jit-test/tests/baseline/bug1095870.js +++ b/js/src/jit-test/tests/baseline/bug1095870.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager; +// |jit-test| --ion-eager; for (var j = 0; j < 2; j++) { (false).__proto__ = 0 } diff --git a/js/src/jit-test/tests/baseline/bug1153458.js b/js/src/jit-test/tests/baseline/bug1153458.js index aadc9f40b1..65124db04f 100644 --- a/js/src/jit-test/tests/baseline/bug1153458.js +++ b/js/src/jit-test/tests/baseline/bug1153458.js @@ -1,6 +1,6 @@ -// |jit-test| baseline-eager; error: TypeError +// |jit-test| --baseline-eager; error: TypeError try { - __defineGetter__("x", Iterator)() + this.__defineGetter__("x", Iterator)() } catch (e) {} f = function() { return (function() { diff --git a/js/src/jit-test/tests/basic/bug593611.js b/js/src/jit-test/tests/basic/bug593611.js index 1beb3d4b82..6106c0f5ce 100644 --- a/js/src/jit-test/tests/basic/bug593611.js +++ b/js/src/jit-test/tests/basic/bug593611.js @@ -1,5 +1,6 @@ +var global = this; (function() { - __defineGetter__("x", /x/.constructor) + global.__defineGetter__("x", /x/.constructor) })() for (var a = 0; a < 4; ++a) { if (a % 4 == 1) { diff --git a/js/src/jit-test/tests/basic/bug641229.js b/js/src/jit-test/tests/basic/bug641229.js index 27854a8e00..4ae5427b26 100644 --- a/js/src/jit-test/tests/basic/bug641229.js +++ b/js/src/jit-test/tests/basic/bug641229.js @@ -1,2 +1,2 @@ -__defineSetter__("x",Math.max) +this.__defineSetter__("x",Math.max) Function("({x}=[])")() diff --git a/js/src/jit-test/tests/basic/bug785094.js b/js/src/jit-test/tests/basic/bug785094.js index 3e44a61228..7c30f834ef 100644 --- a/js/src/jit-test/tests/basic/bug785094.js +++ b/js/src/jit-test/tests/basic/bug785094.js @@ -1,2 +1,2 @@ -// |jit-test| dump-bytecode +// |jit-test| --dump-bytecode Function("for(let b; true; x) \nfalse"); diff --git a/js/src/jit-test/tests/basic/testBug628564.js b/js/src/jit-test/tests/basic/testBug628564.js index 7aebb561a2..8e9455064f 100644 --- a/js/src/jit-test/tests/basic/testBug628564.js +++ b/js/src/jit-test/tests/basic/testBug628564.js @@ -2,7 +2,7 @@ x = 0 for (a = 0; a < 13; ++a) { if (a == 7) { if (!x) { - __defineSetter__("x", Object.defineProperties) + this.__defineSetter__("x", Object.defineProperties) } } } diff --git a/js/src/jit-test/tests/basic/testBug775807.js b/js/src/jit-test/tests/basic/testBug775807.js index 937c4995a1..b136b0a81b 100644 --- a/js/src/jit-test/tests/basic/testBug775807.js +++ b/js/src/jit-test/tests/basic/testBug775807.js @@ -1,4 +1,4 @@ -// |jit-test| dump-bytecode +// |jit-test| --dump-bytecode (function() { const x = ((function() { diff --git a/js/src/jit-test/tests/gc/bug-1053676.js b/js/src/jit-test/tests/gc/bug-1053676.js index 47017fc284..3b3b0b117a 100644 --- a/js/src/jit-test/tests/gc/bug-1053676.js +++ b/js/src/jit-test/tests/gc/bug-1053676.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager; +// |jit-test| --ion-eager; if (typeof Symbol !== "function") quit(0); var x diff --git a/js/src/jit-test/tests/ion/bug-870034.js b/js/src/jit-test/tests/ion/bug-870034.js index 1c904949b2..1bb6e095f5 100644 --- a/js/src/jit-test/tests/ion/bug-870034.js +++ b/js/src/jit-test/tests/ion/bug-870034.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager function f(b) { var a = arguments; if (b) diff --git a/js/src/jit-test/tests/ion/bug1076026.js b/js/src/jit-test/tests/ion/bug1076026.js index b5e90a93e0..1bfacef0e0 100644 --- a/js/src/jit-test/tests/ion/bug1076026.js +++ b/js/src/jit-test/tests/ion/bug1076026.js @@ -1,6 +1,6 @@ (function f() { let x = (new function() {}) - __defineGetter__("x", function() { + this.__defineGetter__("x", function() { ({ e: x }) diff --git a/js/src/jit-test/tests/ion/bug780842.js b/js/src/jit-test/tests/ion/bug780842.js index 2cfaae2336..16a480a0b6 100644 --- a/js/src/jit-test/tests/ion/bug780842.js +++ b/js/src/jit-test/tests/ion/bug780842.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager;error:TypeError +// |jit-test| --ion-eager;error:TypeError function testUKeyUObject(a, key1, key2, key3) { assertEq(a[-1](), "hi"); diff --git a/js/src/jit-test/tests/ion/bug819611.js b/js/src/jit-test/tests/ion/bug819611.js index c050cdc694..f5b662935b 100644 --- a/js/src/jit-test/tests/ion/bug819611.js +++ b/js/src/jit-test/tests/ion/bug819611.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager x = [0, 0] Object.freeze(x).map(function() { x.length = 6 diff --git a/js/src/jit-test/tests/ion/bug875452.js b/js/src/jit-test/tests/ion/bug875452.js index fd3827be6e..27a1de4ac7 100644 --- a/js/src/jit-test/tests/ion/bug875452.js +++ b/js/src/jit-test/tests/ion/bug875452.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager function causeBreak(t, n, r) { gcPreserveCode(); diff --git a/js/src/jit-test/tests/ion/bug892794.js b/js/src/jit-test/tests/ion/bug892794.js index 25b9f8459f..dd9a6236c4 100644 --- a/js/src/jit-test/tests/ion/bug892794.js +++ b/js/src/jit-test/tests/ion/bug892794.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager function test0(v) { return (2147483648-Math.max(1.1,-(((2<<(-v|v))-3)|0)))|0; diff --git a/js/src/jit-test/tests/ion/bug905999.js b/js/src/jit-test/tests/ion/bug905999.js index 7e94ec59d7..b8f33d8fbb 100644 --- a/js/src/jit-test/tests/ion/bug905999.js +++ b/js/src/jit-test/tests/ion/bug905999.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager function reportCompare (expected) { typeof expected; } diff --git a/js/src/jit-test/tests/ion/bug906035.js b/js/src/jit-test/tests/ion/bug906035.js index 7c847aed3e..09d0af7d61 100644 --- a/js/src/jit-test/tests/ion/bug906035.js +++ b/js/src/jit-test/tests/ion/bug906035.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager function y() { return "foo,bar"; } function x() { var z = y().split(','); diff --git a/js/src/jit-test/tests/ion/bug909601.js b/js/src/jit-test/tests/ion/bug909601.js index 3124812e48..01d481597e 100644 --- a/js/src/jit-test/tests/ion/bug909601.js +++ b/js/src/jit-test/tests/ion/bug909601.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager for (var i=0; i<3; i++) z = new Int32Array; diff --git a/js/src/jit-test/tests/ion/bug911707.js b/js/src/jit-test/tests/ion/bug911707.js index 5b6afaf8ab..86f61f2fea 100644 --- a/js/src/jit-test/tests/ion/bug911707.js +++ b/js/src/jit-test/tests/ion/bug911707.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager x = [ "CNY", "TWD", "invalid" ]; Object.freeze(x).map(function() { x.length = 6 diff --git a/js/src/jit-test/tests/ion/divmodself.js b/js/src/jit-test/tests/ion/divmodself.js index 58207bad2d..524d46d022 100644 --- a/js/src/jit-test/tests/ion/divmodself.js +++ b/js/src/jit-test/tests/ion/divmodself.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager // bug 944963 function bug944963(x, y) { diff --git a/js/src/jit-test/tests/ion/inline-Math-random-before-called.js b/js/src/jit-test/tests/ion/inline-Math-random-before-called.js index 8db2fedfb2..25d801b62f 100644 --- a/js/src/jit-test/tests/ion/inline-Math-random-before-called.js +++ b/js/src/jit-test/tests/ion/inline-Math-random-before-called.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager function ionCompiledEagerly() { Math.random; // establish Math.random's identity for inlining diff --git a/js/src/jit-test/tests/ion/object-create-with-primitive-second-arg-in-ion.js b/js/src/jit-test/tests/ion/object-create-with-primitive-second-arg-in-ion.js index 05f82eb23d..a4c6d40da9 100644 --- a/js/src/jit-test/tests/ion/object-create-with-primitive-second-arg-in-ion.js +++ b/js/src/jit-test/tests/ion/object-create-with-primitive-second-arg-in-ion.js @@ -1,4 +1,4 @@ -// |jit-test| ion-eager +// |jit-test| --ion-eager load(libdir + "asserts.js"); [1, "", true, Symbol(), undefined].forEach(props => { diff --git a/js/src/jit-test/tests/jaeger/bug768313.js b/js/src/jit-test/tests/jaeger/bug768313.js index 246c519c80..d7b7ef3d87 100644 --- a/js/src/jit-test/tests/jaeger/bug768313.js +++ b/js/src/jit-test/tests/jaeger/bug768313.js @@ -1,4 +1,4 @@ -// |jit-test| dump-bytecode +// |jit-test| --dump-bytecode function f() { } evaluate('function g() { f(); }', {newContext: true}); diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index d1608997c0..177facd153 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -3711,6 +3711,20 @@ js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_ return NewCopiedArrayTryUseGroup(cx, group, vp, length); } +bool +js::NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleValue rval) +{ + JS::AutoValueArray<2> vec(cx); + vec[0].set(val1); + vec[1].set(val2); + + JSObject* aobj = js::NewDenseCopiedArray(cx, 2, vec.begin()); + if (!aobj) + return false; + rval.setObject(*aobj); + return true; +} + #ifdef DEBUG bool js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp) diff --git a/js/src/jsarray.h b/js/src/jsarray.h index 7d666fd670..275f9c619c 100644 --- a/js/src/jsarray.h +++ b/js/src/jsarray.h @@ -119,6 +119,9 @@ extern JSObject* NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length, HandleObject proto = nullptr); +extern bool +NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleValue rval); + /* * Determines whether a write to the given element on |obj| should fail because * |obj| is an Array with a non-writable length, and writing that element would diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index e93f370040..00f2bd9a6a 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -280,6 +280,14 @@ struct JSCompartment performanceMonitoring.unlink(); isSystem_ = isSystem; } + + // Used to approximate non-content code when reporting telemetry. + inline bool isProbablySystemOrAddonCode() const { + if (creationOptions_.addonIdOrNull()) + return true; + + return isSystem_; + } private: JSPrincipals* principals_; bool isSystem_; diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index bc261868b7..662e9f0d61 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -84,15 +84,7 @@ typedef HashSet IdSet; static inline bool NewKeyValuePair(JSContext* cx, jsid id, const Value& val, MutableHandleValue rval) { - JS::AutoValueArray<2> vec(cx); - vec[0].set(IdToValue(id)); - vec[1].set(val); - - JSObject* aobj = NewDenseCopiedArray(cx, 2, vec.begin()); - if (!aobj) - return false; - rval.setObject(*aobj); - return true; + return NewValuePair(cx, IdToValue(id), val, rval); } static inline bool diff --git a/js/src/tests/ecma_7/Object/defineGetter-defineSetter.js b/js/src/tests/ecma_7/Object/defineGetter-defineSetter.js new file mode 100644 index 0000000000..b2bb21f67c --- /dev/null +++ b/js/src/tests/ecma_7/Object/defineGetter-defineSetter.js @@ -0,0 +1,92 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +let count = 0; +let verifyProxy = new Proxy({}, { + defineProperty(target, property, descriptor) { + assertEq(property, "x"); + + assertEq(descriptor.enumerable, true); + assertEq(descriptor.configurable, true); + + if ("set" in descriptor) + assertEq(descriptor.set, Object.prototype.__defineSetter__); + else + assertEq(descriptor.get, Object.prototype.__defineGetter__); + + assertEq(Object.keys(descriptor).length, 3); + + count++; + return true; + } +}); + +for (let define of [Object.prototype.__defineGetter__, Object.prototype.__defineSetter__]) { + // null/undefined |this| value + for (let thisv of [undefined, null]) + assertThrowsInstanceOf(() => define.call(thisv, "x", define), TypeError); + + // non-callable getter/setter + let nonCallable = [{}, [], new Proxy({}, {})]; + for (let value of nonCallable) + assertThrowsInstanceOf(() => define.call(verifyProxy, "x", value), TypeError); + + // ToPropertyKey + let key = { + [Symbol.toPrimitive](hint) { + assertEq(hint, "string"); + // Throws, because non-primitive is returned + return {}; + }, + valueOf() { throw "wrongly invoked"; }, + toString() { throw "wrongly invoked"; } + }; + assertThrowsInstanceOf(() => define.call(verifyProxy, key, define), TypeError); + + key = { + [Symbol.toPrimitive](hint) { + assertEq(hint, "string"); + return "x"; + }, + valueOf() { throw "wrongly invoked"; }, + toString() { throw "wrongly invoked"; } + } + define.call(verifyProxy, key, define); + + key = { + [Symbol.toPrimitive]: undefined, + + valueOf() { throw "wrongly invoked"; }, + toString() { return "x"; } + } + define.call(verifyProxy, key, define); + + // Bog standard call + define.call(verifyProxy, "x", define); + + let obj = {}; + define.call(obj, "x", define); + let descriptor = Object.getOwnPropertyDescriptor(obj, "x"); + + assertEq(descriptor.enumerable, true); + assertEq(descriptor.configurable, true); + + if (define == Object.prototype.__defineSetter__) { + assertEq(descriptor.get, undefined); + assertEq(descriptor.set, define); + } else { + assertEq(descriptor.get, define); + assertEq(descriptor.set, undefined); + } + + assertEq(Object.keys(descriptor).length, 4); + + +} + +// Number of calls that should succeed +assertEq(count, 6); + +reportCompare(0, 0); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index ff274821da..b55667a579 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -42,6 +42,7 @@ #include "vm/TypedArrayCommon.h" #include "jsfuninlines.h" +#include "jsobjinlines.h" #include "jsscriptinlines.h" #include "vm/BooleanObject-inl.h" @@ -114,7 +115,7 @@ intrinsic_ToPropertyKey(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedId id(cx); - if (!ValueToId(cx, args[0], &id)) + if (!ToPropertyKey(cx, args[0], &id)) return false; args.rval().set(IdToValue(id)); @@ -1487,6 +1488,14 @@ intrinsic_LocalTZA(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +intrinsic_AddContentTelemetry(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + return true; +} + static bool intrinsic_ConstructFunction(JSContext* cx, unsigned argc, Value* vp) { @@ -1818,6 +1827,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("_FinishBoundFunctionInit", intrinsic_FinishBoundFunctionInit, 4,0), JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0), JS_FN("LocalTZA", intrinsic_LocalTZA, 0,0), + JS_FN("AddContentTelemetry", intrinsic_AddContentTelemetry, 2,0), JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing, 0,0, IntrinsicIsConstructing), diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp index 6c46ab0bcc..c909195a56 100644 --- a/js/xpconnect/wrappers/XrayWrapper.cpp +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -913,6 +913,48 @@ JSXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper, unsigned flags clasp->spec.prototypeProperties(), flags, props); } +bool +JSXrayTraits::construct(JSContext* cx, HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance) +{ + JSXrayTraits& self = JSXrayTraits::singleton; + JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper)); + if (self.getProtoKey(holder) == JSProto_Function) { + JSProtoKey standardConstructor = constructorFor(holder); + if (standardConstructor == JSProto_Null) + return baseInstance.construct(cx, wrapper, args); + + const js::Class* clasp = js::ProtoKeyToClass(standardConstructor); + MOZ_ASSERT(clasp); + if (!(clasp->flags & JSCLASS_HAS_XRAYED_CONSTRUCTOR)) + return baseInstance.construct(cx, wrapper, args); + + // If the JSCLASS_HAS_XRAYED_CONSTRUCTOR flag is set on the Class, + // we don't use the constructor at hand. Instead, we retrieve the + // equivalent standard constructor in the xray compartment and run + // it in that compartment. The newTarget isn't unwrapped, and the + // constructor has to be able to detect and handle this situation. + // See the comments in js/public/Class.h and PromiseConstructor for + // details and an example. + RootedObject ctor(cx); + if (!JS_GetClassObject(cx, standardConstructor, &ctor)) + return false; + + RootedValue ctorVal(cx, ObjectValue(*ctor)); + HandleValueArray vals(args); + RootedObject result(cx); + if (!JS::Construct(cx, ctorVal, wrapper, vals, &result)) + return false; + AssertSameCompartment(cx, result); + args.rval().setObject(*result); + return true; + } + + JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); + js::ReportIsNotFunction(cx, v); + return false; +} + JSObject* JSXrayTraits::createHolder(JSContext* cx, JSObject* wrapper) { diff --git a/js/xpconnect/wrappers/XrayWrapper.h b/js/xpconnect/wrappers/XrayWrapper.h index c9be9f3eb9..3856bcefa2 100644 --- a/js/xpconnect/wrappers/XrayWrapper.h +++ b/js/xpconnect/wrappers/XrayWrapper.h @@ -254,20 +254,7 @@ public: } static bool construct(JSContext* cx, JS::HandleObject wrapper, - const JS::CallArgs& args, const js::Wrapper& baseInstance) - { - JSXrayTraits& self = JSXrayTraits::singleton; - JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper)); - if (!holder) { - return false; - } - if (self.getProtoKey(holder) == JSProto_Function) - return baseInstance.construct(cx, wrapper, args); - - JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); - js::ReportIsNotFunction(cx, v); - return false; - } + const JS::CallArgs& args, const js::Wrapper& baseInstance); bool getPrototype(JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, diff --git a/layout/base/AccessibleCaretManager.cpp b/layout/base/AccessibleCaretManager.cpp index fa0504b10f..2924d6b5a3 100644 --- a/layout/base/AccessibleCaretManager.cpp +++ b/layout/base/AccessibleCaretManager.cpp @@ -241,7 +241,6 @@ AccessibleCaretManager::HasNonEmptyTextContent(nsINode* aNode) const aNode, nsContentUtils::eRecurseIntoChildren); } - void AccessibleCaretManager::UpdateCaretsForCursorMode(UpdateCaretsHint aHint) { @@ -995,30 +994,44 @@ AccessibleCaretManager::DragCaretInternal(const nsPoint& aPoint) } nsRect -AccessibleCaretManager::GetContentBoundaryForFrame(nsIFrame* aFrame) const +AccessibleCaretManager::GetAllChildFrameRectsUnion(nsIFrame* aFrame) const { - nsRect resultRect; - nsIFrame* rootFrame = mPresShell->GetRootFrame(); + nsRect unionRect; - for (; aFrame; aFrame = aFrame->GetNextContinuation()) { - nsRect rect = aFrame->GetContentRectRelativeToSelf(); - nsLayoutUtils::TransformRect(aFrame, rootFrame, rect); - resultRect = resultRect.Union(rect); + // Drill through scroll frames, we don't want to include scrollbar child + // frames below. + for (nsIFrame* frame = aFrame->GetContentInsertionFrame(); + frame; + frame = frame->GetNextContinuation()) { + nsRect frameRect; - nsIFrame::ChildListIterator lists(aFrame); - for (; !lists.IsDone(); lists.Next()) { - // Loop over all children to take the overflow rect into consideration. + for (nsIFrame::ChildListIterator lists(frame); !lists.IsDone(); lists.Next()) { + // Loop all children to union their scrollable overflow rect. for (nsIFrame* child : lists.CurrentList()) { - nsRect overflowRect = child->GetScrollableOverflowRect(); - nsLayoutUtils::TransformRect(child, rootFrame, overflowRect); - resultRect = resultRect.Union(overflowRect); + nsRect childRect = child->GetScrollableOverflowRectRelativeToSelf(); + nsLayoutUtils::TransformRect(child, frame, childRect); + + // A TextFrame containing only '\n' has positive height and width 0, or + // positive width and height 0 if it's vertical. Need to use UnionEdges + // to add its rect. BRFrame rect should be non-empty. + if (childRect.IsEmpty()) { + frameRect = frameRect.UnionEdges(childRect); + } else { + frameRect = frameRect.Union(childRect); + } } } + + MOZ_ASSERT(!frameRect.IsEmpty(), + "Editable frames should have at least one BRFrame child to make " + "frameRect non-empty!"); + if (frame != aFrame) { + nsLayoutUtils::TransformRect(frame, aFrame, frameRect); + } + unionRect = unionRect.Union(frameRect); } - // Shrink rect to make sure we never hit the boundary. - resultRect.Deflate(kBoundaryAppUnits); - return resultRect; + return unionRect; } nsPoint @@ -1032,9 +1045,17 @@ AccessibleCaretManager::AdjustDragBoundary(const nsPoint& aPoint) const Element* editingHost = GetEditingHostForFrame(focusFrame); if (editingHost) { - nsRect boundary = - GetContentBoundaryForFrame(editingHost->GetPrimaryFrame()); - adjustedPoint = boundary.ClampPoint(adjustedPoint); + nsIFrame* editingHostFrame = editingHost->GetPrimaryFrame(); + if (editingHostFrame) { + nsRect boundary = GetAllChildFrameRectsUnion(editingHostFrame); + nsLayoutUtils::TransformRect(editingHostFrame, mPresShell->GetRootFrame(), + boundary); + + // Shrink the rect to make sure we never hit the boundary. + boundary.Deflate(kBoundaryAppUnits); + + adjustedPoint = boundary.ClampPoint(adjustedPoint); + } } if (GetCaretMode() == CaretMode::Selection) { diff --git a/layout/base/AccessibleCaretManager.h b/layout/base/AccessibleCaretManager.h index 53d707449b..84ee77eed6 100644 --- a/layout/base/AccessibleCaretManager.h +++ b/layout/base/AccessibleCaretManager.h @@ -165,9 +165,10 @@ protected: dom::Selection* GetSelection() const; already_AddRefed GetFrameSelection() const; - // Get the bounding rectangle for aFrame where the caret under cursor mode can - // be positioned. The rectangle is relative to the root frame. - nsRect GetContentBoundaryForFrame(nsIFrame* aFrame) const; + // Get the union of all the child frame scrollable overflow rects for aFrame, + // which is used as a helper function to restrict the area where the caret can + // be dragged. Returns the rect relative to aFrame. + nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame) const; // If we're dragging the first caret, we do not want to drag it over the // previous character of the second caret. Same as the second caret. So we diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 43b97feb08..5c83fa1ca8 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -2473,7 +2473,7 @@ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(nsDisplayListBuilder* aBuil const DisplayItemScrollClip* scrollClip = aBuilder->ClipState().GetCurrentInnermostScrollClip(); - + bool needBlendContainer = false; // Passing bg == nullptr in this macro will result in one iteration with @@ -2965,7 +2965,7 @@ static void CheckForBorderItem(nsDisplayItem *aItem, uint32_t& aFlags) while (nextItem && nextItem->GetType() == nsDisplayItem::TYPE_BACKGROUND) { nextItem = nextItem->GetAbove(); } - if (nextItem && + if (nextItem && nextItem->Frame() == aItem->Frame() && nextItem->GetType() == nsDisplayItem::TYPE_BORDER) { aFlags |= nsCSSRendering::PAINTBG_WILL_PAINT_BORDER; @@ -3602,8 +3602,8 @@ nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect) return false; } - -nsDisplayItemGeometry* + +nsDisplayItemGeometry* nsDisplayBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder) { return new nsDisplayBorderGeometry(this, aBuilder); @@ -3630,7 +3630,7 @@ nsDisplayBorder::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); } } - + void nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) { @@ -3917,7 +3917,7 @@ nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, mList.AppendToTop(aItem); UpdateBounds(aBuilder); - + if (!aFrame || !aFrame->IsTransformed()) { return; } @@ -4460,7 +4460,7 @@ nsDisplayBlendContainer::BuildLayer(nsDisplayListBuilder* aBuilder, if (!container) { return nullptr; } - + container->SetForceIsolatedGroup(true); return container.forget(); } @@ -4561,7 +4561,7 @@ nsDisplaySubDocument::BuildLayer(nsDisplayListBuilder* aBuilder, if ((mFlags & GENERATE_SCROLLABLE_LAYER) && rootScrollFrame->GetContent() && nsLayoutUtils::HasCriticalDisplayPort(rootScrollFrame->GetContent())) { - params.mInLowPrecisionDisplayPort = true; + params.mInLowPrecisionDisplayPort = true; } RefPtr layer = nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, params); @@ -4987,7 +4987,7 @@ nsDisplayScrollInfoLayer::ComputeFrameMetrics(Layer* aLayer, ContainerLayerParameters params = aContainerParameters; if (mScrolledFrame->GetContent() && nsLayoutUtils::HasCriticalDisplayPort(mScrolledFrame->GetContent())) { - params.mInLowPrecisionDisplayPort = true; + params.mInLowPrecisionDisplayPort = true; } nsRect viewport = mScrollFrame->GetRect() - @@ -5444,7 +5444,7 @@ nsDisplayTransform::GetResultingTransformMatrix(const FrameTransformProperties& return GetResultingTransformMatrixInternal(aProperties, aOrigin, aAppUnitsPerPixel, aFlags, aBoundsOverride, aOutAncestor); } - + Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix(const nsIFrame* aFrame, const nsPoint& aOrigin, diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index a69628f609..f14a95d9f4 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -142,7 +142,6 @@ using namespace mozilla::gfx; #define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled" #define GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME "layout.css.grid-template-subgrid-value.enabled" -#define STICKY_ENABLED_PREF_NAME "layout.css.sticky.enabled" #define DISPLAY_CONTENTS_ENABLED_PREF_NAME "layout.css.display-contents.enabled" #define TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME "layout.css.text-align-unsafe-value.enabled" #define FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME "layout.css.float-logical-values.enabled" @@ -223,39 +222,6 @@ GridEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) } } -// 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 void -StickyEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) -{ - MOZ_ASSERT(strncmp(aPrefName, STICKY_ENABLED_PREF_NAME, - ArrayLength(STICKY_ENABLED_PREF_NAME)) == 0, - "We only registered this callback for a single pref, so it " - "should only be called for that pref"); - - static int32_t sIndexOfStickyInPositionTable; - static bool sIsStickyKeywordIndexInitialized; // initialized to false - - 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].mKeyword = - isStickyEnabled ? eCSSKeyword_sticky : eCSSKeyword_UNKNOWN; -} - // When the pref "layout.css.display-contents.enabled" changes, this function is // invoked to let us update kDisplayKTable, to selectively disable or restore // the entries for "contents" in that table. @@ -669,6 +635,7 @@ nsLayoutUtils::IsTextAlignUnsafeValueEnabled() { static bool sTextAlignUnsafeValueEnabled; static bool sTextAlignUnsafeValueEnabledPrefCached = false; + if (!sTextAlignUnsafeValueEnabledPrefCached) { sTextAlignUnsafeValueEnabledPrefCached = true; Preferences::AddBoolVarCache(&sTextAlignUnsafeValueEnabled, @@ -950,8 +917,11 @@ GetDisplayPortFromMarginsData(nsIContent* aContent, ScreenSize alignment; if (gfxPrefs::LayersTilesEnabled()) { - alignment = ScreenSize(gfxPlatform::GetPlatform()->GetTileWidth(), - gfxPlatform::GetPlatform()->GetTileHeight()); + // Don't align to tiles if they are too large, because we could expand + // the displayport by a lot which can take more paint time. It's a tradeoff + // though because if we don't align to tiles we have more waste on upload. + alignment = ScreenSize(std::min(256, gfxPlatform::GetPlatform()->GetTileWidth()), + std::min(256, gfxPlatform::GetPlatform()->GetTileHeight())); } else { // If we're not drawing with tiles then we need to be careful about not // hitting the max texture size and we only need 1 draw call per layer @@ -4602,19 +4572,23 @@ AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext, pctTotal += pctOutsideSize; nscoord size; - if (GetAbsoluteCoord(aStyleSize, size) || - GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame, - PROP_WIDTH, size)) { + if (aType == nsLayoutUtils::MIN_ISIZE && + (((aStyleSize.HasPercent() || aStyleMaxSize.HasPercent()) && + aFrame->IsFrameOfType(nsIFrame::eReplacedSizing)) || + (aStyleSize.HasPercent() && + FormControlShrinksForPercentISize(aFrame)))) { + // A percentage width or max-width on replaced elements means they + // can shrink to 0. + // This is also true for percentage widths (but not max-widths) on + // text inputs. + // Note that if this is max-width, this overrides the fixed-width + // rule in the next condition. + result = 0; // let |min| handle padding/border/margin + } else if (GetAbsoluteCoord(aStyleSize, size) || + GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame, + PROP_WIDTH, size)) { result = nsLayoutUtils::AddPercents(aType, size + coordOutsideSize, pctOutsideSize); - } else if (aType == nsLayoutUtils::MIN_ISIZE && - // The only cases of coord-percent-calc() units that - // GetAbsoluteCoord didn't handle are percent and calc()s - // containing percent. - aStyleSize.IsCoordPercentCalcUnit() && - aFrame->IsFrameOfType(nsIFrame::eReplaced)) { - // A percentage width on replaced elements means they can shrink to 0. - result = 0; // let |min| handle padding/border/margin } else { // NOTE: We could really do a lot better for percents and for some // cases of calc() containing percent (certainly including any where @@ -7633,9 +7607,6 @@ nsLayoutUtils::Initialize() Preferences::RegisterCallback(GridEnabledPrefChangeCallback, GRID_ENABLED_PREF_NAME); GridEnabledPrefChangeCallback(GRID_ENABLED_PREF_NAME, nullptr); - Preferences::RegisterCallback(StickyEnabledPrefChangeCallback, - STICKY_ENABLED_PREF_NAME); - StickyEnabledPrefChangeCallback(STICKY_ENABLED_PREF_NAME, nullptr); Preferences::RegisterCallback(TextAlignUnsafeEnabledPrefChangeCallback, TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME); Preferences::RegisterCallback(DisplayContentsEnabledPrefChangeCallback, @@ -7663,9 +7634,6 @@ nsLayoutUtils::Shutdown() Preferences::UnregisterCallback(GridEnabledPrefChangeCallback, GRID_ENABLED_PREF_NAME); - Preferences::UnregisterCallback(StickyEnabledPrefChangeCallback, - STICKY_ENABLED_PREF_NAME); - nsComputedDOMStyle::UnregisterPrefChangeCallbacks(); } @@ -9045,9 +9013,7 @@ nsLayoutUtils::IsScrollFrameWithSnapping(nsIFrame* aFrame) if (!sf) { return false; } - ScrollbarStyles styles = sf->GetScrollbarStyles(); - return styles.mScrollSnapTypeY != NS_STYLE_SCROLL_SNAP_TYPE_NONE || - styles.mScrollSnapTypeX != NS_STYLE_SCROLL_SNAP_TYPE_NONE; + return sf->IsScrollFrameWithSnapping(); } /* static */ nsBlockFrame* @@ -9143,16 +9109,15 @@ static void UpdateDisplayPortMarginsForPendingMetrics(FrameMetrics& aMetrics) { DisplayPortMarginsPropertyData* currentData = static_cast(content->GetProperty(nsGkAtoms::DisplayPortMargins)); - if (!currentData || currentData->mPriority > 0) { + if (!currentData) { return; } CSSPoint frameScrollOffset = CSSPoint::FromAppUnits(frame->GetScrollPosition()); APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, frameScrollOffset); - content->SetProperty(nsGkAtoms::DisplayPortMargins, - new DisplayPortMarginsPropertyData(aMetrics.GetDisplayPortMargins(), 0), - nsINode::DeleteProperty); + nsLayoutUtils::SetDisplayPortMargins(content, shell, + aMetrics.GetDisplayPortMargins(), 0); } /* static */ void diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index f103f6b54c..c934e1a794 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -704,7 +704,7 @@ public: * it was propagated from. */ nsIContent* UpdateViewportScrollbarStylesOverride(); - ScrollbarStyles GetViewportScrollbarStylesOverride() + const ScrollbarStyles& GetViewportScrollbarStylesOverride() { return mViewportStyleScrollbar; } diff --git a/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py index 25d2eef338..1893cc7636 100644 --- a/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py +++ b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py @@ -20,10 +20,16 @@ class AccessibleCaretCursorModeTestCase(MarionetteTestCase): caret for short. ''' + # Element IDs. _input_id = 'input' + _input_padding_id = 'input-padding' _textarea_id = 'textarea' + _textarea_one_line_id = 'textarea-one-line' _contenteditable_id = 'contenteditable' + # Test html files. + _cursor_html = 'test_carets_cursor.html' + def setUp(self): # Code to execute before every test is running. super(AccessibleCaretCursorModeTestCase, self).setUp() @@ -36,35 +42,34 @@ class AccessibleCaretCursorModeTestCase(MarionetteTestCase): self.marionette.set_prefs(self.prefs) self.actions = Actions(self.marionette) - def open_test_html(self): - test_html = self.marionette.absolute_url('test_touchcaret.html') - self.marionette.navigate(test_html) + def open_test_html(self, test_html): + self.marionette.navigate(self.marionette.absolute_url(test_html)) @parameterized(_input_id, el_id=_input_id) @parameterized(_textarea_id, el_id=_textarea_id) @parameterized(_contenteditable_id, el_id=_contenteditable_id) def test_move_cursor_to_the_right_by_one_character(self, el_id): - self.open_test_html() + self.open_test_html(self._cursor_html) el = self.marionette.find_element(By.ID, el_id) sel = SelectionManager(el) content_to_add = '!' target_content = sel.content target_content = target_content[:1] + content_to_add + target_content[1:] - # Get touch caret (x, y) at position 1 and 2. + # Get first caret (x, y) at position 1 and 2. el.tap() - sel.move_caret_to_front() - caret0_x, caret0_y = sel.caret_location() - touch_caret0_x, touch_caret0_y = sel.touch_caret_location() - sel.move_caret_by_offset(1) - touch_caret1_x, touch_caret1_y = sel.touch_caret_location() + sel.move_cursor_to_front() + cursor0_x, cursor0_y = sel.cursor_location() + first_caret0_x, first_caret0_y = sel.first_caret_location() + sel.move_cursor_by_offset(1) + first_caret1_x, first_caret1_y = sel.first_caret_location() - # Tap the front of the input to make touch caret appear. - el.tap(caret0_x, caret0_y) + # Tap the front of the input to make first caret appear. + el.tap(cursor0_x, cursor0_y) - # Move touch caret - self.actions.flick(el, touch_caret0_x, touch_caret0_y, - touch_caret1_x, touch_caret1_y).perform() + # Move first caret. + self.actions.flick(el, first_caret0_x, first_caret0_y, + first_caret1_x, first_caret1_y).perform() self.actions.key_down(content_to_add).key_up(content_to_add).perform() self.assertEqual(target_content, sel.content) @@ -73,19 +78,19 @@ class AccessibleCaretCursorModeTestCase(MarionetteTestCase): @parameterized(_textarea_id, el_id=_textarea_id) @parameterized(_contenteditable_id, el_id=_contenteditable_id) def test_move_cursor_to_end_by_dragging_caret_to_bottom_right_corner(self, el_id): - self.open_test_html() + self.open_test_html(self._cursor_html) el = self.marionette.find_element(By.ID, el_id) sel = SelectionManager(el) content_to_add = '!' target_content = sel.content + content_to_add - # Tap the front of the input to make touch caret appear. + # Tap the front of the input to make first caret appear. el.tap() - sel.move_caret_to_front() - el.tap(*sel.caret_location()) + sel.move_cursor_to_front() + el.tap(*sel.cursor_location()) - # Move touch caret to the bottom-right corner of the element. - src_x, src_y = sel.touch_caret_location() + # Move first caret to the bottom-right corner of the element. + src_x, src_y = sel.first_caret_location() dest_x, dest_y = el.size['width'], el.size['height'] self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform() @@ -96,27 +101,27 @@ class AccessibleCaretCursorModeTestCase(MarionetteTestCase): @parameterized(_textarea_id, el_id=_textarea_id) @parameterized(_contenteditable_id, el_id=_contenteditable_id) def test_move_cursor_to_front_by_dragging_caret_to_front(self, el_id): - self.open_test_html() + self.open_test_html(self._cursor_html) el = self.marionette.find_element(By.ID, el_id) sel = SelectionManager(el) content_to_add = '!' target_content = content_to_add + sel.content - # Get touch caret location at the front. + # Get first caret location at the front. el.tap() - sel.move_caret_to_front() - dest_x, dest_y = sel.touch_caret_location() + sel.move_cursor_to_front() + dest_x, dest_y = sel.first_caret_location() - # Tap to make touch caret appear. Note: it's strange that when the caret + # Tap to make first caret appear. Note: it's strange that when the caret # is at the end, the rect of the caret in -
-
-
-
ABC DEF GHI
-
-
ABC DEF GHI
-
-
Non-selectable
- - diff --git a/testing/marionette/client/marionette/www/test_touchcaret.html b/testing/marionette/client/marionette/www/test_touchcaret.html deleted file mode 100644 index f8ccc5dfb9..0000000000 --- a/testing/marionette/client/marionette/www/test_touchcaret.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Bug 960897: Marionette tests for touch caret - - -
-
-
-
-
ABCDEFGHI
- - diff --git a/testing/marionette/driver/marionette_driver/__init__.py b/testing/marionette/client/marionette_driver/__init__.py similarity index 100% rename from testing/marionette/driver/marionette_driver/__init__.py rename to testing/marionette/client/marionette_driver/__init__.py diff --git a/testing/marionette/driver/marionette_driver/addons.py b/testing/marionette/client/marionette_driver/addons.py similarity index 100% rename from testing/marionette/driver/marionette_driver/addons.py rename to testing/marionette/client/marionette_driver/addons.py diff --git a/testing/marionette/driver/marionette_driver/by.py b/testing/marionette/client/marionette_driver/by.py similarity index 100% rename from testing/marionette/driver/marionette_driver/by.py rename to testing/marionette/client/marionette_driver/by.py diff --git a/testing/marionette/driver/marionette_driver/date_time_value.py b/testing/marionette/client/marionette_driver/date_time_value.py similarity index 100% rename from testing/marionette/driver/marionette_driver/date_time_value.py rename to testing/marionette/client/marionette_driver/date_time_value.py diff --git a/testing/marionette/driver/marionette_driver/decorators.py b/testing/marionette/client/marionette_driver/decorators.py similarity index 100% rename from testing/marionette/driver/marionette_driver/decorators.py rename to testing/marionette/client/marionette_driver/decorators.py diff --git a/testing/marionette/driver/marionette_driver/errors.py b/testing/marionette/client/marionette_driver/errors.py similarity index 100% rename from testing/marionette/driver/marionette_driver/errors.py rename to testing/marionette/client/marionette_driver/errors.py diff --git a/testing/marionette/driver/marionette_driver/expected.py b/testing/marionette/client/marionette_driver/expected.py similarity index 100% rename from testing/marionette/driver/marionette_driver/expected.py rename to testing/marionette/client/marionette_driver/expected.py diff --git a/testing/marionette/driver/marionette_driver/geckoinstance.py b/testing/marionette/client/marionette_driver/geckoinstance.py similarity index 100% rename from testing/marionette/driver/marionette_driver/geckoinstance.py rename to testing/marionette/client/marionette_driver/geckoinstance.py diff --git a/testing/marionette/driver/marionette_driver/gestures.py b/testing/marionette/client/marionette_driver/gestures.py similarity index 100% rename from testing/marionette/driver/marionette_driver/gestures.py rename to testing/marionette/client/marionette_driver/gestures.py diff --git a/testing/marionette/driver/marionette_driver/keys.py b/testing/marionette/client/marionette_driver/keys.py similarity index 100% rename from testing/marionette/driver/marionette_driver/keys.py rename to testing/marionette/client/marionette_driver/keys.py diff --git a/testing/marionette/driver/marionette_driver/selection.py b/testing/marionette/client/marionette_driver/selection.py similarity index 79% rename from testing/marionette/driver/marionette_driver/selection.py rename to testing/marionette/client/marionette_driver/selection.py index 967de2a5fb..134e45ed40 100644 --- a/testing/marionette/driver/marionette_driver/selection.py +++ b/testing/marionette/client/marionette_driver/selection.py @@ -7,6 +7,9 @@ class SelectionManager(object): '''Interface for manipulating the selection and carets of the element. + We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as + caret for short. + Simple usage example: :: @@ -41,19 +44,25 @@ class SelectionManager(object): else: return '''var sel = window.getSelection();''' - def move_caret_by_offset(self, offset, backward=False): - '''Move caret in the element by character offset.''' - cmd = self.js_selection_cmd() +\ - '''sel.modify("move", arguments[1], "character");''' - direction = 'backward' if backward else 'forward' + def move_cursor_by_offset(self, offset, backward=False): + '''Move cursor in the element by character offset. - for i in range(offset): - self.element.marionette.execute_script( - cmd, script_args=[self.element, direction], - sandbox='system') + :param offset: Move the cursor to the direction by offset characters. + :param backward: Optional, True to move backward; Default to False to + move forward. - def move_caret_to_front(self): - '''Move caret in the element to the front of the content.''' + ''' + cmd = self.js_selection_cmd() + ''' + for (let i = 0; i < %d; ++i) { + sel.modify("move", "%s", "character"); + } + ''' % (offset, 'backward' if backward else 'forward') + + self.element.marionette.execute_script( + cmd, script_args=[self.element], sandbox='system') + + def move_cursor_to_front(self): + '''Move cursor in the element to the front of the content.''' if self._input_or_textarea(): cmd = '''arguments[0].setSelectionRange(0, 0);''' else: @@ -62,8 +71,8 @@ class SelectionManager(object): self.element.marionette.execute_script(cmd, script_args=[self.element]) - def move_caret_to_end(self): - '''Move caret in the element to the end of the content.''' + def move_cursor_to_end(self): + '''Move cursor in the element to the end of the content.''' if self._input_or_textarea(): cmd = '''var len = arguments[0].value.length; arguments[0].setSelectionRange(len, len);''' @@ -104,7 +113,7 @@ class SelectionManager(object): considered. ''' - range_count = self.range_count(); + range_count = self.range_count() first_rect_list = self.selection_rect_list(0) last_rect_list = self.selection_rect_list(range_count - 1) last_list_length = last_rect_list['length'] @@ -147,33 +156,42 @@ class SelectionManager(object): ''' return self._selection_location_helper('center') - def selection_carets_location(self): - '''Return a pair of the two selection carets' location. + def carets_location(self): + '''Return a pair of the two carets' location. Return a tuple containing two pairs of (x, y) coordinates of the two - selection carets' tip. The coordinates are relative to the top left-hand - corner of the element. Both ltr and rtl direction are considered. + carets' tip. The coordinates are relative to the top left-hand corner of + the element. Both ltr and rtl direction are considered. ''' return self._selection_location_helper('caret') - def caret_location(self): - '''Return caret's center location within the element. + def cursor_location(self): + '''Return the blanking cursor's center location within the element. - Return (x, y) coordinates of the caret's center relative to the top + Return (x, y) coordinates of the cursor's center relative to the top left-hand corner of the element. ''' return self._selection_location_helper('center')[0] - def touch_caret_location(self): - '''Return touch caret's location (based on current caret location). + def first_caret_location(self): + '''Return the first caret's location. - Return (x, y) coordinates of the touch caret's tip relative to the top + Return (x, y) coordinates of the first caret's tip relative to the top left-hand corner of the element. ''' - return self._selection_location_helper('caret')[0] + return self.carets_location()[0] + + def second_caret_location(self): + '''Return the second caret's location. + + Return (x, y) coordinates of the second caret's tip relative to the top + left-hand corner of the element. + + ''' + return self.carets_location()[1] def select_all(self): '''Select all the content in the element.''' diff --git a/testing/marionette/driver/marionette_driver/transport.py b/testing/marionette/client/marionette_driver/transport.py similarity index 100% rename from testing/marionette/driver/marionette_driver/transport.py rename to testing/marionette/client/marionette_driver/transport.py diff --git a/testing/marionette/driver/marionette_driver/wait.py b/testing/marionette/client/marionette_driver/wait.py similarity index 100% rename from testing/marionette/driver/marionette_driver/wait.py rename to testing/marionette/client/marionette_driver/wait.py diff --git a/testing/marionette/client/requirements.txt b/testing/marionette/client/requirements.txt index 7058980bbc..a77e91f0e8 100644 --- a/testing/marionette/client/requirements.txt +++ b/testing/marionette/client/requirements.txt @@ -1,14 +1 @@ -marionette-driver >= 1.3.0 -browsermob-proxy >= 0.6.0 -manifestparser >= 1.1 -wptserve >= 1.3.0 -mozinfo >= 0.8 -mozprocess >= 0.9 mozrunner >= 6.9 -mozdevice >= 0.44 -mozlog >= 3.0 -moznetwork >= 0.21 -mozcrash >= 0.5 -mozprofile >= 0.7 -moztest >= 0.7 -mozversion >= 1.1 diff --git a/testing/marionette/client/setup.py b/testing/marionette/client/setup.py index c768cd1d79..9a1d999191 100644 --- a/testing/marionette/client/setup.py +++ b/testing/marionette/client/setup.py @@ -1,8 +1,11 @@ +# 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/. + import os import re from setuptools import setup, find_packages - THIS_DIR = os.path.dirname(os.path.realpath(__name__)) @@ -13,27 +16,21 @@ def read(*parts): def get_version(): return re.findall("__version__ = '([\d\.]+)'", - read('marionette', '__init__.py'), re.M)[0] + read('marionette_driver', '__init__.py'), re.M)[0] -setup(name='marionette_client', +setup(name='marionette_driver', version=get_version(), - description="Marionette test automation client", - long_description='See http://marionette-client.readthedocs.org/', + description="Marionette Driver", + long_description='See http://marionette-client.readthedocs.org/en/latest/', classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers keywords='mozilla', - author='Jonathan Griffin', - author_email='jgriffin@mozilla.com', + author='Auto-tools', + author_email='tools-marionette@lists.mozilla.org', url='https://wiki.mozilla.org/Auto-tools/Projects/Marionette', license='MPL', - packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), - package_data={'marionette': ['touch/*.js']}, + packages=find_packages(), include_package_data=True, zip_safe=False, - entry_points=""" - # -*- Entry points: -*- - [console_scripts] - marionette = marionette.runtests:cli - """, install_requires=read('requirements.txt').splitlines(), ) diff --git a/testing/marionette/driver/MANIFEST.in b/testing/marionette/driver/MANIFEST.in deleted file mode 100644 index cf628b039c..0000000000 --- a/testing/marionette/driver/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -exclude MANIFEST.in -include requirements.txt diff --git a/testing/marionette/driver/marionette_driver/marionette.py b/testing/marionette/driver/marionette_driver/marionette.py deleted file mode 100644 index e9fed002cc..0000000000 --- a/testing/marionette/driver/marionette_driver/marionette.py +++ /dev/null @@ -1,1990 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import base64 -import ConfigParser -import json -import os -import socket -import StringIO -import traceback -import warnings - -from contextlib import contextmanager - -from decorators import do_crash_check -from keys import Keys - -from mozrunner import B2GEmulatorRunner - -import geckoinstance -import errors -import transport - -WEBELEMENT_KEY = "ELEMENT" -W3C_WEBELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf" - - -class HTMLElement(object): - """ - Represents a DOM Element. - """ - - def __init__(self, marionette, id): - self.marionette = marionette - assert(id is not None) - self.id = id - - def __str__(self): - return self.id - - def __eq__(self, other_element): - return self.id == other_element.id - - def find_element(self, method, target): - """Returns an ``HTMLElement`` instance that matches the specified - method and target, relative to the current element. - - For more details on this function, see the `find_element` method - in the Marionette class. - """ - return self.marionette.find_element(method, target, self.id) - - def find_elements(self, method, target): - """Returns a list of all ``HTMLElement`` instances that match the - specified method and target in the current context. - - For more details on this function, see the find_elements method - in the Marionette class. - """ - return self.marionette.find_elements(method, target, self.id) - - def get_attribute(self, attribute): - """Returns the requested attribute, or None if no attribute - is set. - - :param attribute: The name of the attribute. - """ - body = {"id": self.id, "name": attribute} - return self.marionette._send_message("getElementAttribute", body, key="value") - - def click(self): - self.marionette._send_message("clickElement", {"id": self.id}) - - def tap(self, x=None, y=None): - """Simulates a set of tap events on the element. - - :param x: X coordinate of tap event. If not given, default to - the centre of the element. - :param y: Y coordinate of tap event. If not given, default to - the centre of the element. - """ - body = {"id": self.id, "x": x, "y": y} - self.marionette._send_message("singleTap", body) - - @property - def text(self): - """Returns the visible text of the element, and its child elements.""" - body = {"id": self.id} - return self.marionette._send_message("getElementText", body, key="value") - - def send_keys(self, *string): - """Sends the string via synthesized keypresses to the element.""" - keys = Marionette.convert_keys(*string) - body = {"id": self.id, "value": keys} - self.marionette._send_message("sendKeysToElement", body) - - def clear(self): - """Clears the input of the element.""" - self.marionette._send_message("clearElement", {"id": self.id}) - - def is_selected(self): - """Returns True if the element is selected.""" - body = {"id": self.id} - return self.marionette._send_message("isElementSelected", body, key="value") - - def is_enabled(self): - """This command will return False if all the following criteria - are met otherwise return True: - - * A form control is disabled. - * A ``HTMLElement`` has a disabled boolean attribute. - """ - body = {"id": self.id} - return self.marionette._send_message("isElementEnabled", body, key="value") - - def is_displayed(self): - """Returns True if the element is displayed, False otherwise.""" - body = {"id": self.id} - return self.marionette._send_message("isElementDisplayed", body, key="value") - - @property - def size(self): - """A dictionary with the size of the element.""" - warnings.warn("The size property has been deprecated and will be removed in a future version. \ - Please use HTMLElement#rect", DeprecationWarning) - rect = self.rect - return {"width": rect["width"], "height": rect["height"]} - - @property - def tag_name(self): - """The tag name of the element.""" - body = {"id": self.id} - return self.marionette._send_message("getElementTagName", body, key="value") - - @property - def location(self): - """Get an element's location on the page. - - The returned point will contain the x and y coordinates of the - top left-hand corner of the given element. The point (0,0) - refers to the upper-left corner of the document. - - :returns: a dictionary containing x and y as entries - """ - warnings.warn("The location property has been deprecated and will be removed in a future version. \ - Please use HTMLElement#rect", DeprecationWarning) - rect = self.rect - return {"x": rect["x"], "y": rect["y"]} - - @property - def rect(self): - """Gets the element's bounding rectangle. - - This will return a dictionary with the following: - - * x and y represent the top left coordinates of the ``HTMLElement`` - relative to top left corner of the document. - * height and the width will contain the height and the width - of the DOMRect of the ``HTMLElement``. - """ - body = {"id": self.id} - return self.marionette._send_message( - "getElementRect", body, key="value" if self.marionette.protocol == 1 else None) - - def value_of_css_property(self, property_name): - """Gets the value of the specified CSS property name. - - :param property_name: Property name to get the value of. - """ - body = {"id": self.id, "propertyName": property_name} - return self.marionette._send_message( - "getElementValueOfCssProperty", body, key="value") - - -class MouseButton(object): - """Enum-like class for mouse button constants.""" - LEFT = 0 - MIDDLE = 1 - RIGHT = 2 - - -class Actions(object): - ''' - An Action object represents a set of actions that are executed in a particular order. - - All action methods (press, etc.) return the Actions object itself, to make - it easy to create a chain of events. - - Example usage: - - :: - - # get html file - testAction = marionette.absolute_url("testFool.html") - # navigate to the file - marionette.navigate(testAction) - # find element1 and element2 - element1 = marionette.find_element("id", "element1") - element2 = marionette.find_element("id", "element2") - # create action object - action = Actions(marionette) - # add actions (press, wait, move, release) into the object - action.press(element1).wait(5). move(element2).release() - # fire all the added events - action.perform() - ''' - - def __init__(self, marionette): - self.action_chain = [] - self.marionette = marionette - self.current_id = None - - def press(self, element, x=None, y=None): - ''' - Sends a 'touchstart' event to this element. - - If no coordinates are given, it will be targeted at the center of the - element. If given, it will be targeted at the (x,y) coordinates - relative to the top-left corner of the element. - - :param element: The element to press on. - :param x: Optional, x-coordinate to tap, relative to the top-left - corner of the element. - :param y: Optional, y-coordinate to tap, relative to the top-left - corner of the element. - ''' - element=element.id - self.action_chain.append(['press', element, x, y]) - return self - - def release(self): - ''' - Sends a 'touchend' event to this element. - - May only be called if press() has already be called on this element. - - If press and release are chained without a move action between them, - then it will be processed as a 'tap' event, and will dispatch the - expected mouse events ('mousemove' (if necessary), 'mousedown', - 'mouseup', 'mouseclick') after the touch events. If there is a wait - period between press and release that will trigger a contextmenu, - then the 'contextmenu' menu event will be fired instead of the - touch/mouse events. - ''' - self.action_chain.append(['release']) - return self - - def move(self, element): - ''' - Sends a 'touchmove' event at the center of the target element. - - :param element: Element to move towards. - - May only be called if press() has already be called. - ''' - element=element.id - self.action_chain.append(['move', element]) - return self - - def move_by_offset(self, x, y): - ''' - Sends 'touchmove' event to the given x, y coordinates relative to the top-left of the currently touched element. - - May only be called if press() has already be called. - - :param x: Specifies x-coordinate of move event, relative to the - top-left corner of the element. - :param y: Specifies y-coordinate of move event, relative to the - top-left corner of the element. - ''' - self.action_chain.append(['moveByOffset', x, y]) - return self - - def wait(self, time=None): - ''' - Waits for specified time period. - - :param time: Time in seconds to wait. If time is None then this has no effect for a single action chain. If used inside a multi-action chain, then time being None indicates that we should wait for all other currently executing actions that are part of the chain to complete. - ''' - self.action_chain.append(['wait', time]) - return self - - def cancel(self): - ''' - Sends 'touchcancel' event to the target of the original 'touchstart' event. - - May only be called if press() has already be called. - ''' - self.action_chain.append(['cancel']) - return self - - def tap(self, element, x=None, y=None): - ''' - Performs a quick tap on the target element. - - :param element: The element to tap. - :param x: Optional, x-coordinate of tap, relative to the top-left - corner of the element. If not specified, default to center of - element. - :param y: Optional, y-coordinate of tap, relative to the top-left - corner of the element. If not specified, default to center of - element. - - This is equivalent to calling: - - :: - - action.press(element, x, y).release() - ''' - element=element.id - self.action_chain.append(['press', element, x, y]) - self.action_chain.append(['release']) - return self - - def double_tap(self, element, x=None, y=None): - ''' - Performs a double tap on the target element. - - :param element: The element to double tap. - :param x: Optional, x-coordinate of double tap, relative to the - top-left corner of the element. - :param y: Optional, y-coordinate of double tap, relative to the - top-left corner of the element. - ''' - element=element.id - self.action_chain.append(['press', element, x, y]) - self.action_chain.append(['release']) - self.action_chain.append(['press', element, x, y]) - self.action_chain.append(['release']) - return self - - def click(self, element, button=MouseButton.LEFT, count=1): - ''' - Performs a click with additional parameters to allow for double clicking, - right click, middle click, etc. - - :param element: The element to click. - :param button: The mouse button to click (indexed from 0, left to right). - :param count: Optional, the count of clicks to synthesize (for double - click events). - ''' - el = element.id - self.action_chain.append(['click', el, button, count]) - return self - - def context_click(self, element): - ''' - Performs a context click on the specified element. - - :param element: The element to context click. - ''' - return self.click(element, button=MouseButton.RIGHT) - - def middle_click(self, element): - ''' - Performs a middle click on the specified element. - - :param element: The element to middle click. - ''' - return self.click(element, button=MouseButton.MIDDLE) - - def double_click(self, element): - ''' - Performs a double click on the specified element. - - :param element: The element to double click. - ''' - return self.click(element, count=2) - - def flick(self, element, x1, y1, x2, y2, duration=200): - ''' - Performs a flick gesture on the target element. - - :param element: The element to perform the flick gesture on. - :param x1: Starting x-coordinate of flick, relative to the top left - corner of the element. - :param y1: Starting y-coordinate of flick, relative to the top left - corner of the element. - :param x2: Ending x-coordinate of flick, relative to the top left - corner of the element. - :param y2: Ending y-coordinate of flick, relative to the top left - corner of the element. - :param duration: Time needed for the flick gesture for complete (in - milliseconds). - ''' - element = element.id - elapsed = 0 - time_increment = 10 - if time_increment >= duration: - time_increment = duration - move_x = time_increment*1.0/duration * (x2 - x1) - move_y = time_increment*1.0/duration * (y2 - y1) - self.action_chain.append(['press', element, x1, y1]) - while elapsed < duration: - elapsed += time_increment - self.action_chain.append(['moveByOffset', move_x, move_y]) - self.action_chain.append(['wait', time_increment/1000]) - self.action_chain.append(['release']) - return self - - def long_press(self, element, time_in_seconds, x=None, y=None): - ''' - Performs a long press gesture on the target element. - - :param element: The element to press. - :param time_in_seconds: Time in seconds to wait before releasing the press. - :param x: Optional, x-coordinate to tap, relative to the top-left - corner of the element. - :param y: Optional, y-coordinate to tap, relative to the top-left - corner of the element. - - This is equivalent to calling: - - :: - - action.press(element, x, y).wait(time_in_seconds).release() - - ''' - element = element.id - self.action_chain.append(['press', element, x, y]) - self.action_chain.append(['wait', time_in_seconds]) - self.action_chain.append(['release']) - return self - - def key_down(self, key_code): - """ - Perform a "keyDown" action for the given key code. Modifier keys are - respected by the server for the course of an action chain. - - :param key_code: The key to press as a result of this action. - """ - self.action_chain.append(['keyDown', key_code]) - return self - - def key_up(self, key_code): - """ - Perform a "keyUp" action for the given key code. Modifier keys are - respected by the server for the course of an action chain. - :param key_up: The key to release as a result of this action. - """ - self.action_chain.append(['keyUp', key_code]) - return self - - def perform(self): - """Sends the action chain built so far to the server side for - execution and clears the current chain of actions.""" - body = {"chain": self.action_chain, "nextId": self.current_id} - self.current_id = self.marionette._send_message("actionChain", body, key="value") - self.action_chain = [] - return self - -class MultiActions(object): - ''' - A MultiActions object represents a sequence of actions that may be - performed at the same time. Its intent is to allow the simulation - of multi-touch gestures. - Usage example: - - :: - - # create multiaction object - multitouch = MultiActions(marionette) - # create several action objects - action_1 = Actions(marionette) - action_2 = Actions(marionette) - # add actions to each action object/finger - action_1.press(element1).move_to(element2).release() - action_2.press(element3).wait().release(element3) - # fire all the added events - multitouch.add(action_1).add(action_2).perform() - ''' - - def __init__(self, marionette): - self.multi_actions = [] - self.max_length = 0 - self.marionette = marionette - - def add(self, action): - ''' - Adds a set of actions to perform. - - :param action: An Actions object. - ''' - self.multi_actions.append(action.action_chain) - if len(action.action_chain) > self.max_length: - self.max_length = len(action.action_chain) - return self - - def perform(self): - """Perform all the actions added to this object.""" - body = {"value": self.multi_actions, "max_length": self.max_length} - self.marionette._send_message("multiAction", body) - - -class Alert(object): - """A class for interacting with alerts. - - :: - - Alert(marionette).accept() - Alert(merionette).dismiss() - """ - - def __init__(self, marionette): - self.marionette = marionette - - def accept(self): - """Accept a currently displayed modal dialog.""" - self.marionette._send_message("acceptDialog") - - def dismiss(self): - """Dismiss a currently displayed modal dialog.""" - self.marionette._send_message("dismissDialog") - - @property - def text(self): - """Return the currently displayed text in a tab modal.""" - return self.marionette._send_message("getTextFromDialog", key="value") - - def send_keys(self, *string): - """Send keys to the currently displayed text input area in an open - tab modal dialog.""" - body = {"value": Marionette.convert_keys(*string)} - self.marionette._send_message("sendKeysToDialog", body) - - -class Marionette(object): - """Represents a Marionette connection to a browser or device.""" - - CONTEXT_CHROME = 'chrome' # non-browser content: windows, dialogs, etc. - CONTEXT_CONTENT = 'content' # browser content: iframes, divs, etc. - TIMEOUT_SEARCH = 'implicit' - TIMEOUT_SCRIPT = 'script' - TIMEOUT_PAGE = 'page load' - DEFAULT_STARTUP_TIMEOUT = 60 - - def __init__(self, host='localhost', port=2828, app=None, app_args=None, bin=None, - profile=None, addons=None, emulator=None, sdcard=None, emulator_img=None, - emulator_binary=None, emulator_res=None, connect_to_running_emulator=False, - gecko_log=None, homedir=None, baseurl=None, no_window=False, logdir=None, - busybox=None, symbols_path=None, timeout=None, socket_timeout=360, - device_serial=None, adb_path=None, process_args=None, - adb_host=None, adb_port=None, prefs=None, startup_timeout=None): - self.host = host - self.port = self.local_port = port - self.bin = bin - self.profile = profile - self.addons = addons - self.instance = None - self.session = None - self.session_id = None - self.window = None - self.chrome_window = None - self.runner = None - self.emulator = None - self.extra_emulators = [] - self.baseurl = baseurl - self.no_window = no_window - self._test_name = None - self.timeout = timeout - self.socket_timeout = socket_timeout - self.device_serial = device_serial - self.adb_host = adb_host - self.adb_port = adb_port - - startup_timeout = startup_timeout or self.DEFAULT_STARTUP_TIMEOUT - - if bin: - port = int(self.port) - if not Marionette.is_port_available(port, host=self.host): - ex_msg = "%s:%d is unavailable." % (self.host, port) - raise errors.MarionetteException(message=ex_msg) - if app: - # select instance class for the given app - try: - instance_class = geckoinstance.apps[app] - except KeyError: - msg = 'Application "%s" unknown (should be one of %s)' - raise NotImplementedError(msg % (app, geckoinstance.apps.keys())) - else: - try: - config = ConfigParser.RawConfigParser() - config.read(os.path.join(os.path.dirname(bin), 'application.ini')) - app = config.get('App', 'Name') - instance_class = geckoinstance.apps[app.lower()] - except (ConfigParser.NoOptionError, - ConfigParser.NoSectionError, - KeyError): - instance_class = geckoinstance.GeckoInstance - self.instance = instance_class(host=self.host, port=self.port, - bin=self.bin, profile=self.profile, - app_args=app_args, symbols_path=symbols_path, - gecko_log=gecko_log, prefs=prefs, - addons=self.addons) - self.instance.start() - assert(self.wait_for_port(timeout=startup_timeout)), "Timed out waiting for port!" - - if emulator: - self.runner = B2GEmulatorRunner(b2g_home=homedir, - no_window=self.no_window, - logdir=logdir, - arch=emulator, - sdcard=sdcard, - symbols_path=symbols_path, - binary=emulator_binary, - userdata=emulator_img, - resolution=emulator_res, - profile=self.profile, - addons=self.addons, - adb_path=adb_path, - process_args=process_args) - self.emulator = self.runner.device - self.emulator.start() - self.port = self.emulator.setup_port_forwarding(remote_port=self.port) - assert(self.emulator.wait_for_port(self.port)), "Timed out waiting for port!" - - if connect_to_running_emulator: - self.runner = B2GEmulatorRunner(b2g_home=homedir, - logdir=logdir, - process_args=process_args) - self.emulator = self.runner.device - self.emulator.connect() - self.port = self.emulator.setup_port_forwarding(remote_port=self.port) - assert(self.emulator.wait_for_port(self.port)), "Timed out waiting for port!" - - if emulator: - if busybox: - self.emulator.install_busybox(busybox=busybox) - self.emulator.wait_for_system_message(self) - - # for callbacks from a protocol level 2 or lower remote, - # we store the callback ID so it can be used by _send_emulator_result - self.emulator_callback_id = None - - def cleanup(self): - if self.session: - try: - self.delete_session() - except (errors.MarionetteException, socket.error, IOError): - # These exceptions get thrown if the Marionette server - # hit an exception/died or the connection died. We can - # do no further server-side cleanup in this case. - pass - self.session = None - if self.runner: - self.runner.cleanup() - if self.instance: - self.instance.close() - for qemu in self.extra_emulators: - qemu.emulator.close() - - def __del__(self): - self.cleanup() - - @staticmethod - def is_port_available(port, host=''): - port = int(port) - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - try: - s.bind((host, port)) - return True - except socket.error: - return False - finally: - s.close() - - def wait_for_port(self, timeout=60): - return transport.wait_for_port(self.host, self.port, timeout=timeout) - - @do_crash_check - def _send_message(self, name, params=None, key=None): - if not self.session_id and name != "newSession": - raise errors.MarionetteException("Please start a session") - - try: - if self.protocol < 3: - data = {"name": name} - if params: - data["parameters"] = params - self.client.send(data) - msg = self.client.receive() - - else: - msg = self.client.request(name, params) - - except IOError: - if self.instance and not hasattr(self.instance, 'detached'): - # If we've launched the binary we've connected to, wait - # for it to shut down. - returncode = self.instance.runner.wait(timeout=self.DEFAULT_STARTUP_TIMEOUT) - raise IOError("process died with returncode %s" % returncode) - raise - except socket.timeout: - self.session = None - self.window = None - self.client.close() - raise errors.TimeoutException("Connection timed out") - - if isinstance(msg, transport.Command): - if msg.name == "runEmulatorCmd": - self.emulator_callback_id = msg.params.get("id") - msg = self._emulator_cmd(msg.params["emulator_cmd"]) - elif msg.name == "runEmulatorShell": - self.emulator_callback_id = msg.params.get("id") - msg = self._emulator_shell(msg.params["emulator_shell"]) - else: - raise IOError("Unknown command: %s" % msg) - - res, err = msg.result, msg.error - if err: - self._handle_error(err) - - if key is not None: - return self._unwrap_response(res.get(key)) - else: - return self._unwrap_response(res) - - def _unwrap_response(self, value): - if isinstance(value, dict) and \ - (WEBELEMENT_KEY in value or W3C_WEBELEMENT_KEY in value): - if value.get(WEBELEMENT_KEY): - return HTMLElement(self, value.get(WEBELEMENT_KEY)) - else: - return HTMLElement(self, value.get(W3C_WEBELEMENT_KEY)) - elif isinstance(value, list): - return list(self._unwrap_response(item) for item in value) - else: - return value - - def _emulator_cmd(self, cmd): - if not self.emulator: - raise errors.MarionetteException( - "No emulator in this test to run command against") - payload = cmd.encode("ascii") - result = self.emulator._run_telnet(payload) - return self._send_emulator_result(result) - - def _emulator_shell(self, args): - if not isinstance(args, list) or not self.emulator: - raise errors.MarionetteException( - "No emulator in this test to run shell command against") - buf = StringIO.StringIO() - self.emulator.dm.shell(args, buf) - result = str(buf.getvalue()[0:-1]).rstrip().splitlines() - buf.close() - return self._send_emulator_result(result) - - def _send_emulator_result(self, result): - if self.protocol < 3: - body = {"name": "emulatorCmdResult", - "id": self.emulator_callback_id, - "result": result} - self.client.send(body) - return self.client.receive() - else: - return self.client.respond(result) - - def _handle_error(self, obj): - if self.protocol == 1: - if "error" not in obj or not isinstance(obj["error"], dict): - raise errors.MarionetteException( - "Malformed packet, expected key 'error' to be a dict: %s" % obj) - error = obj["error"].get("status") - message = obj["error"].get("message") - stacktrace = obj["error"].get("stacktrace") - - else: - error = obj["error"] - message = obj["message"] - stacktrace = obj["stacktrace"] - - raise errors.lookup(error)(message, stacktrace=stacktrace) - - def _reset_timeouts(self): - if self.timeout is not None: - self.timeouts(self.TIMEOUT_SEARCH, self.timeout) - self.timeouts(self.TIMEOUT_SCRIPT, self.timeout) - self.timeouts(self.TIMEOUT_PAGE, self.timeout) - else: - self.timeouts(self.TIMEOUT_PAGE, 30000) - - def check_for_crash(self): - returncode = None - name = None - crashed = False - if self.runner: - if self.runner.check_for_crashes(test_name=self.test_name): - returncode = self.emulator.proc.returncode - name = 'emulator' - crashed = True - elif self.instance: - if self.instance.runner.check_for_crashes( - test_name=self.test_name): - crashed = True - if returncode is not None: - print ('PROCESS-CRASH | %s | abnormal termination with exit code %d' % - (name, returncode)) - return crashed - - @staticmethod - def convert_keys(*string): - typing = [] - for val in string: - if isinstance(val, Keys): - typing.append(val) - elif isinstance(val, int): - val = str(val) - for i in range(len(val)): - typing.append(val[i]) - else: - for i in range(len(val)): - typing.append(val[i]) - return typing - - def get_permission(self, perm): - with self.using_context('content'): - value = self.execute_script(""" - let value = { - 'url': document.nodePrincipal.URI.spec, - 'appId': document.nodePrincipal.appId, - 'isInBrowserElement': document.nodePrincipal.isInBrowserElement, - 'type': arguments[0] - }; - return value; - """, script_args=[perm], sandbox='system') - - with self.using_context('chrome'): - permission = self.execute_script(""" - Components.utils.import("resource://gre/modules/Services.jsm"); - let perm = arguments[0]; - let secMan = Services.scriptSecurityManager; - let attrs = {appId: perm.appId, inBrowser: perm.isInBrowserElement}; - let principal = secMan.createCodebasePrincipal( - Services.io.newURI(perm.url, null, null), - attrs); - let testPerm = Services.perms.testPermissionFromPrincipal( - principal, perm.type); - return testPerm; - """, script_args=[value]) - return permission - - def push_permission(self, perm, allow): - with self.using_context('content'): - perm = self.execute_script(""" - let allow = arguments[0]; - if (typeof(allow) == "boolean") { - if (allow) { - allow = Components.interfaces.nsIPermissionManager.ALLOW_ACTION; - } - else { - allow = Components.interfaces.nsIPermissionManager.DENY_ACTION; - } - } - let perm_type = arguments[1]; - - Components.utils.import("resource://gre/modules/Services.jsm"); - window.wrappedJSObject.permChanged = false; - window.wrappedJSObject.permObserver = function(subject, topic, data) { - if (topic == "perm-changed") { - let permission = subject.QueryInterface(Components.interfaces.nsIPermission); - if (perm_type == permission.type) { - Services.obs.removeObserver(window.wrappedJSObject.permObserver, "perm-changed"); - window.wrappedJSObject.permChanged = true; - } - } - }; - Services.obs.addObserver(window.wrappedJSObject.permObserver, - "perm-changed", false); - - let value = { - 'url': document.nodePrincipal.URI.spec, - 'appId': document.nodePrincipal.appId, - 'isInBrowserElement': document.nodePrincipal.isInBrowserElement, - 'type': perm_type, - 'action': allow - }; - return value; - """, script_args=[allow, perm], sandbox='system') - - current_perm = self.get_permission(perm['type']) - if current_perm == perm['action']: - with self.using_context('content'): - self.execute_script(""" - Components.utils.import("resource://gre/modules/Services.jsm"); - Services.obs.removeObserver(window.wrappedJSObject.permObserver, "perm-changed"); - """, sandbox='system') - return - - with self.using_context('chrome'): - self.execute_script(""" - Components.utils.import("resource://gre/modules/Services.jsm"); - let perm = arguments[0]; - let secMan = Services.scriptSecurityManager; - let attrs = {appId: perm.appId, inBrowser: perm.isInBrowserElement}; - let principal = secMan.createCodebasePrincipal(Services.io.newURI(perm.url, null, null), - attrs); - Services.perms.addFromPrincipal(principal, perm.type, perm.action); - return true; - """, script_args=[perm]) - - with self.using_context('content'): - self.execute_async_script(""" - waitFor(marionetteScriptFinished, function() { - return window.wrappedJSObject.permChanged; - }); - """, sandbox='system') - - @contextmanager - def using_permissions(self, perms): - ''' - Sets permissions for code being executed in a `with` block, - and restores them on exit. - - :param perms: A dict containing one or more perms and their - values to be set. - - Usage example:: - - with marionette.using_permissions({'systemXHR': True}): - ... do stuff ... - ''' - original_perms = {} - for perm in perms: - original_perms[perm] = self.get_permission(perm) - self.push_permission(perm, perms[perm]) - - try: - yield - finally: - for perm in original_perms: - self.push_permission(perm, original_perms[perm]) - - def get_pref(self, pref): - '''Gets the preference value. - - :param pref: Name of the preference. - - Usage example:: - - marionette.get_pref('browser.tabs.warnOnClose') - - ''' - with self.using_context(self.CONTEXT_CONTENT): - pref_value = self.execute_script(""" - Components.utils.import("resource://gre/modules/Services.jsm"); - let pref = arguments[0]; - let type = Services.prefs.getPrefType(pref); - switch (type) { - case Services.prefs.PREF_STRING: - return Services.prefs.getCharPref(pref); - case Services.prefs.PREF_INT: - return Services.prefs.getIntPref(pref); - case Services.prefs.PREF_BOOL: - return Services.prefs.getBoolPref(pref); - case Services.prefs.PREF_INVALID: - return null; - } - """, script_args=[pref], sandbox='system') - return pref_value - - def clear_pref(self, pref): - with self.using_context(self.CONTEXT_CHROME): - self.execute_script(""" - Components.utils.import("resource://gre/modules/Services.jsm"); - let pref = arguments[0]; - Services.prefs.clearUserPref(pref); - """, script_args=[pref]) - - def set_pref(self, pref, value): - with self.using_context(self.CONTEXT_CHROME): - if value is None: - self.clear_pref(pref) - return - - if isinstance(value, bool): - func = 'setBoolPref' - elif isinstance(value, (int, long)): - func = 'setIntPref' - elif isinstance(value, basestring): - func = 'setCharPref' - else: - raise errors.MarionetteException( - "Unsupported preference type: %s" % type(value)) - - self.execute_script(""" - Components.utils.import("resource://gre/modules/Services.jsm"); - let pref = arguments[0]; - let value = arguments[1]; - Services.prefs.%s(pref, value); - """ % func, script_args=[pref, value]) - - def set_prefs(self, prefs): - '''Sets preferences. - - If the value of the preference to be set is None, reset the preference - to its default value. If no default value exists, the preference will - cease to exist. - - :param prefs: A dict containing one or more preferences and their values - to be set. - - Usage example:: - - marionette.set_prefs({'browser.tabs.warnOnClose': True}) - - ''' - for pref, value in prefs.items(): - self.set_pref(pref, value) - - @contextmanager - def using_prefs(self, prefs): - '''Sets preferences for code being executed in a `with` block, - and restores them on exit. - - :param prefs: A dict containing one or more preferences and their values - to be set. - - Usage example:: - - with marionette.using_prefs({'browser.tabs.warnOnClose': True}): - # ... do stuff ... - - ''' - original_prefs = {p: self.get_pref(p) for p in prefs} - self.set_prefs(prefs) - - try: - yield - finally: - self.set_prefs(original_prefs) - - def enforce_gecko_prefs(self, prefs): - """ - Checks if the running instance has the given prefs. If not, it will kill the - currently running instance, and spawn a new instance with the requested preferences. - - : param prefs: A dictionary whose keys are preference names. - """ - if not self.instance: - raise errors.MarionetteException("enforce_gecko_prefs can only be called " \ - "on gecko instances launched by Marionette") - pref_exists = True - self.set_context(self.CONTEXT_CHROME) - for pref, value in prefs.iteritems(): - if type(value) is not str: - value = json.dumps(value) - pref_exists = self.execute_script(""" - let prefInterface = Components.classes["@mozilla.org/preferences-service;1"] - .getService(Components.interfaces.nsIPrefBranch); - let pref = '%s'; - let value = '%s'; - let type = prefInterface.getPrefType(pref); - switch(type) { - case prefInterface.PREF_STRING: - return value == prefInterface.getCharPref(pref).toString(); - case prefInterface.PREF_BOOL: - return value == prefInterface.getBoolPref(pref).toString(); - case prefInterface.PREF_INT: - return value == prefInterface.getIntPref(pref).toString(); - case prefInterface.PREF_INVALID: - return false; - } - """ % (pref, value)) - if not pref_exists: - break - self.set_context(self.CONTEXT_CONTENT) - if not pref_exists: - self.delete_session() - self.instance.restart(prefs) - assert(self.wait_for_port()), "Timed out waiting for port!" - self.start_session() - self._reset_timeouts() - - def restart(self, clean=False, in_app=False): - """ - This will terminate the currently running instance, and spawn a new instance - with the same profile and then reuse the session id when creating a session again. - - : param clean: If False the same profile will be used after the restart. Note - that the in app initiated restart always maintains the same - profile. - : param in_app: If True, marionette will cause a restart from within the - browser. Otherwise the browser will be restarted immediately - by killing the process. - """ - if not self.instance: - raise errors.MarionetteException("restart can only be called " \ - "on gecko instances launched by Marionette") - - if in_app: - if clean: - raise ValueError - # Values here correspond to constants in nsIAppStartup. - # See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup - restart_flags = [ - "eForceQuit", - "eRestart", - ] - self._send_message("quitApplication", {"flags": restart_flags}) - self.client.close() - # The instance is restarting itself; we will no longer be able to - # track it by pid, so mark it as 'detached'. - self.instance.detached = True - else: - self.delete_session() - self.instance.restart(clean=clean) - assert(self.wait_for_port()), "Timed out waiting for port!" - self.start_session(session_id=self.session_id) - self._reset_timeouts() - - def absolute_url(self, relative_url): - ''' - Returns an absolute url for files served from Marionette's www directory. - - :param relative_url: The url of a static file, relative to Marionette's www directory. - ''' - return "%s%s" % (self.baseurl, relative_url) - - def start_session(self, desired_capabilities=None, session_id=None, timeout=60): - """Create a new Marionette session. - - This method must be called before performing any other action. - - :param desired_capabilities: An optional dict of desired - capabilities. This is currently ignored. - :param timeout: Timeout in seconds for the server to be ready. - :param session_id: unique identifier for the session. If no session id is - passed in then one will be generated by the marionette server. - - :returns: A dict of the capabilities offered.""" - if self.instance: - returncode = self.instance.runner.process_handler.proc.returncode - if returncode is not None: - # We're managing a binary which has terminated, so restart it. - self.instance.restart() - - self.client = transport.TcpTransport( - self.host, - self.port, - self.socket_timeout) - self.protocol, _ = self.client.connect() - self.wait_for_port(timeout=timeout) - - body = {"capabilities": desired_capabilities, "sessionId": session_id} - resp = self._send_message("newSession", body) - - self.session_id = resp["sessionId"] - self.session = resp["value"] if self.protocol == 1 else resp["capabilities"] - self.b2g = "b2g" in self.session - - return self.session - - @property - def test_name(self): - return self._test_name - - @test_name.setter - def test_name(self, test_name): - if self._send_message("setTestName", {"value": test_name}): - self._test_name = test_name - - def delete_session(self): - """Close the current session and disconnect from the server.""" - self._send_message("deleteSession") - self.session_id = None - self.session = None - self.window = None - self.client.close() - - @property - def session_capabilities(self): - ''' - A JSON dictionary representing the capabilities of the current session. - ''' - return self.session - - def set_script_timeout(self, timeout): - """Sets the maximum number of ms that an asynchronous script is - allowed to run. - - If a script does not return in the specified amount of time, - a ScriptTimeoutException is raised. - - :param timeout: The maximum number of milliseconds an asynchronous - script can run without causing an ScriptTimeoutException to - be raised - """ - self._send_message("setScriptTimeout", {"ms": timeout}) - - def set_search_timeout(self, timeout): - """Sets a timeout for the find methods. - - When searching for an element using - either :class:`Marionette.find_element` or - :class:`Marionette.find_elements`, the method will continue - trying to locate the element for up to timeout ms. This can be - useful if, for example, the element you're looking for might - not exist immediately, because it belongs to a page which is - currently being loaded. - - :param timeout: Timeout in milliseconds. - """ - self._send_message("setSearchTimeout", {"ms": timeout}) - - @property - def current_window_handle(self): - """Get the current window's handle. - - Returns an opaque server-assigned identifier to this window - that uniquely identifies it within this Marionette instance. - This can be used to switch to this window at a later point. - - :returns: unique window handle - :rtype: string - """ - self.window = self._send_message("getWindowHandle", key="value") - return self.window - - @property - def current_chrome_window_handle(self): - """Get the current chrome window's handle. Corresponds to - a chrome window that may itself contain tabs identified by - window_handles. - - Returns an opaque server-assigned identifier to this window - that uniquely identifies it within this Marionette instance. - This can be used to switch to this window at a later point. - - :returns: unique window handle - :rtype: string - """ - self.chrome_window = self._send_message( - "getCurrentChromeWindowHandle", key="value") - return self.chrome_window - - def get_window_position(self): - """Get the current window's position. - - :returns: a dictionary with x and y - """ - return self._send_message( - "getWindowPosition", key="value" if self.protocol == 1 else None) - - def set_window_position(self, x, y): - """Set the position of the current window - - :param x: x coordinate for the top left of the window - :param y: y coordinate for the top left of the window - """ - self._send_message("setWindowPosition", {"x": x, "y": y}) - - @property - def title(self): - """Current title of the active window.""" - return self._send_message("getTitle", key="value") - - @property - def window_handles(self): - """Get list of windows in the current context. - - If called in the content context it will return a list of - references to all available browser windows. Called in the - chrome context, it will list all available windows, not just - browser windows (e.g. not just navigator.browser). - - Each window handle is assigned by the server, and the list of - strings returned does not have a guaranteed ordering. - - :returns: unordered list of unique window handles as strings - """ - return self._send_message( - "getWindowHandles", key="value" if self.protocol == 1 else None) - - @property - def chrome_window_handles(self): - """Get a list of currently open chrome windows. - - Each window handle is assigned by the server, and the list of - strings returned does not have a guaranteed ordering. - - :returns: unordered list of unique window handles as strings - """ - return self._send_message( - "getChromeWindowHandles", key="value" if self.protocol == 1 else None) - - @property - def page_source(self): - """A string representation of the DOM.""" - return self._send_message("getPageSource", key="value") - - def close(self): - """Close the current window, ending the session if it's the last - window currently open. - - On B2G this method is a noop and will return immediately. - """ - self._send_message("close") - - def close_chrome_window(self): - """Close the currently selected chrome window, ending the session - if it's the last window open. - - On B2G this method is a noop and will return immediately. - """ - self._send_message("closeChromeWindow") - - def set_context(self, context): - """Sets the context that Marionette commands are running in. - - :param context: Context, may be one of the class properties - `CONTEXT_CHROME` or `CONTEXT_CONTENT`. - - Usage example:: - - marionette.set_context(marionette.CONTEXT_CHROME) - """ - if context not in [self.CONTEXT_CHROME, self.CONTEXT_CONTENT]: - raise ValueError("Unknown context: %s" % context) - self._send_message("setContext", {"value": context}) - - @contextmanager - def using_context(self, context): - """Sets the context that Marionette commands are running in using - a `with` statement. The state of the context on the server is - saved before entering the block, and restored upon exiting it. - - :param context: Context, may be one of the class properties - `CONTEXT_CHROME` or `CONTEXT_CONTENT`. - - Usage example:: - - with marionette.using_context(marionette.CONTEXT_CHROME): - # chrome scope - ... do stuff ... - """ - scope = self._send_message("getContext", key="value") - self.set_context(context) - try: - yield - finally: - self.set_context(scope) - - def switch_to_alert(self): - """Returns an Alert object for interacting with a currently - displayed alert. - - :: - - alert = self.marionette.switch_to_alert() - text = alert.text - alert.accept() - """ - return Alert(self) - - def switch_to_window(self, window_id): - """Switch to the specified window; subsequent commands will be - directed at the new window. - - :param window_id: The id or name of the window to switch to. - """ - self._send_message("switchToWindow", {"name": window_id}) - self.window = window_id - - def get_active_frame(self): - """Returns an HTMLElement representing the frame Marionette is - currently acting on.""" - element = self._send_message("getActiveFrame", key="value") - if element: - return HTMLElement(self, element) - return None - - def switch_to_default_content(self): - """Switch the current context to page's default content.""" - return self.switch_to_frame() - - def switch_to_frame(self, frame=None, focus=True): - """Switch the current context to the specified frame. Subsequent - commands will operate in the context of the specified frame, - if applicable. - - :param frame: A reference to the frame to switch to. This can - be an ``HTMLElement``, an integer index, string name, or an - ID attribute. If you call ``switch_to_frame`` without an - argument, it will switch to the top-level frame. - - :param focus: A boolean value which determins whether to focus - the frame that we just switched to. - """ - body = {"focus": focus} - if isinstance(frame, HTMLElement): - body["element"] = frame.id - elif frame is not None: - body["id"] = frame - self._send_message("switchToFrame", body) - - def switch_to_shadow_root(self, host=None): - """Switch the current context to the specified host's Shadow DOM. - Subsequent commands will operate in the context of the specified Shadow - DOM, if applicable. - - :param host: A reference to the host element containing Shadow DOM. - This can be an ``HTMLElement``. If you call - ``switch_to_shadow_root`` without an argument, it will switch to the - parent Shadow DOM or the top-level frame. - """ - body = {} - if isinstance(host, HTMLElement): - body["id"] = host.id - return self._send_message("switchToShadowRoot", body) - - def get_url(self): - """Get a string representing the current URL. - - On Desktop this returns a string representation of the URL of - the current top level browsing context. This is equivalent to - document.location.href. - - When in the context of the chrome, this returns the canonical - URL of the current resource. - - :returns: string representation of URL - """ - return self._send_message("getCurrentUrl", key="value") - - def get_window_type(self): - """Gets the windowtype attribute of the window Marionette is - currently acting on. - - This command only makes sense in a chrome context. You might use this - method to distinguish a browser window from an editor window. - """ - return self._send_message("getWindowType", key="value") - - def navigate(self, url): - """Navigate to given `url`. - - Navigates the current top-level browsing context's content - frame to the given URL and waits for the document to load or - the session's page timeout duration to elapse before returning. - - The command will return with a failure if there is an error - loading the document or the URL is blocked. This can occur if - it fails to reach the host, the URL is malformed, the page is - restricted (about:* pages), or if there is a certificate issue - to name some examples. - - The document is considered successfully loaded when the - `DOMContentLoaded` event on the frame element associated with the - `window` triggers and `document.readState` is "complete". - - In chrome context it will change the current `window`'s location - to the supplied URL and wait until `document.readState` equals - "complete" or the page timeout duration has elapsed. - - :param url: The URL to navigate to. - """ - self._send_message("get", {"url": url}) - - def timeouts(self, timeout_type, ms): - """An interface for managing timeout behaviour of a Marionette - instance. - - Setting timeouts specifies the type and amount of time the - Marionette instance should wait during requests. - - There are three types of timeouts that can be set: implicit, - script and page load. - - * An implicit timeout specifies the amount of time a Marionette - instance should wait when searching for elements. Here, marionette - polls a page until an element is found or the timeout expires, - whichever occurs first. When searching for multiple elements, - the driver should poll the page until at least one element is - found or the timeout expires, at which point it should return - an empty list. - - * A script timeout specifies the amount of time the Marionette - instance should wait after calling executeAsyncScript for the - callback to have executed before returning a timeout response. - - * A page load timeout specifies the amount of time the Marionette - instance should wait for a page load operation to complete. If - this limit is exceeded, the Marionette instance will return a - "timeout" response status. - - :param timeout_type: A string value specifying the timeout - type. This must be one of three types: 'implicit', 'script' - or 'page load' - :param ms: A number value specifying the timeout length in - milliseconds (ms) - """ - if timeout_type not in [self.TIMEOUT_SEARCH, self.TIMEOUT_SCRIPT, self.TIMEOUT_PAGE]: - raise ValueError("Unknown timeout type: %s" % timeout_type) - body = {"type": timeout_type, "ms": ms} - self._send_message("timeouts", body) - - def go_back(self): - """Causes the browser to perform a back navigation.""" - self._send_message("goBack") - - def go_forward(self): - """Causes the browser to perform a forward navigation.""" - self._send_message("goForward") - - def refresh(self): - """Causes the browser to perform to refresh the current page.""" - self._send_message("refresh") - - def wrapArguments(self, args): - if isinstance(args, list): - wrapped = [] - for arg in args: - wrapped.append(self.wrapArguments(arg)) - elif isinstance(args, dict): - wrapped = {} - for arg in args: - wrapped[arg] = self.wrapArguments(args[arg]) - elif type(args) == HTMLElement: - wrapped = {W3C_WEBELEMENT_KEY: args.id, - WEBELEMENT_KEY: args.id} - elif (isinstance(args, bool) or isinstance(args, basestring) or - isinstance(args, int) or isinstance(args, float) or args is None): - wrapped = args - return wrapped - - def unwrapValue(self, value): - if isinstance(value, list): - unwrapped = [] - for item in value: - unwrapped.append(self.unwrapValue(item)) - elif isinstance(value, dict): - unwrapped = {} - for key in value: - if key == W3C_WEBELEMENT_KEY: - unwrapped = HTMLElement(self, value[key]) - break - elif key == WEBELEMENT_KEY: - unwrapped = HTMLElement(self, value[key]) - break - else: - unwrapped[key] = self.unwrapValue(value[key]) - else: - unwrapped = value - return unwrapped - - def execute_js_script(self, script, script_args=None, async=True, - new_sandbox=True, script_timeout=None, - inactivity_timeout=None, filename=None, - sandbox='default'): - if script_args is None: - script_args = [] - args = self.wrapArguments(script_args) - body = {"script": script, - "args": args, - "async": async, - "newSandbox": new_sandbox, - "scriptTimeout": script_timeout, - "inactivityTimeout": inactivity_timeout, - "filename": filename, - "line": None} - rv = self._send_message("executeJSScript", body, key="value") - return self.unwrapValue(rv) - - def execute_script(self, script, script_args=None, new_sandbox=True, - sandbox="default", script_timeout=None): - """Executes a synchronous JavaScript script, and returns the - result (or None if the script does return a value). - - The script is executed in the context set by the most recent - set_context() call, or to the CONTEXT_CONTENT context if set_context() - has not been called. - - :param script: A string containing the JavaScript to execute. - :param script_args: A list of arguments to pass to the script. - :param sandbox: A tag referring to the sandbox you wish to use; - if you specify a new tag, a new sandbox will be created. - If you use the special tag `system`, the sandbox will - be created using the system principal which has elevated - privileges. - :param new_sandbox: If False, preserve global variables from - the last execute_*script call. This is True by default, in which - case no globals are preserved. - - Simple usage example: - - :: - - result = marionette.execute_script("return 1;") - assert result == 1 - - You can use the `script_args` parameter to pass arguments to the - script: - - :: - - result = marionette.execute_script("return arguments[0] + arguments[1];", - script_args=[2, 3]) - assert result == 5 - some_element = marionette.find_element("id", "someElement") - sid = marionette.execute_script("return arguments[0].id;", script_args=[some_element]) - assert some_element.get_attribute("id") == sid - - Scripts wishing to access non-standard properties of the window - object must use window.wrappedJSObject: - - :: - - result = marionette.execute_script(''' - window.wrappedJSObject.test1 = "foo"; - window.wrappedJSObject.test2 = "bar"; - return window.wrappedJSObject.test1 + window.wrappedJSObject.test2; - ''') - assert result == "foobar" - - Global variables set by individual scripts do not persist between - script calls by default. If you wish to persist data between - script calls, you can set new_sandbox to False on your next call, - and add any new variables to a new 'global' object like this: - - :: - - marionette.execute_script("global.test1 = 'foo';") - result = self.marionette.execute_script("return global.test1;", new_sandbox=False) - assert result == "foo" - - """ - if script_args is None: - script_args = [] - args = self.wrapArguments(script_args) - stack = traceback.extract_stack() - frame = stack[-2:-1][0] # grab the second-to-last frame - body = {"script": script, - "args": args, - "newSandbox": new_sandbox, - "sandbox": sandbox, - "scriptTimeout": script_timeout, - "line": int(frame[1]), - "filename": os.path.basename(frame[0])} - rv = self._send_message("executeScript", body, key="value") - return self.unwrapValue(rv) - - def execute_async_script(self, script, script_args=None, new_sandbox=True, - sandbox="default", script_timeout=None, - debug_script=False): - """Executes an asynchronous JavaScript script, and returns the - result (or None if the script does return a value). - - The script is executed in the context set by the most recent - set_context() call, or to the CONTEXT_CONTENT context if - set_context() has not been called. - - :param script: A string containing the JavaScript to execute. - :param script_args: A list of arguments to pass to the script. - :param sandbox: A tag referring to the sandbox you wish to use; if - you specify a new tag, a new sandbox will be created. If you - use the special tag `system`, the sandbox will be created - using the system principal which has elevated privileges. - :param new_sandbox: If False, preserve global variables from - the last execute_*script call. This is True by default, - in which case no globals are preserved. - :param debug_script: Capture javascript exceptions when in - `CONTEXT_CHROME` context. - - Usage example: - - :: - - marionette.set_script_timeout(10000) # set timeout period of 10 seconds - result = self.marionette.execute_async_script(''' - // this script waits 5 seconds, and then returns the number 1 - setTimeout(function() { - marionetteScriptFinished(1); - }, 5000); - ''') - assert result == 1 - """ - if script_args is None: - script_args = [] - args = self.wrapArguments(script_args) - stack = traceback.extract_stack() - frame = stack[-2:-1][0] # grab the second-to-last frame - body = {"script": script, - "args": args, - "newSandbox": new_sandbox, - "sandbox": sandbox, - "scriptTimeout": script_timeout, - "line": int(frame[1]), - "filename": os.path.basename(frame[0]), - "debug_script": debug_script} - rv = self._send_message("executeAsyncScript", body, key="value") - return self.unwrapValue(rv) - - def find_element(self, method, target, id=None): - """Returns an HTMLElement instances that matches the specified - method and target in the current context. - - An HTMLElement instance may be used to call other methods on the - element, such as click(). If no element is immediately found, the - attempt to locate an element will be repeated for up to the amount of - time set by set_search_timeout(). If multiple elements match the given - criteria, only the first is returned. If no element matches, a - NoSuchElementException will be raised. - - :param method: The method to use to locate the element; one of: - "id", "name", "class name", "tag name", "css selector", - "link text", "partial link text", "xpath", "anon" and "anon - attribute". Note that the "name", "link text" and "partial - link test" methods are not supported in the chrome DOM. - :param target: The target of the search. For example, if method = - "tag", target might equal "div". If method = "id", target would - be an element id. - :param id: If specified, search for elements only inside the element - with the specified id. - """ - body = {"value": target, "using": method} - if id: - body["element"] = id - return self._send_message("findElement", body, key="value") - - def find_elements(self, method, target, id=None): - """Returns a list of all HTMLElement instances that match the - specified method and target in the current context. - - An HTMLElement instance may be used to call other methods on the - element, such as click(). If no element is immediately found, - the attempt to locate an element will be repeated for up to the - amount of time set by set_search_timeout(). - - :param method: The method to use to locate the elements; one - of: "id", "name", "class name", "tag name", "css selector", - "link text", "partial link text", "xpath", "anon" and "anon - attribute". Note that the "name", "link text" and "partial link - test" methods are not supported in the chrome DOM. - :param target: The target of the search. For example, if method = - "tag", target might equal "div". If method = "id", target would be - an element id. - :param id: If specified, search for elements only inside the element - with the specified id. - """ - body = {"value": target, "using": method} - if id: - body["element"] = id - return self._send_message( - "findElements", body, key="value" if self.protocol == 1 else None) - - - def get_active_element(self): - el = self._send_message("getActiveElement", key="value") - return HTMLElement(self, el) - - def log(self, msg, level=None): - """Stores a timestamped log message in the Marionette server - for later retrieval. - - :param msg: String with message to log. - :param level: String with log level (e.g. "INFO" or "DEBUG"). If None, - defaults to "INFO". - """ - body = {"value": msg, "level": level} - self._send_message("log", body) - - def get_logs(self): - """Returns the list of logged messages. - - Each log message is an array with three string elements: the level, - the message, and a date. - - Usage example:: - - marionette.log("I AM INFO") - marionette.log("I AM ERROR", "ERROR") - logs = marionette.get_logs() - assert logs[0][1] == "I AM INFO" - assert logs[1][1] == "I AM ERROR" - """ - return self._send_message("getLogs", - key="value" if self.protocol == 1 else None) - - def import_script(self, js_file): - """Imports a script into the scope of the execute_script and - execute_async_script calls. - - This is particularly useful if you wish to import your own - libraries. - - :param js_file: Filename of JavaScript file to import. - - For example, Say you have a script, importfunc.js, that contains: - - :: - - let testFunc = function() { return "i'm a test function!";}; - - Assuming this file is in the same directory as the test, you - could do something like: - - :: - - js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importfunc.js")) - marionette.import_script(js) - assert "i'm a test function!" == self.marionette.execute_script("return testFunc();") - """ - js = "" - with open(js_file, "r") as f: - js = f.read() - body = {"script": js} - self._send_message("importScript", body) - - def clear_imported_scripts(self): - """Clears all imported scripts in this context, ie: calling - clear_imported_scripts in chrome context will clear only scripts - you imported in chrome, and will leave the scripts you imported - in content context. - """ - self._send_message("clearImportedScripts") - - def add_cookie(self, cookie): - """Adds a cookie to your current session. - - :param cookie: A dictionary object, with required keys - "name" - and "value"; optional keys - "path", "domain", "secure", - "expiry". - - Usage example: - - :: - - driver.add_cookie({"name": "foo", "value": "bar"}) - driver.add_cookie({"name": "foo", "value": "bar", "path": "/"}) - driver.add_cookie({"name": "foo", "value": "bar", "path": "/", - "secure": True}) - """ - body = {"cookie": cookie} - self._send_message("addCookie", body) - - def delete_all_cookies(self): - """Delete all cookies in the scope of the current session. - - Usage example: - - :: - - driver.delete_all_cookies() - """ - self._send_message("deleteAllCookies") - - def delete_cookie(self, name): - """Delete a cookie by its name. - - :param name: Name of cookie to delete. - - Usage example: - - :: - - driver.delete_cookie("foo") - """ - self._send_message("deleteCookie", {"name": name}) - - def get_cookie(self, name): - """Get a single cookie by name. Returns the cookie if found, - None if not. - - :param name: Name of cookie to get. - """ - cookies = self.get_cookies() - for cookie in cookies: - if cookie["name"] == name: - return cookie - return None - - def get_cookies(self): - """Get all the cookies for the current domain. - - This is the equivalent of calling `document.cookie` and - parsing the result. - - :returns: A list of cookies for the current domain. - """ - return self._send_message("getCookies", key="value" if self.protocol == 1 else None) - - def screenshot(self, element=None, highlights=None, format="base64", - full=True): - """Takes a screenshot of a web element or the current frame. - - The screen capture is returned as a lossless PNG image encoded - as a base 64 string by default. If the `element` argument is defined the - capture area will be limited to the bounding box of that - element. Otherwise, the capture area will be the bounding box - of the current frame. - - :param element: The element to take a screenshot of. If None, will - take a screenshot of the current frame. - - :param highlights: A list of HTMLElement objects to draw a red - box around in the returned screenshot. - - :param format: if "base64" (the default), returns the screenshot - as a base64-string. If "binary", the data is decoded and - returned as raw binary. - - :param full: If True (the default), the capture area will be the - complete frame. Else only the viewport is captured. Only applies - when `element` is None. - """ - - if element: - element = element.id - lights = None - if highlights: - lights = [highlight.id for highlight in highlights] - - body = {"id": element, - "highlights": lights, - "full": full} - data = self._send_message("takeScreenshot", body, key="value") - - if format == "base64": - return data - elif format == "binary": - return base64.b64decode(data.encode("ascii")) - else: - raise ValueError("format parameter must be either 'base64'" - " or 'binary', not {0}".format(repr(format))) - - @property - def orientation(self): - """Get the current browser orientation. - - Will return one of the valid primary orientation values - portrait-primary, landscape-primary, portrait-secondary, or - landscape-secondary. - """ - return self._send_message("getScreenOrientation", key="value") - - def set_orientation(self, orientation): - """Set the current browser orientation. - - The supplied orientation should be given as one of the valid - orientation values. If the orientation is unknown, an error - will be raised. - - Valid orientations are "portrait" and "landscape", which fall - back to "portrait-primary" and "landscape-primary" - respectively, and "portrait-secondary" as well as - "landscape-secondary". - - :param orientation: The orientation to lock the screen in. - """ - body = {"orientation": orientation} - self._send_message("setScreenOrientation", body) - if self.emulator: - self.emulator.screen.orientation = orientation.lower() - - @property - def window_size(self): - """Get the current browser window size. - - Will return the current browser window size in pixels. Refers to - window outerWidth and outerHeight values, which include scroll bars, - title bars, etc. - - :returns: dictionary representation of current window width and height - """ - return self._send_message("getWindowSize", - key="value" if self.protocol == 1 else None) - - def set_window_size(self, width, height): - """Resize the browser window currently in focus. - - The supplied width and height values refer to the window outerWidth - and outerHeight values, which include scroll bars, title bars, etc. - - An error will be returned if the requested window size would result - in the window being in the maximised state. - - :param width: The width to resize the window to. - :param height: The height to resize the window to. - - """ - body = {"width": width, "height": height} - self._send_message("setWindowSize", body) - - def maximize_window(self): - """ Resize the browser window currently receiving commands. The action - should be equivalent to the user pressing the the maximize button - """ - return self._send_message("maximizeWindow") diff --git a/testing/marionette/driver/requirements.txt b/testing/marionette/driver/requirements.txt deleted file mode 100644 index a77e91f0e8..0000000000 --- a/testing/marionette/driver/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -mozrunner >= 6.9 diff --git a/testing/marionette/elements.js b/testing/marionette/elements.js index 3c0b4ac192..339ebe645d 100644 --- a/testing/marionette/elements.js +++ b/testing/marionette/elements.js @@ -233,7 +233,8 @@ ElementManager.prototype = { delete this.seenItems[i]; } } - let id = uuidGen.generateUUID().toString(); + let uuid = uuidGen.generateUUID().toString(); + let id = uuid.substring(1, uuid.length - 1); this.seenItems[id] = Components.utils.getWeakReference(element); return id; }, @@ -670,7 +671,7 @@ ElementManager.prototype = { break; case LINK_TEXT: case PARTIAL_LINK_TEXT: - let allLinks = rootNode.getElementsByTagName('A'); + let allLinks = startNode.getElementsByTagName('A'); for (let i = 0; i < allLinks.length; i++) { let text = allLinks[i].text; if (PARTIAL_LINK_TEXT == using) { diff --git a/testing/marionette/harness/MANIFEST.in b/testing/marionette/harness/MANIFEST.in new file mode 100644 index 0000000000..7dc49db874 --- /dev/null +++ b/testing/marionette/harness/MANIFEST.in @@ -0,0 +1,6 @@ +recursive-include marionette/touch *.js +recursive-include marionette/www * +recursive-include marionette/chrome * +recursive-include marionette/runner/mixins/resources * +exclude MANIFEST.in +include requirements.txt diff --git a/testing/marionette/client/docs/Makefile b/testing/marionette/harness/docs/Makefile similarity index 100% rename from testing/marionette/client/docs/Makefile rename to testing/marionette/harness/docs/Makefile diff --git a/testing/marionette/client/docs/advanced/actions.rst b/testing/marionette/harness/docs/advanced/actions.rst similarity index 100% rename from testing/marionette/client/docs/advanced/actions.rst rename to testing/marionette/harness/docs/advanced/actions.rst diff --git a/testing/marionette/client/docs/advanced/debug.rst b/testing/marionette/harness/docs/advanced/debug.rst similarity index 100% rename from testing/marionette/client/docs/advanced/debug.rst rename to testing/marionette/harness/docs/advanced/debug.rst diff --git a/testing/marionette/client/docs/advanced/findelement.rst b/testing/marionette/harness/docs/advanced/findelement.rst similarity index 100% rename from testing/marionette/client/docs/advanced/findelement.rst rename to testing/marionette/harness/docs/advanced/findelement.rst diff --git a/testing/marionette/client/docs/advanced/landing.rst b/testing/marionette/harness/docs/advanced/landing.rst similarity index 100% rename from testing/marionette/client/docs/advanced/landing.rst rename to testing/marionette/harness/docs/advanced/landing.rst diff --git a/testing/marionette/client/docs/advanced/stale.rst b/testing/marionette/harness/docs/advanced/stale.rst similarity index 100% rename from testing/marionette/client/docs/advanced/stale.rst rename to testing/marionette/harness/docs/advanced/stale.rst diff --git a/testing/marionette/client/docs/basics.rst b/testing/marionette/harness/docs/basics.rst similarity index 100% rename from testing/marionette/client/docs/basics.rst rename to testing/marionette/harness/docs/basics.rst diff --git a/testing/marionette/client/docs/conf.py b/testing/marionette/harness/docs/conf.py similarity index 100% rename from testing/marionette/client/docs/conf.py rename to testing/marionette/harness/docs/conf.py diff --git a/testing/marionette/client/docs/index.rst b/testing/marionette/harness/docs/index.rst similarity index 100% rename from testing/marionette/client/docs/index.rst rename to testing/marionette/harness/docs/index.rst diff --git a/testing/marionette/client/docs/interactive.rst b/testing/marionette/harness/docs/interactive.rst similarity index 100% rename from testing/marionette/client/docs/interactive.rst rename to testing/marionette/harness/docs/interactive.rst diff --git a/testing/marionette/client/docs/make.bat b/testing/marionette/harness/docs/make.bat similarity index 100% rename from testing/marionette/client/docs/make.bat rename to testing/marionette/harness/docs/make.bat diff --git a/testing/marionette/client/docs/reference.rst b/testing/marionette/harness/docs/reference.rst similarity index 100% rename from testing/marionette/client/docs/reference.rst rename to testing/marionette/harness/docs/reference.rst diff --git a/testing/marionette/client/marionette/__init__.py b/testing/marionette/harness/marionette/__init__.py similarity index 100% rename from testing/marionette/client/marionette/__init__.py rename to testing/marionette/harness/marionette/__init__.py diff --git a/testing/marionette/client/marionette/atoms/b2g_update_test.js b/testing/marionette/harness/marionette/atoms/b2g_update_test.js similarity index 100% rename from testing/marionette/client/marionette/atoms/b2g_update_test.js rename to testing/marionette/harness/marionette/atoms/b2g_update_test.js diff --git a/testing/marionette/client/marionette/b2g_update_test.py b/testing/marionette/harness/marionette/b2g_update_test.py similarity index 100% rename from testing/marionette/client/marionette/b2g_update_test.py rename to testing/marionette/harness/marionette/b2g_update_test.py diff --git a/testing/marionette/client/marionette/chrome/test.xul b/testing/marionette/harness/marionette/chrome/test.xul similarity index 100% rename from testing/marionette/client/marionette/chrome/test.xul rename to testing/marionette/harness/marionette/chrome/test.xul diff --git a/testing/marionette/client/marionette/chrome/test2.xul b/testing/marionette/harness/marionette/chrome/test2.xul similarity index 100% rename from testing/marionette/client/marionette/chrome/test2.xul rename to testing/marionette/harness/marionette/chrome/test2.xul diff --git a/testing/marionette/client/marionette/chrome/test_anonymous_content.xul b/testing/marionette/harness/marionette/chrome/test_anonymous_content.xul similarity index 100% rename from testing/marionette/client/marionette/chrome/test_anonymous_content.xul rename to testing/marionette/harness/marionette/chrome/test_anonymous_content.xul diff --git a/testing/marionette/client/marionette/chrome/test_nested_iframe.xul b/testing/marionette/harness/marionette/chrome/test_nested_iframe.xul similarity index 100% rename from testing/marionette/client/marionette/chrome/test_nested_iframe.xul rename to testing/marionette/harness/marionette/chrome/test_nested_iframe.xul diff --git a/testing/marionette/client/marionette/marionette_test.py b/testing/marionette/harness/marionette/marionette_test.py similarity index 100% rename from testing/marionette/client/marionette/marionette_test.py rename to testing/marionette/harness/marionette/marionette_test.py diff --git a/testing/marionette/client/marionette/runner/__init__.py b/testing/marionette/harness/marionette/runner/__init__.py similarity index 100% rename from testing/marionette/client/marionette/runner/__init__.py rename to testing/marionette/harness/marionette/runner/__init__.py diff --git a/testing/marionette/client/marionette/runner/base.py b/testing/marionette/harness/marionette/runner/base.py similarity index 100% rename from testing/marionette/client/marionette/runner/base.py rename to testing/marionette/harness/marionette/runner/base.py diff --git a/testing/marionette/client/marionette/runner/httpd.py b/testing/marionette/harness/marionette/runner/httpd.py similarity index 100% rename from testing/marionette/client/marionette/runner/httpd.py rename to testing/marionette/harness/marionette/runner/httpd.py diff --git a/testing/marionette/client/marionette/runner/mixins/__init__.py b/testing/marionette/harness/marionette/runner/mixins/__init__.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/__init__.py rename to testing/marionette/harness/marionette/runner/mixins/__init__.py diff --git a/testing/marionette/client/marionette/runner/mixins/b2g.py b/testing/marionette/harness/marionette/runner/mixins/b2g.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/b2g.py rename to testing/marionette/harness/marionette/runner/mixins/b2g.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/History.md b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/History.md similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/History.md rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/History.md diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/__init__.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/client.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/server.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/browsermobproxy/webdriver_event_listener.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/Makefile b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/Makefile similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/Makefile rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/Makefile diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/.buildinfo diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/browsermobproxy.html diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_modules/index.html diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/client.txt diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/index.txt diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_sources/server.txt diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/basic.css diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/default.css diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/doctools.js diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/jquery.js diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/pygments.css diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/searchtools.js diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/sidebar.js diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/underscore.js diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/_static/websupport.js diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/client.html diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/genindex.html diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/index.html diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/objects.inv diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/py-modindex.html diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/search.html diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/searchindex.js diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/_build/html/server.html diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/client.rst b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/client.rst similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/client.rst rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/client.rst diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/conf.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/conf.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/conf.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/conf.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/index.rst b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/index.rst similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/index.rst rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/index.rst diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/make.bat b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/make.bat similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/make.bat rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/make.bat diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/server.rst b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/server.rst similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/docs/server.rst rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/docs/server.rst diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/readme.md b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/readme.md similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/readme.md rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/readme.md diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/setup.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/setup.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/setup.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/setup.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/test/test_client.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/test/test_client.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/test/test_client.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/test/test_client.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/test/test_remote.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/test/test_remote.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/test/test_remote.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/test/test_remote.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/test/test_webdriver.py b/testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/test/test_webdriver.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py/test/test_webdriver.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob-proxy-py/test/test_webdriver.py diff --git a/testing/marionette/client/marionette/runner/mixins/browsermob.py b/testing/marionette/harness/marionette/runner/mixins/browsermob.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/browsermob.py rename to testing/marionette/harness/marionette/runner/mixins/browsermob.py diff --git a/testing/marionette/client/marionette/runner/mixins/endurance.py b/testing/marionette/harness/marionette/runner/mixins/endurance.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/endurance.py rename to testing/marionette/harness/marionette/runner/mixins/endurance.py diff --git a/testing/marionette/client/marionette/runner/mixins/reporting.py b/testing/marionette/harness/marionette/runner/mixins/reporting.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/reporting.py rename to testing/marionette/harness/marionette/runner/mixins/reporting.py diff --git a/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/jquery.js b/testing/marionette/harness/marionette/runner/mixins/resources/htmlreport/jquery.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/resources/htmlreport/jquery.js rename to testing/marionette/harness/marionette/runner/mixins/resources/htmlreport/jquery.js diff --git a/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/main.js b/testing/marionette/harness/marionette/runner/mixins/resources/htmlreport/main.js similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/resources/htmlreport/main.js rename to testing/marionette/harness/marionette/runner/mixins/resources/htmlreport/main.js diff --git a/testing/marionette/client/marionette/runner/mixins/resources/htmlreport/style.css b/testing/marionette/harness/marionette/runner/mixins/resources/htmlreport/style.css similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/resources/htmlreport/style.css rename to testing/marionette/harness/marionette/runner/mixins/resources/htmlreport/style.css diff --git a/testing/marionette/client/marionette/runner/mixins/xmlgen.py b/testing/marionette/harness/marionette/runner/mixins/xmlgen.py similarity index 100% rename from testing/marionette/client/marionette/runner/mixins/xmlgen.py rename to testing/marionette/harness/marionette/runner/mixins/xmlgen.py diff --git a/testing/marionette/client/marionette/runtests.py b/testing/marionette/harness/marionette/runtests.py similarity index 100% rename from testing/marionette/client/marionette/runtests.py rename to testing/marionette/harness/marionette/runtests.py diff --git a/testing/marionette/client/marionette/tests/print-manifest-dirs.py b/testing/marionette/harness/marionette/tests/print-manifest-dirs.py similarity index 100% rename from testing/marionette/client/marionette/tests/print-manifest-dirs.py rename to testing/marionette/harness/marionette/tests/print-manifest-dirs.py diff --git a/testing/marionette/client/marionette/tests/unit-tests.ini b/testing/marionette/harness/marionette/tests/unit-tests.ini similarity index 100% rename from testing/marionette/client/marionette/tests/unit-tests.ini rename to testing/marionette/harness/marionette/tests/unit-tests.ini diff --git a/testing/marionette/client/marionette/tests/unit/importanotherscript.js b/testing/marionette/harness/marionette/tests/unit/importanotherscript.js similarity index 100% rename from testing/marionette/client/marionette/tests/unit/importanotherscript.js rename to testing/marionette/harness/marionette/tests/unit/importanotherscript.js diff --git a/testing/marionette/client/marionette/tests/unit/importscript.js b/testing/marionette/harness/marionette/tests/unit/importscript.js similarity index 100% rename from testing/marionette/client/marionette/tests/unit/importscript.js rename to testing/marionette/harness/marionette/tests/unit/importscript.js diff --git a/testing/marionette/client/marionette/tests/unit/mn-restartless-unsigned.xpi b/testing/marionette/harness/marionette/tests/unit/mn-restartless-unsigned.xpi similarity index 100% rename from testing/marionette/client/marionette/tests/unit/mn-restartless-unsigned.xpi rename to testing/marionette/harness/marionette/tests/unit/mn-restartless-unsigned.xpi diff --git a/testing/marionette/client/marionette/tests/unit/single_finger_functions.py b/testing/marionette/harness/marionette/tests/unit/single_finger_functions.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/single_finger_functions.py rename to testing/marionette/harness/marionette/tests/unit/single_finger_functions.py diff --git a/testing/marionette/client/marionette/tests/unit/test_about_pages.py b/testing/marionette/harness/marionette/tests/unit/test_about_pages.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_about_pages.py rename to testing/marionette/harness/marionette/tests/unit/test_about_pages.py diff --git a/testing/marionette/client/marionette/tests/unit/test_accessibility.py b/testing/marionette/harness/marionette/tests/unit/test_accessibility.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_accessibility.py rename to testing/marionette/harness/marionette/tests/unit/test_accessibility.py diff --git a/testing/marionette/client/marionette/tests/unit/test_addons.py b/testing/marionette/harness/marionette/tests/unit/test_addons.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_addons.py rename to testing/marionette/harness/marionette/tests/unit/test_addons.py diff --git a/testing/marionette/client/marionette/tests/unit/test_anonymous_content.py b/testing/marionette/harness/marionette/tests/unit/test_anonymous_content.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_anonymous_content.py rename to testing/marionette/harness/marionette/tests/unit/test_anonymous_content.py diff --git a/testing/marionette/client/marionette/tests/unit/test_browsermobproxy.py b/testing/marionette/harness/marionette/tests/unit/test_browsermobproxy.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_browsermobproxy.py rename to testing/marionette/harness/marionette/tests/unit/test_browsermobproxy.py diff --git a/testing/marionette/client/marionette/tests/unit/test_capabilities.py b/testing/marionette/harness/marionette/tests/unit/test_capabilities.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_capabilities.py rename to testing/marionette/harness/marionette/tests/unit/test_capabilities.py diff --git a/testing/marionette/harness/marionette/tests/unit/test_chrome.py b/testing/marionette/harness/marionette/tests/unit/test_chrome.py new file mode 100644 index 0000000000..72e59218e4 --- /dev/null +++ b/testing/marionette/harness/marionette/tests/unit/test_chrome.py @@ -0,0 +1,35 @@ +#Copyright 2007-2009 WebDriver committers +#Copyright 2007-2009 Google Inc. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from marionette_driver import By +from marionette_driver.errors import NoSuchElementException +from marionette import MarionetteTestCase + + +class ChromeTests(MarionetteTestCase): + + def test_hang_until_timeout(self): + with self.marionette.using_context('chrome'): + start_handle = self.marionette.current_chrome_window_handle + menu = self.marionette.find_element(By.ID, 'aboutName') + menu.click() + handles = self.marionette.chrome_window_handles + handles.remove(start_handle) + self.marionette.switch_to_window(handles[0]) + self.assertRaises(NoSuchElementException, self.marionette.find_element, By.ID, 'dek') + + # Clean up the window + self.marionette.close() + self.marionette.switch_to_window(start_handle) diff --git a/testing/marionette/client/marionette/tests/unit/test_chrome_async_finish.js b/testing/marionette/harness/marionette/tests/unit/test_chrome_async_finish.js similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_chrome_async_finish.js rename to testing/marionette/harness/marionette/tests/unit/test_chrome_async_finish.js diff --git a/testing/marionette/client/marionette/tests/unit/test_chrome_element_css.py b/testing/marionette/harness/marionette/tests/unit/test_chrome_element_css.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_chrome_element_css.py rename to testing/marionette/harness/marionette/tests/unit/test_chrome_element_css.py diff --git a/testing/marionette/client/marionette/tests/unit/test_clearing.py b/testing/marionette/harness/marionette/tests/unit/test_clearing.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_clearing.py rename to testing/marionette/harness/marionette/tests/unit/test_clearing.py diff --git a/testing/marionette/client/marionette/tests/unit/test_click.py b/testing/marionette/harness/marionette/tests/unit/test_click.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_click.py rename to testing/marionette/harness/marionette/tests/unit/test_click.py diff --git a/testing/marionette/client/marionette/tests/unit/test_click_chrome.py b/testing/marionette/harness/marionette/tests/unit/test_click_chrome.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_click_chrome.py rename to testing/marionette/harness/marionette/tests/unit/test_click_chrome.py diff --git a/testing/marionette/client/marionette/tests/unit/test_click_scrolling.py b/testing/marionette/harness/marionette/tests/unit/test_click_scrolling.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_click_scrolling.py rename to testing/marionette/harness/marionette/tests/unit/test_click_scrolling.py diff --git a/testing/marionette/client/marionette/tests/unit/test_cookies.py b/testing/marionette/harness/marionette/tests/unit/test_cookies.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_cookies.py rename to testing/marionette/harness/marionette/tests/unit/test_cookies.py diff --git a/testing/marionette/client/marionette/tests/unit/test_data_driven.py b/testing/marionette/harness/marionette/tests/unit/test_data_driven.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_data_driven.py rename to testing/marionette/harness/marionette/tests/unit/test_data_driven.py diff --git a/testing/marionette/client/marionette/tests/unit/test_date_time_value.py b/testing/marionette/harness/marionette/tests/unit/test_date_time_value.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_date_time_value.py rename to testing/marionette/harness/marionette/tests/unit/test_date_time_value.py diff --git a/testing/marionette/client/marionette/tests/unit/test_elementState.py b/testing/marionette/harness/marionette/tests/unit/test_elementState.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_elementState.py rename to testing/marionette/harness/marionette/tests/unit/test_elementState.py diff --git a/testing/marionette/client/marionette/tests/unit/test_elementState_chrome.py b/testing/marionette/harness/marionette/tests/unit/test_elementState_chrome.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_elementState_chrome.py rename to testing/marionette/harness/marionette/tests/unit/test_elementState_chrome.py diff --git a/testing/marionette/client/marionette/tests/unit/test_element_touch.py b/testing/marionette/harness/marionette/tests/unit/test_element_touch.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_element_touch.py rename to testing/marionette/harness/marionette/tests/unit/test_element_touch.py diff --git a/testing/marionette/client/marionette/tests/unit/test_elementsize.py b/testing/marionette/harness/marionette/tests/unit/test_elementsize.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_elementsize.py rename to testing/marionette/harness/marionette/tests/unit/test_elementsize.py diff --git a/testing/marionette/client/marionette/tests/unit/test_emulator.py b/testing/marionette/harness/marionette/tests/unit/test_emulator.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_emulator.py rename to testing/marionette/harness/marionette/tests/unit/test_emulator.py diff --git a/testing/marionette/client/marionette/tests/unit/test_errors.py b/testing/marionette/harness/marionette/tests/unit/test_errors.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_errors.py rename to testing/marionette/harness/marionette/tests/unit/test_errors.py diff --git a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py b/testing/marionette/harness/marionette/tests/unit/test_execute_async_script.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_execute_async_script.py rename to testing/marionette/harness/marionette/tests/unit/test_execute_async_script.py diff --git a/testing/marionette/client/marionette/tests/unit/test_execute_isolate.py b/testing/marionette/harness/marionette/tests/unit/test_execute_isolate.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_execute_isolate.py rename to testing/marionette/harness/marionette/tests/unit/test_execute_isolate.py diff --git a/testing/marionette/client/marionette/tests/unit/test_execute_sandboxes.py b/testing/marionette/harness/marionette/tests/unit/test_execute_sandboxes.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_execute_sandboxes.py rename to testing/marionette/harness/marionette/tests/unit/test_execute_sandboxes.py diff --git a/testing/marionette/client/marionette/tests/unit/test_execute_script.py b/testing/marionette/harness/marionette/tests/unit/test_execute_script.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_execute_script.py rename to testing/marionette/harness/marionette/tests/unit/test_execute_script.py diff --git a/testing/marionette/client/marionette/tests/unit/test_expected.py b/testing/marionette/harness/marionette/tests/unit/test_expected.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_expected.py rename to testing/marionette/harness/marionette/tests/unit/test_expected.py diff --git a/testing/marionette/client/marionette/tests/unit/test_expectedfail.py b/testing/marionette/harness/marionette/tests/unit/test_expectedfail.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_expectedfail.py rename to testing/marionette/harness/marionette/tests/unit/test_expectedfail.py diff --git a/testing/marionette/client/marionette/tests/unit/test_file_upload.py b/testing/marionette/harness/marionette/tests/unit/test_file_upload.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_file_upload.py rename to testing/marionette/harness/marionette/tests/unit/test_file_upload.py diff --git a/testing/marionette/client/marionette/tests/unit/test_findelement.py b/testing/marionette/harness/marionette/tests/unit/test_findelement.py similarity index 89% rename from testing/marionette/client/marionette/tests/unit/test_findelement.py rename to testing/marionette/harness/marionette/tests/unit/test_findelement.py index 1057e2185f..3f2dff3eaf 100644 --- a/testing/marionette/client/marionette/tests/unit/test_findelement.py +++ b/testing/marionette/harness/marionette/tests/unit/test_findelement.py @@ -159,3 +159,21 @@ class TestElements(MarionetteTestCase): test_html = self.marionette.absolute_url("test.html") self.marionette.navigate(test_html) self.assertRaises(InvalidSelectorException, self.marionette.find_element, "Brie Search Type", "doesn't matter") + + def test_element_id_is_valid_uuid(self): + import re + test_html = self.marionette.absolute_url("test.html") + self.marionette.navigate(test_html) + el = self.marionette.find_element(By.TAG_NAME, "body") + uuid_regex = re.compile('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$') + self.assertIsNotNone(re.search(uuid_regex, el.id), + 'UUID for the WebElement is not valid. ID is {}'\ + .format(el.id)) + def test_should_find_elements_by_link_text(self): + test_html = self.marionette.absolute_url("nestedElements.html") + self.marionette.navigate(test_html) + element = self.marionette.find_element(By.NAME, "div1") + children = element.find_elements(By.LINK_TEXT, "hello world") + self.assertEqual(len(children), 2) + self.assertEqual("link1", children[0].get_attribute("name")) + self.assertEqual("link2", children[1].get_attribute("name")) diff --git a/testing/marionette/client/marionette/tests/unit/test_findelement_chrome.py b/testing/marionette/harness/marionette/tests/unit/test_findelement_chrome.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_findelement_chrome.py rename to testing/marionette/harness/marionette/tests/unit/test_findelement_chrome.py diff --git a/testing/marionette/client/marionette/tests/unit/test_gesture.py b/testing/marionette/harness/marionette/tests/unit/test_gesture.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_gesture.py rename to testing/marionette/harness/marionette/tests/unit/test_gesture.py diff --git a/testing/marionette/client/marionette/tests/unit/test_getactiveframe_oop.py b/testing/marionette/harness/marionette/tests/unit/test_getactiveframe_oop.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_getactiveframe_oop.py rename to testing/marionette/harness/marionette/tests/unit/test_getactiveframe_oop.py diff --git a/testing/marionette/client/marionette/tests/unit/test_getattr.py b/testing/marionette/harness/marionette/tests/unit/test_getattr.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_getattr.py rename to testing/marionette/harness/marionette/tests/unit/test_getattr.py diff --git a/testing/marionette/client/marionette/tests/unit/test_getattr_chrome.py b/testing/marionette/harness/marionette/tests/unit/test_getattr_chrome.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_getattr_chrome.py rename to testing/marionette/harness/marionette/tests/unit/test_getattr_chrome.py diff --git a/testing/marionette/client/marionette/tests/unit/test_implicit_waits.py b/testing/marionette/harness/marionette/tests/unit/test_implicit_waits.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_implicit_waits.py rename to testing/marionette/harness/marionette/tests/unit/test_implicit_waits.py diff --git a/testing/marionette/client/marionette/tests/unit/test_import_script.py b/testing/marionette/harness/marionette/tests/unit/test_import_script.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_import_script.py rename to testing/marionette/harness/marionette/tests/unit/test_import_script.py diff --git a/testing/marionette/client/marionette/tests/unit/test_import_script_reuse_window.py b/testing/marionette/harness/marionette/tests/unit/test_import_script_reuse_window.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_import_script_reuse_window.py rename to testing/marionette/harness/marionette/tests/unit/test_import_script_reuse_window.py diff --git a/testing/marionette/client/marionette/tests/unit/test_key_actions.py b/testing/marionette/harness/marionette/tests/unit/test_key_actions.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_key_actions.py rename to testing/marionette/harness/marionette/tests/unit/test_key_actions.py diff --git a/testing/marionette/client/marionette/tests/unit/test_log.py b/testing/marionette/harness/marionette/tests/unit/test_log.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_log.py rename to testing/marionette/harness/marionette/tests/unit/test_log.py diff --git a/testing/marionette/client/marionette/tests/unit/test_marionette.py b/testing/marionette/harness/marionette/tests/unit/test_marionette.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_marionette.py rename to testing/marionette/harness/marionette/tests/unit/test_marionette.py diff --git a/testing/marionette/client/marionette/tests/unit/test_modal_dialogs.py b/testing/marionette/harness/marionette/tests/unit/test_modal_dialogs.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_modal_dialogs.py rename to testing/marionette/harness/marionette/tests/unit/test_modal_dialogs.py diff --git a/testing/marionette/client/marionette/tests/unit/test_mouse_action.py b/testing/marionette/harness/marionette/tests/unit/test_mouse_action.py similarity index 90% rename from testing/marionette/client/marionette/tests/unit/test_mouse_action.py rename to testing/marionette/harness/marionette/tests/unit/test_mouse_action.py index 5d7d807123..a0b6c9c2d7 100644 --- a/testing/marionette/client/marionette/tests/unit/test_mouse_action.py +++ b/testing/marionette/harness/marionette/tests/unit/test_mouse_action.py @@ -32,18 +32,14 @@ class TestMouseAction(MarionetteTestCase): self.action.click(el).perform() def test_double_click_action(self): - test_html = self.marionette.absolute_url("javascriptPage.html") + test_html = self.marionette.absolute_url("double_click.html") self.marionette.navigate(test_html) - el = self.marionette.find_element(By.ID, 'displayed') - # The first click just brings the element into view so text selection - # works as expected. (A different test page could be used to isolate - # this element and make sure it's always in view) - el.click() + el = self.marionette.find_element(By.ID, 'one-word-div') self.action.double_click(el).perform() el.send_keys(self.mod_key + 'c') - rel = self.marionette.find_element("id", "keyReporter") + rel = self.marionette.find_element("id", "input-field") rel.send_keys(self.mod_key + 'v') - self.assertEqual(rel.get_attribute('value'), 'Displayed') + self.assertEqual(rel.get_attribute('value'), 'zyxw') def test_context_click_action(self): test_html = self.marionette.absolute_url("javascriptPage.html") diff --git a/testing/marionette/client/marionette/tests/unit/test_multi_finger.py b/testing/marionette/harness/marionette/tests/unit/test_multi_finger.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_multi_finger.py rename to testing/marionette/harness/marionette/tests/unit/test_multi_finger.py diff --git a/testing/marionette/client/marionette/tests/unit/test_navigation.py b/testing/marionette/harness/marionette/tests/unit/test_navigation.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_navigation.py rename to testing/marionette/harness/marionette/tests/unit/test_navigation.py diff --git a/testing/marionette/client/marionette/tests/unit/test_pagesource.py b/testing/marionette/harness/marionette/tests/unit/test_pagesource.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_pagesource.py rename to testing/marionette/harness/marionette/tests/unit/test_pagesource.py diff --git a/testing/marionette/client/marionette/tests/unit/test_position.py b/testing/marionette/harness/marionette/tests/unit/test_position.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_position.py rename to testing/marionette/harness/marionette/tests/unit/test_position.py diff --git a/testing/marionette/client/marionette/tests/unit/test_profile_management.py b/testing/marionette/harness/marionette/tests/unit/test_profile_management.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_profile_management.py rename to testing/marionette/harness/marionette/tests/unit/test_profile_management.py diff --git a/testing/marionette/client/marionette/tests/unit/test_proxy.py b/testing/marionette/harness/marionette/tests/unit/test_proxy.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_proxy.py rename to testing/marionette/harness/marionette/tests/unit/test_proxy.py diff --git a/testing/marionette/client/marionette/tests/unit/test_rendered_element.py b/testing/marionette/harness/marionette/tests/unit/test_rendered_element.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_rendered_element.py rename to testing/marionette/harness/marionette/tests/unit/test_rendered_element.py diff --git a/testing/marionette/client/marionette/tests/unit/test_report.py b/testing/marionette/harness/marionette/tests/unit/test_report.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_report.py rename to testing/marionette/harness/marionette/tests/unit/test_report.py diff --git a/testing/marionette/client/marionette/tests/unit/test_run_js_test.py b/testing/marionette/harness/marionette/tests/unit/test_run_js_test.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_run_js_test.py rename to testing/marionette/harness/marionette/tests/unit/test_run_js_test.py diff --git a/testing/marionette/client/marionette/tests/unit/test_screen_orientation.py b/testing/marionette/harness/marionette/tests/unit/test_screen_orientation.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_screen_orientation.py rename to testing/marionette/harness/marionette/tests/unit/test_screen_orientation.py diff --git a/testing/marionette/client/marionette/tests/unit/test_screenshot.py b/testing/marionette/harness/marionette/tests/unit/test_screenshot.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_screenshot.py rename to testing/marionette/harness/marionette/tests/unit/test_screenshot.py diff --git a/testing/marionette/client/marionette/tests/unit/test_selected.py b/testing/marionette/harness/marionette/tests/unit/test_selected.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_selected.py rename to testing/marionette/harness/marionette/tests/unit/test_selected.py diff --git a/testing/marionette/client/marionette/tests/unit/test_selected_chrome.py b/testing/marionette/harness/marionette/tests/unit/test_selected_chrome.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_selected_chrome.py rename to testing/marionette/harness/marionette/tests/unit/test_selected_chrome.py diff --git a/testing/marionette/client/marionette/tests/unit/test_session.py b/testing/marionette/harness/marionette/tests/unit/test_session.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_session.py rename to testing/marionette/harness/marionette/tests/unit/test_session.py diff --git a/testing/marionette/client/marionette/tests/unit/test_set_window_size.py b/testing/marionette/harness/marionette/tests/unit/test_set_window_size.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_set_window_size.py rename to testing/marionette/harness/marionette/tests/unit/test_set_window_size.py diff --git a/testing/marionette/client/marionette/tests/unit/test_shadow_dom.py b/testing/marionette/harness/marionette/tests/unit/test_shadow_dom.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_shadow_dom.py rename to testing/marionette/harness/marionette/tests/unit/test_shadow_dom.py diff --git a/testing/marionette/client/marionette/tests/unit/test_simpletest_chrome.js b/testing/marionette/harness/marionette/tests/unit/test_simpletest_chrome.js similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_simpletest_chrome.js rename to testing/marionette/harness/marionette/tests/unit/test_simpletest_chrome.js diff --git a/testing/marionette/client/marionette/tests/unit/test_simpletest_fail.js b/testing/marionette/harness/marionette/tests/unit/test_simpletest_fail.js similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_simpletest_fail.js rename to testing/marionette/harness/marionette/tests/unit/test_simpletest_fail.js diff --git a/testing/marionette/client/marionette/tests/unit/test_simpletest_pass.js b/testing/marionette/harness/marionette/tests/unit/test_simpletest_pass.js similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_simpletest_pass.js rename to testing/marionette/harness/marionette/tests/unit/test_simpletest_pass.js diff --git a/testing/marionette/client/marionette/tests/unit/test_simpletest_sanity.py b/testing/marionette/harness/marionette/tests/unit/test_simpletest_sanity.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_simpletest_sanity.py rename to testing/marionette/harness/marionette/tests/unit/test_simpletest_sanity.py diff --git a/testing/marionette/client/marionette/tests/unit/test_simpletest_timeout.js b/testing/marionette/harness/marionette/tests/unit/test_simpletest_timeout.js similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_simpletest_timeout.js rename to testing/marionette/harness/marionette/tests/unit/test_simpletest_timeout.js diff --git a/testing/marionette/client/marionette/tests/unit/test_single_finger.py b/testing/marionette/harness/marionette/tests/unit/test_single_finger.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_single_finger.py rename to testing/marionette/harness/marionette/tests/unit/test_single_finger.py diff --git a/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py b/testing/marionette/harness/marionette/tests/unit/test_single_finger_desktop.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py rename to testing/marionette/harness/marionette/tests/unit/test_single_finger_desktop.py diff --git a/testing/marionette/client/marionette/tests/unit/test_skip_setup.py b/testing/marionette/harness/marionette/tests/unit/test_skip_setup.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_skip_setup.py rename to testing/marionette/harness/marionette/tests/unit/test_skip_setup.py diff --git a/testing/marionette/client/marionette/tests/unit/test_switch_frame.py b/testing/marionette/harness/marionette/tests/unit/test_switch_frame.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_switch_frame.py rename to testing/marionette/harness/marionette/tests/unit/test_switch_frame.py diff --git a/testing/marionette/client/marionette/tests/unit/test_switch_frame_chrome.py b/testing/marionette/harness/marionette/tests/unit/test_switch_frame_chrome.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_switch_frame_chrome.py rename to testing/marionette/harness/marionette/tests/unit/test_switch_frame_chrome.py diff --git a/testing/marionette/client/marionette/tests/unit/test_switch_remote_frame.py b/testing/marionette/harness/marionette/tests/unit/test_switch_remote_frame.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_switch_remote_frame.py rename to testing/marionette/harness/marionette/tests/unit/test_switch_remote_frame.py diff --git a/testing/marionette/client/marionette/tests/unit/test_teardown_context_preserved.py b/testing/marionette/harness/marionette/tests/unit/test_teardown_context_preserved.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_teardown_context_preserved.py rename to testing/marionette/harness/marionette/tests/unit/test_teardown_context_preserved.py diff --git a/testing/marionette/client/marionette/tests/unit/test_text.py b/testing/marionette/harness/marionette/tests/unit/test_text.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_text.py rename to testing/marionette/harness/marionette/tests/unit/test_text.py diff --git a/testing/marionette/client/marionette/tests/unit/test_text_chrome.py b/testing/marionette/harness/marionette/tests/unit/test_text_chrome.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_text_chrome.py rename to testing/marionette/harness/marionette/tests/unit/test_text_chrome.py diff --git a/testing/marionette/client/marionette/tests/unit/test_timeouts.py b/testing/marionette/harness/marionette/tests/unit/test_timeouts.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_timeouts.py rename to testing/marionette/harness/marionette/tests/unit/test_timeouts.py diff --git a/testing/marionette/client/marionette/tests/unit/test_transport.py b/testing/marionette/harness/marionette/tests/unit/test_transport.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_transport.py rename to testing/marionette/harness/marionette/tests/unit/test_transport.py diff --git a/testing/marionette/client/marionette/tests/unit/test_typing.py b/testing/marionette/harness/marionette/tests/unit/test_typing.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_typing.py rename to testing/marionette/harness/marionette/tests/unit/test_typing.py diff --git a/testing/marionette/client/marionette/tests/unit/test_using_permissions.py b/testing/marionette/harness/marionette/tests/unit/test_using_permissions.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_using_permissions.py rename to testing/marionette/harness/marionette/tests/unit/test_using_permissions.py diff --git a/testing/marionette/client/marionette/tests/unit/test_using_prefs.py b/testing/marionette/harness/marionette/tests/unit/test_using_prefs.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_using_prefs.py rename to testing/marionette/harness/marionette/tests/unit/test_using_prefs.py diff --git a/testing/marionette/client/marionette/tests/unit/test_visibility.py b/testing/marionette/harness/marionette/tests/unit/test_visibility.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_visibility.py rename to testing/marionette/harness/marionette/tests/unit/test_visibility.py diff --git a/testing/marionette/client/marionette/tests/unit/test_wait.py b/testing/marionette/harness/marionette/tests/unit/test_wait.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_wait.py rename to testing/marionette/harness/marionette/tests/unit/test_wait.py diff --git a/testing/marionette/client/marionette/tests/unit/test_window_handles.py b/testing/marionette/harness/marionette/tests/unit/test_window_handles.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_window_handles.py rename to testing/marionette/harness/marionette/tests/unit/test_window_handles.py diff --git a/testing/marionette/client/marionette/tests/unit/test_window_management.py b/testing/marionette/harness/marionette/tests/unit/test_window_management.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_window_management.py rename to testing/marionette/harness/marionette/tests/unit/test_window_management.py diff --git a/testing/marionette/client/marionette/tests/unit/test_window_position.py b/testing/marionette/harness/marionette/tests/unit/test_window_position.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_window_position.py rename to testing/marionette/harness/marionette/tests/unit/test_window_position.py diff --git a/testing/marionette/client/marionette/tests/unit/test_window_switching.py b/testing/marionette/harness/marionette/tests/unit/test_window_switching.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_window_switching.py rename to testing/marionette/harness/marionette/tests/unit/test_window_switching.py diff --git a/testing/marionette/client/marionette/tests/unit/test_window_title.py b/testing/marionette/harness/marionette/tests/unit/test_window_title.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_window_title.py rename to testing/marionette/harness/marionette/tests/unit/test_window_title.py diff --git a/testing/marionette/client/marionette/tests/unit/test_window_type.py b/testing/marionette/harness/marionette/tests/unit/test_window_type.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_window_type.py rename to testing/marionette/harness/marionette/tests/unit/test_window_type.py diff --git a/testing/marionette/client/marionette/tests/unit/test_with_using_context.py b/testing/marionette/harness/marionette/tests/unit/test_with_using_context.py similarity index 100% rename from testing/marionette/client/marionette/tests/unit/test_with_using_context.py rename to testing/marionette/harness/marionette/tests/unit/test_with_using_context.py diff --git a/testing/marionette/client/marionette/tests/unit/unit-tests.ini b/testing/marionette/harness/marionette/tests/unit/unit-tests.ini similarity index 98% rename from testing/marionette/client/marionette/tests/unit/unit-tests.ini rename to testing/marionette/harness/marionette/tests/unit/unit-tests.ini index 01216b1ed5..fe19fe9143 100644 --- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini +++ b/testing/marionette/harness/marionette/tests/unit/unit-tests.ini @@ -159,4 +159,9 @@ skip-if = os == "win" # http://bugs.python.org/issue14574 [test_shadow_dom.py] +[test_chrome.py] +b2g = false + [test_addons.py] + + diff --git a/testing/marionette/client/marionette/tests/update-tests.ini b/testing/marionette/harness/marionette/tests/update-tests.ini similarity index 100% rename from testing/marionette/client/marionette/tests/update-tests.ini rename to testing/marionette/harness/marionette/tests/update-tests.ini diff --git a/testing/marionette/client/marionette/tests/webapi-tests.ini b/testing/marionette/harness/marionette/tests/webapi-tests.ini similarity index 100% rename from testing/marionette/client/marionette/tests/webapi-tests.ini rename to testing/marionette/harness/marionette/tests/webapi-tests.ini diff --git a/testing/marionette/client/marionette/venv_b2g_update_test.sh b/testing/marionette/harness/marionette/venv_b2g_update_test.sh old mode 100755 new mode 100644 similarity index 100% rename from testing/marionette/client/marionette/venv_b2g_update_test.sh rename to testing/marionette/harness/marionette/venv_b2g_update_test.sh diff --git a/testing/marionette/client/marionette/www/bug814037.html b/testing/marionette/harness/marionette/www/bug814037.html similarity index 100% rename from testing/marionette/client/marionette/www/bug814037.html rename to testing/marionette/harness/marionette/www/bug814037.html diff --git a/testing/marionette/client/marionette/www/click_out_of_bounds_overflow.html b/testing/marionette/harness/marionette/www/click_out_of_bounds_overflow.html similarity index 100% rename from testing/marionette/client/marionette/www/click_out_of_bounds_overflow.html rename to testing/marionette/harness/marionette/www/click_out_of_bounds_overflow.html diff --git a/testing/marionette/client/marionette/www/clicks.html b/testing/marionette/harness/marionette/www/clicks.html similarity index 100% rename from testing/marionette/client/marionette/www/clicks.html rename to testing/marionette/harness/marionette/www/clicks.html diff --git a/testing/marionette/client/marionette/www/cssTransform.html b/testing/marionette/harness/marionette/www/cssTransform.html similarity index 100% rename from testing/marionette/client/marionette/www/cssTransform.html rename to testing/marionette/harness/marionette/www/cssTransform.html diff --git a/testing/marionette/client/marionette/www/cssTransform2.html b/testing/marionette/harness/marionette/www/cssTransform2.html similarity index 100% rename from testing/marionette/client/marionette/www/cssTransform2.html rename to testing/marionette/harness/marionette/www/cssTransform2.html diff --git a/testing/marionette/client/marionette/www/datetimePage.html b/testing/marionette/harness/marionette/www/datetimePage.html similarity index 100% rename from testing/marionette/client/marionette/www/datetimePage.html rename to testing/marionette/harness/marionette/www/datetimePage.html diff --git a/testing/marionette/client/marionette/www/deletingFrame.html b/testing/marionette/harness/marionette/www/deletingFrame.html similarity index 100% rename from testing/marionette/client/marionette/www/deletingFrame.html rename to testing/marionette/harness/marionette/www/deletingFrame.html diff --git a/testing/marionette/harness/marionette/www/double_click.html b/testing/marionette/harness/marionette/www/double_click.html new file mode 100644 index 0000000000..fb3ec217a6 --- /dev/null +++ b/testing/marionette/harness/marionette/www/double_click.html @@ -0,0 +1,18 @@ + + + + + Testing Double Click + +
+

zyxw

+
+ +
+ + + +
+ diff --git a/testing/marionette/client/marionette/www/element_bottom.html b/testing/marionette/harness/marionette/www/element_bottom.html similarity index 100% rename from testing/marionette/client/marionette/www/element_bottom.html rename to testing/marionette/harness/marionette/www/element_bottom.html diff --git a/testing/marionette/client/marionette/www/element_left.html b/testing/marionette/harness/marionette/www/element_left.html similarity index 100% rename from testing/marionette/client/marionette/www/element_left.html rename to testing/marionette/harness/marionette/www/element_left.html diff --git a/testing/marionette/client/marionette/www/element_outside_viewport.html b/testing/marionette/harness/marionette/www/element_outside_viewport.html similarity index 100% rename from testing/marionette/client/marionette/www/element_outside_viewport.html rename to testing/marionette/harness/marionette/www/element_outside_viewport.html diff --git a/testing/marionette/client/marionette/www/element_right.html b/testing/marionette/harness/marionette/www/element_right.html similarity index 100% rename from testing/marionette/client/marionette/www/element_right.html rename to testing/marionette/harness/marionette/www/element_right.html diff --git a/testing/marionette/client/marionette/www/element_top.html b/testing/marionette/harness/marionette/www/element_top.html similarity index 100% rename from testing/marionette/client/marionette/www/element_top.html rename to testing/marionette/harness/marionette/www/element_top.html diff --git a/testing/marionette/client/marionette/www/empty.html b/testing/marionette/harness/marionette/www/empty.html similarity index 100% rename from testing/marionette/client/marionette/www/empty.html rename to testing/marionette/harness/marionette/www/empty.html diff --git a/testing/marionette/client/marionette/www/formPage.html b/testing/marionette/harness/marionette/www/formPage.html similarity index 100% rename from testing/marionette/client/marionette/www/formPage.html rename to testing/marionette/harness/marionette/www/formPage.html diff --git a/testing/marionette/client/marionette/www/frameset.html b/testing/marionette/harness/marionette/www/frameset.html similarity index 100% rename from testing/marionette/client/marionette/www/frameset.html rename to testing/marionette/harness/marionette/www/frameset.html diff --git a/testing/marionette/client/marionette/www/framesetPage2.html b/testing/marionette/harness/marionette/www/framesetPage2.html similarity index 100% rename from testing/marionette/client/marionette/www/framesetPage2.html rename to testing/marionette/harness/marionette/www/framesetPage2.html diff --git a/testing/marionette/client/marionette/www/hidden.html b/testing/marionette/harness/marionette/www/hidden.html similarity index 100% rename from testing/marionette/client/marionette/www/hidden.html rename to testing/marionette/harness/marionette/www/hidden.html diff --git a/testing/marionette/client/marionette/www/html5/blue.jpg b/testing/marionette/harness/marionette/www/html5/blue.jpg similarity index 100% rename from testing/marionette/client/marionette/www/html5/blue.jpg rename to testing/marionette/harness/marionette/www/html5/blue.jpg diff --git a/testing/marionette/client/marionette/www/html5/boolean_attributes.html b/testing/marionette/harness/marionette/www/html5/boolean_attributes.html similarity index 100% rename from testing/marionette/client/marionette/www/html5/boolean_attributes.html rename to testing/marionette/harness/marionette/www/html5/boolean_attributes.html diff --git a/testing/marionette/client/marionette/www/html5/geolocation.js b/testing/marionette/harness/marionette/www/html5/geolocation.js similarity index 100% rename from testing/marionette/client/marionette/www/html5/geolocation.js rename to testing/marionette/harness/marionette/www/html5/geolocation.js diff --git a/testing/marionette/client/marionette/www/html5/green.jpg b/testing/marionette/harness/marionette/www/html5/green.jpg similarity index 100% rename from testing/marionette/client/marionette/www/html5/green.jpg rename to testing/marionette/harness/marionette/www/html5/green.jpg diff --git a/testing/marionette/client/marionette/www/html5/offline.html b/testing/marionette/harness/marionette/www/html5/offline.html similarity index 100% rename from testing/marionette/client/marionette/www/html5/offline.html rename to testing/marionette/harness/marionette/www/html5/offline.html diff --git a/testing/marionette/client/marionette/www/html5/red.jpg b/testing/marionette/harness/marionette/www/html5/red.jpg similarity index 100% rename from testing/marionette/client/marionette/www/html5/red.jpg rename to testing/marionette/harness/marionette/www/html5/red.jpg diff --git a/testing/marionette/client/marionette/www/html5/status.html b/testing/marionette/harness/marionette/www/html5/status.html similarity index 100% rename from testing/marionette/client/marionette/www/html5/status.html rename to testing/marionette/harness/marionette/www/html5/status.html diff --git a/testing/marionette/client/marionette/www/html5/test.appcache b/testing/marionette/harness/marionette/www/html5/test.appcache similarity index 100% rename from testing/marionette/client/marionette/www/html5/test.appcache rename to testing/marionette/harness/marionette/www/html5/test.appcache diff --git a/testing/marionette/client/marionette/www/html5/test_html_inputs.html b/testing/marionette/harness/marionette/www/html5/test_html_inputs.html similarity index 100% rename from testing/marionette/client/marionette/www/html5/test_html_inputs.html rename to testing/marionette/harness/marionette/www/html5/test_html_inputs.html diff --git a/testing/marionette/client/marionette/www/html5/yellow.jpg b/testing/marionette/harness/marionette/www/html5/yellow.jpg similarity index 100% rename from testing/marionette/client/marionette/www/html5/yellow.jpg rename to testing/marionette/harness/marionette/www/html5/yellow.jpg diff --git a/testing/marionette/client/marionette/www/html5Page.html b/testing/marionette/harness/marionette/www/html5Page.html similarity index 100% rename from testing/marionette/client/marionette/www/html5Page.html rename to testing/marionette/harness/marionette/www/html5Page.html diff --git a/testing/marionette/client/marionette/www/javascriptPage.html b/testing/marionette/harness/marionette/www/javascriptPage.html similarity index 100% rename from testing/marionette/client/marionette/www/javascriptPage.html rename to testing/marionette/harness/marionette/www/javascriptPage.html diff --git a/testing/marionette/client/marionette/www/macbeth.html b/testing/marionette/harness/marionette/www/macbeth.html similarity index 100% rename from testing/marionette/client/marionette/www/macbeth.html rename to testing/marionette/harness/marionette/www/macbeth.html diff --git a/testing/marionette/client/marionette/www/modal_dialogs.html b/testing/marionette/harness/marionette/www/modal_dialogs.html similarity index 100% rename from testing/marionette/client/marionette/www/modal_dialogs.html rename to testing/marionette/harness/marionette/www/modal_dialogs.html diff --git a/testing/marionette/harness/marionette/www/nestedElements.html b/testing/marionette/harness/marionette/www/nestedElements.html new file mode 100644 index 0000000000..618bf3231b --- /dev/null +++ b/testing/marionette/harness/marionette/www/nestedElements.html @@ -0,0 +1,9 @@ + +hello world +hello worldhello world + + +hello worldhello worldhello world diff --git a/testing/marionette/client/marionette/www/rectangles.html b/testing/marionette/harness/marionette/www/rectangles.html similarity index 100% rename from testing/marionette/client/marionette/www/rectangles.html rename to testing/marionette/harness/marionette/www/rectangles.html diff --git a/testing/marionette/client/marionette/www/resultPage.html b/testing/marionette/harness/marionette/www/resultPage.html similarity index 100% rename from testing/marionette/client/marionette/www/resultPage.html rename to testing/marionette/harness/marionette/www/resultPage.html diff --git a/testing/marionette/client/marionette/www/scroll.html b/testing/marionette/harness/marionette/www/scroll.html similarity index 100% rename from testing/marionette/client/marionette/www/scroll.html rename to testing/marionette/harness/marionette/www/scroll.html diff --git a/testing/marionette/client/marionette/www/scroll2.html b/testing/marionette/harness/marionette/www/scroll2.html similarity index 100% rename from testing/marionette/client/marionette/www/scroll2.html rename to testing/marionette/harness/marionette/www/scroll2.html diff --git a/testing/marionette/client/marionette/www/scroll3.html b/testing/marionette/harness/marionette/www/scroll3.html similarity index 100% rename from testing/marionette/client/marionette/www/scroll3.html rename to testing/marionette/harness/marionette/www/scroll3.html diff --git a/testing/marionette/client/marionette/www/scroll4.html b/testing/marionette/harness/marionette/www/scroll4.html similarity index 100% rename from testing/marionette/client/marionette/www/scroll4.html rename to testing/marionette/harness/marionette/www/scroll4.html diff --git a/testing/marionette/client/marionette/www/scroll5.html b/testing/marionette/harness/marionette/www/scroll5.html similarity index 100% rename from testing/marionette/client/marionette/www/scroll5.html rename to testing/marionette/harness/marionette/www/scroll5.html diff --git a/testing/marionette/client/marionette/www/shim.js b/testing/marionette/harness/marionette/www/shim.js similarity index 100% rename from testing/marionette/client/marionette/www/shim.js rename to testing/marionette/harness/marionette/www/shim.js diff --git a/testing/marionette/client/marionette/www/test.html b/testing/marionette/harness/marionette/www/test.html similarity index 100% rename from testing/marionette/client/marionette/www/test.html rename to testing/marionette/harness/marionette/www/test.html diff --git a/testing/marionette/client/marionette/www/testAction.html b/testing/marionette/harness/marionette/www/testAction.html similarity index 100% rename from testing/marionette/client/marionette/www/testAction.html rename to testing/marionette/harness/marionette/www/testAction.html diff --git a/testing/marionette/client/marionette/www/testPageSource.html b/testing/marionette/harness/marionette/www/testPageSource.html similarity index 100% rename from testing/marionette/client/marionette/www/testPageSource.html rename to testing/marionette/harness/marionette/www/testPageSource.html diff --git a/testing/marionette/client/marionette/www/testPageSource.xml b/testing/marionette/harness/marionette/www/testPageSource.xml similarity index 100% rename from testing/marionette/client/marionette/www/testPageSource.xml rename to testing/marionette/harness/marionette/www/testPageSource.xml diff --git a/testing/marionette/client/marionette/www/testPageSourceWithUnicodeChars.html b/testing/marionette/harness/marionette/www/testPageSourceWithUnicodeChars.html similarity index 100% rename from testing/marionette/client/marionette/www/testPageSourceWithUnicodeChars.html rename to testing/marionette/harness/marionette/www/testPageSourceWithUnicodeChars.html diff --git a/testing/marionette/client/marionette/www/testSize.html b/testing/marionette/harness/marionette/www/testSize.html similarity index 100% rename from testing/marionette/client/marionette/www/testSize.html rename to testing/marionette/harness/marionette/www/testSize.html diff --git a/testing/marionette/client/marionette/www/test_accessibility.html b/testing/marionette/harness/marionette/www/test_accessibility.html similarity index 100% rename from testing/marionette/client/marionette/www/test_accessibility.html rename to testing/marionette/harness/marionette/www/test_accessibility.html diff --git a/testing/marionette/harness/marionette/www/test_carets_columns.html b/testing/marionette/harness/marionette/www/test_carets_columns.html new file mode 100644 index 0000000000..4952361085 --- /dev/null +++ b/testing/marionette/harness/marionette/www/test_carets_columns.html @@ -0,0 +1,31 @@ + + + + + + + + + +
+
+

Before image 1

+

+

After image 1

+

Before image 2

+

+

After image 2

+
+
+ + diff --git a/testing/marionette/harness/marionette/www/test_carets_cursor.html b/testing/marionette/harness/marionette/www/test_carets_cursor.html new file mode 100644 index 0000000000..fdbd6fe7a8 --- /dev/null +++ b/testing/marionette/harness/marionette/www/test_carets_cursor.html @@ -0,0 +1,31 @@ + + + + + + Marionette tests for AccessibleCaret in cursor mode + + + +
+ + +
+
+
+ + +
+
+
ABCDEFGHI
+ + diff --git a/testing/marionette/client/marionette/www/test_carets_display_none.html b/testing/marionette/harness/marionette/www/test_carets_display_none.html similarity index 100% rename from testing/marionette/client/marionette/www/test_carets_display_none.html rename to testing/marionette/harness/marionette/www/test_carets_display_none.html diff --git a/testing/marionette/harness/marionette/www/test_carets_iframe.html b/testing/marionette/harness/marionette/www/test_carets_iframe.html new file mode 100644 index 0000000000..f6e6df9ba9 --- /dev/null +++ b/testing/marionette/harness/marionette/www/test_carets_iframe.html @@ -0,0 +1,20 @@ + + + + + + + Marionette tests for AccessibleCaret in selection mode (iframe) + + + + + + diff --git a/testing/marionette/harness/marionette/www/test_carets_longtext.html b/testing/marionette/harness/marionette/www/test_carets_longtext.html new file mode 100644 index 0000000000..7e2495509b --- /dev/null +++ b/testing/marionette/harness/marionette/www/test_carets_longtext.html @@ -0,0 +1,9 @@ + + + Bug 1094072: Orientation change test for AccessibleCaret positions + + +

long long text for orientation change test long long text for orientation change test long long text for orientation change test long long text for orientation change test

+
bottom text
+ + diff --git a/testing/marionette/client/marionette/www/test_selectioncarets_multipleline.html b/testing/marionette/harness/marionette/www/test_carets_multipleline.html similarity index 76% rename from testing/marionette/client/marionette/www/test_selectioncarets_multipleline.html rename to testing/marionette/harness/marionette/www/test_carets_multipleline.html index 8cd204d809..ff46a954ba 100644 --- a/testing/marionette/client/marionette/www/test_selectioncarets_multipleline.html +++ b/testing/marionette/harness/marionette/www/test_carets_multipleline.html @@ -6,7 +6,7 @@ - Bug 1019441: Marionette tests for selection carets (multiple lines) + Bug 1019441: Marionette tests for AccessibleCaret (multiple lines)
-
-
First Line

Second Line

Third Line
-
-
First Line

Second Line

Third Line
+
+
First Line

Second Line

Third Line
+
+
First Line

Second Line

Third Line
diff --git a/testing/marionette/client/marionette/www/test_selectioncarets_multiplerange.html b/testing/marionette/harness/marionette/www/test_carets_multiplerange.html similarity index 100% rename from testing/marionette/client/marionette/www/test_selectioncarets_multiplerange.html rename to testing/marionette/harness/marionette/www/test_carets_multiplerange.html diff --git a/testing/marionette/harness/marionette/www/test_carets_selection.html b/testing/marionette/harness/marionette/www/test_carets_selection.html new file mode 100644 index 0000000000..f58b92fbff --- /dev/null +++ b/testing/marionette/harness/marionette/www/test_carets_selection.html @@ -0,0 +1,38 @@ + + + + + + + Marionette tests for AccessibleCaret in selection mode + + + +
+ + +
+
+
+ + +
+
+
+
+
ABC DEF GHI
+
+
ABC DEF GHI
+
+
Non-selectable
+ + diff --git a/testing/marionette/client/marionette/www/test_clearing.html b/testing/marionette/harness/marionette/www/test_clearing.html similarity index 100% rename from testing/marionette/client/marionette/www/test_clearing.html rename to testing/marionette/harness/marionette/www/test_clearing.html diff --git a/testing/marionette/client/marionette/www/test_dynamic.html b/testing/marionette/harness/marionette/www/test_dynamic.html similarity index 100% rename from testing/marionette/client/marionette/www/test_dynamic.html rename to testing/marionette/harness/marionette/www/test_dynamic.html diff --git a/testing/marionette/client/marionette/www/test_iframe.html b/testing/marionette/harness/marionette/www/test_iframe.html similarity index 100% rename from testing/marionette/client/marionette/www/test_iframe.html rename to testing/marionette/harness/marionette/www/test_iframe.html diff --git a/testing/marionette/client/marionette/www/test_inner_iframe.html b/testing/marionette/harness/marionette/www/test_inner_iframe.html similarity index 100% rename from testing/marionette/client/marionette/www/test_inner_iframe.html rename to testing/marionette/harness/marionette/www/test_inner_iframe.html diff --git a/testing/marionette/client/marionette/www/test_nested_iframe.html b/testing/marionette/harness/marionette/www/test_nested_iframe.html similarity index 100% rename from testing/marionette/client/marionette/www/test_nested_iframe.html rename to testing/marionette/harness/marionette/www/test_nested_iframe.html diff --git a/testing/marionette/client/marionette/www/test_oop_1.html b/testing/marionette/harness/marionette/www/test_oop_1.html similarity index 100% rename from testing/marionette/client/marionette/www/test_oop_1.html rename to testing/marionette/harness/marionette/www/test_oop_1.html diff --git a/testing/marionette/client/marionette/www/test_oop_2.html b/testing/marionette/harness/marionette/www/test_oop_2.html similarity index 100% rename from testing/marionette/client/marionette/www/test_oop_2.html rename to testing/marionette/harness/marionette/www/test_oop_2.html diff --git a/testing/marionette/client/marionette/www/test_shadow_dom.html b/testing/marionette/harness/marionette/www/test_shadow_dom.html similarity index 100% rename from testing/marionette/client/marionette/www/test_shadow_dom.html rename to testing/marionette/harness/marionette/www/test_shadow_dom.html diff --git a/testing/marionette/client/marionette/www/test_windows.html b/testing/marionette/harness/marionette/www/test_windows.html similarity index 100% rename from testing/marionette/client/marionette/www/test_windows.html rename to testing/marionette/harness/marionette/www/test_windows.html diff --git a/testing/marionette/client/marionette/www/windowHandles.html b/testing/marionette/harness/marionette/www/windowHandles.html similarity index 100% rename from testing/marionette/client/marionette/www/windowHandles.html rename to testing/marionette/harness/marionette/www/windowHandles.html diff --git a/testing/marionette/client/marionette/www/xhtmlTest.html b/testing/marionette/harness/marionette/www/xhtmlTest.html similarity index 100% rename from testing/marionette/client/marionette/www/xhtmlTest.html rename to testing/marionette/harness/marionette/www/xhtmlTest.html diff --git a/testing/marionette/harness/requirements.txt b/testing/marionette/harness/requirements.txt new file mode 100644 index 0000000000..7058980bbc --- /dev/null +++ b/testing/marionette/harness/requirements.txt @@ -0,0 +1,14 @@ +marionette-driver >= 1.3.0 +browsermob-proxy >= 0.6.0 +manifestparser >= 1.1 +wptserve >= 1.3.0 +mozinfo >= 0.8 +mozprocess >= 0.9 +mozrunner >= 6.9 +mozdevice >= 0.44 +mozlog >= 3.0 +moznetwork >= 0.21 +mozcrash >= 0.5 +mozprofile >= 0.7 +moztest >= 0.7 +mozversion >= 1.1 diff --git a/testing/marionette/driver/setup.py b/testing/marionette/harness/setup.py similarity index 54% rename from testing/marionette/driver/setup.py rename to testing/marionette/harness/setup.py index dfcbc15dec..c768cd1d79 100644 --- a/testing/marionette/driver/setup.py +++ b/testing/marionette/harness/setup.py @@ -1,11 +1,8 @@ -# 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/. - import os import re from setuptools import setup, find_packages + THIS_DIR = os.path.dirname(os.path.realpath(__name__)) @@ -16,21 +13,27 @@ def read(*parts): def get_version(): return re.findall("__version__ = '([\d\.]+)'", - read('marionette_driver', '__init__.py'), re.M)[0] + read('marionette', '__init__.py'), re.M)[0] -setup(name='marionette_driver', +setup(name='marionette_client', version=get_version(), - description="Marionette Driver", - long_description='See http://marionette-driver.readthedocs.org/', + description="Marionette test automation client", + long_description='See http://marionette-client.readthedocs.org/', classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers keywords='mozilla', - author='Auto-tools', - author_email='tools-marionette@lists.mozilla.org', + author='Jonathan Griffin', + author_email='jgriffin@mozilla.com', url='https://wiki.mozilla.org/Auto-tools/Projects/Marionette', license='MPL', - packages=find_packages(), + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + package_data={'marionette': ['touch/*.js']}, include_package_data=True, zip_safe=False, + entry_points=""" + # -*- Entry points: -*- + [console_scripts] + marionette = marionette.runtests:cli + """, install_requires=read('requirements.txt').splitlines(), ) diff --git a/testing/marionette/jar.mn b/testing/marionette/jar.mn index 4dc32200f2..6238ee4840 100644 --- a/testing/marionette/jar.mn +++ b/testing/marionette/jar.mn @@ -22,10 +22,10 @@ marionette.jar: content/modal.js (modal.js) content/proxy.js (proxy.js) #ifdef ENABLE_TESTS - content/test.xul (client/marionette/chrome/test.xul) - content/test2.xul (client/marionette/chrome/test2.xul) - content/test_nested_iframe.xul (client/marionette/chrome/test_nested_iframe.xul) - content/test_anonymous_content.xul (client/marionette/chrome/test_anonymous_content.xul) + content/test.xul (harness/marionette/chrome/test.xul) + content/test2.xul (harness/marionette/chrome/test2.xul) + content/test_nested_iframe.xul (harness/marionette/chrome/test_nested_iframe.xul) + content/test_anonymous_content.xul (harness/marionette/chrome/test_anonymous_content.xul) #endif % content specialpowers %content/ diff --git a/testing/marionette/mach_commands.py b/testing/marionette/mach_commands.py index 53066954b0..d4e6277173 100644 --- a/testing/marionette/mach_commands.py +++ b/testing/marionette/mach_commands.py @@ -49,7 +49,7 @@ def run_marionette(tests, b2g_path=None, emulator=None, testtype=None, if not tests: tests = [os.path.join(topsrcdir, - 'testing/marionette/client/marionette/tests/unit-tests.ini')] + 'testing/marionette/harness/marionette/tests/unit-tests.ini')] args.tests = tests if b2g_path: diff --git a/testing/marionette/moz.build b/testing/marionette/moz.build index 0c87f1e9ac..d7d7e7c0cf 100644 --- a/testing/marionette/moz.build +++ b/testing/marionette/moz.build @@ -1,9 +1,14 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -DIRS += ['components', 'atoms'] +DIRS += ["components"] -JAR_MANIFESTS += ['jar.mn'] +JAR_MANIFESTS += ["jar.mn"] +#MARIONETTE_UNIT_MANIFESTS += ["harness/marionette/tests/unit/unit-tests.ini"] +#MARIONETTE_UPDATE_MANIFESTS += ["harness/marionette/tests/update-tests.ini"] +#MARIONETTE_WEBAPI_MANIFESTS += ["harness/marionette/tests/webapi-tests.ini"] +#XPCSHELL_TESTS_MANIFESTS += ["unit.ini"] + +with Files("**"): + BUG_COMPONENT = ("Testing", "Marionette") diff --git a/testing/marionette/test_element.js b/testing/marionette/test_element.js new file mode 100644 index 0000000000..0e14dc3140 --- /dev/null +++ b/testing/marionette/test_element.js @@ -0,0 +1,44 @@ +/* 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/. */ + +const {utils: Cu} = Components; + +Cu.import("chrome://marionette/content/element.js"); + +let el = { + getBoundingClientRect: function() { + return { + top: 0, + left: 0, + width: 100, + height: 100, + }; + } +}; + +add_test(function test_coordinates() { + let p = element.coordinates(el); + ok(p.hasOwnProperty("x")); + ok(p.hasOwnProperty("y")); + equal("number", typeof p.x); + equal("number", typeof p.y); + + deepEqual({x: 50, y: 50}, element.coordinates(el)); + deepEqual({x: 10, y: 10}, element.coordinates(el, 10, 10)); + deepEqual({x: -5, y: -5}, element.coordinates(el, -5, -5)); + + Assert.throws(() => element.coordinates(null)); + + Assert.throws(() => element.coordinates(el, "string", undefined)); + Assert.throws(() => element.coordinates(el, undefined, "string")); + Assert.throws(() => element.coordinates(el, "string", "string")); + Assert.throws(() => element.coordinates(el, {}, undefined)); + Assert.throws(() => element.coordinates(el, undefined, {})); + Assert.throws(() => element.coordinates(el, {}, {})); + Assert.throws(() => element.coordinates(el, [], undefined)); + Assert.throws(() => element.coordinates(el, undefined, [])); + Assert.throws(() => element.coordinates(el, [], [])); + + run_next_test(); +}); diff --git a/testing/marionette/test_error.js b/testing/marionette/test_error.js new file mode 100644 index 0000000000..d4caa7ca0d --- /dev/null +++ b/testing/marionette/test_error.js @@ -0,0 +1,337 @@ +/* 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/. */ + +const {utils: Cu} = Components; + +Cu.import("chrome://marionette/content/error.js"); + +function notok(condition) { + ok(!(condition)); +} + +add_test(function test_BuiltinErrors() { + ok("Error" in error.BuiltinErrors); + ok("EvalError" in error.BuiltinErrors); + ok("InternalError" in error.BuiltinErrors); + ok("RangeError" in error.BuiltinErrors); + ok("ReferenceError" in error.BuiltinErrors); + ok("SyntaxError" in error.BuiltinErrors); + ok("TypeError" in error.BuiltinErrors); + ok("URIError" in error.BuiltinErrors); + + run_next_test(); +}); + +add_test(function test_isError() { + notok(error.isError(null)); + notok(error.isError([])); + notok(error.isError(new Date())); + + ok(error.isError(new Components.Exception())); + ok(error.isError(new Error())); + ok(error.isError(new EvalError())); + ok(error.isError(new InternalError())); + ok(error.isError(new RangeError())); + ok(error.isError(new ReferenceError())); + ok(error.isError(new SyntaxError())); + ok(error.isError(new TypeError())); + ok(error.isError(new URIError())); + ok(error.isError(new WebDriverError())); + ok(error.isError(new InvalidArgumentError())); + + run_next_test(); +}); + +add_test(function test_isWebDriverError() { + notok(error.isWebDriverError(new Components.Exception())); + notok(error.isWebDriverError(new Error())); + notok(error.isWebDriverError(new EvalError())); + notok(error.isWebDriverError(new InternalError())); + notok(error.isWebDriverError(new RangeError())); + notok(error.isWebDriverError(new ReferenceError())); + notok(error.isWebDriverError(new SyntaxError())); + notok(error.isWebDriverError(new TypeError())); + notok(error.isWebDriverError(new URIError())); + + ok(error.isWebDriverError(new WebDriverError())); + ok(error.isWebDriverError(new InvalidArgumentError())); + + run_next_test(); +}); + +add_test(function test_wrap() { + equal(error.wrap(new WebDriverError()).name, "WebDriverError"); + equal(error.wrap(new InvalidArgumentError()).name, "InvalidArgumentError"); + equal(error.wrap(new Error()).name, "WebDriverError"); + equal(error.wrap(new EvalError()).name, "WebDriverError"); + equal(error.wrap(new InternalError()).name, "WebDriverError"); + equal(error.wrap(new RangeError()).name, "WebDriverError"); + equal(error.wrap(new ReferenceError()).name, "WebDriverError"); + equal(error.wrap(new SyntaxError()).name, "WebDriverError"); + equal(error.wrap(new TypeError()).name, "WebDriverError"); + equal(error.wrap(new URIError()).name, "WebDriverError"); + + run_next_test(); +}); + +add_test(function test_stringify() { + equal("", error.stringify()); + equal("", error.stringify("foo")); + equal("[object Object]", error.stringify({})); + equal("[object Object]\nfoo", error.stringify({stack: "foo"})); + equal("Error: foo", error.stringify(new Error("foo")).split("\n")[0]); + equal("WebDriverError: foo", + error.stringify(new WebDriverError("foo")).split("\n")[0]); + equal("InvalidArgumentError: foo", + error.stringify(new InvalidArgumentError("foo")).split("\n")[0]); + + run_next_test(); +}); + +add_test(function test_toJson() { + Assert.throws(() => error.toJson(new Error()), + /Unserialisable error type: [object Error]/); + + let e1 = new WebDriverError("a"); + deepEqual({error: e1.status, message: "a", stacktrace: null}, + error.toJson(e1)); + + let e2 = new JavaScriptError("first", "second", "third", "fourth"); + let e2s = error.toJson(e2); + equal(e2.status, e2s.error); + equal(e2.message, e2s.message); + ok(e2s.stacktrace.match(/second/)); + ok(e2s.stacktrace.match(/third/)); + ok(e2s.stacktrace.match(/fourth/)); + + run_next_test(); +}); + +add_test(function test_fromJson() { + Assert.throws(() => error.fromJson({error: "foo"}), + /Undeserialisable error type: foo/); + Assert.throws(() => error.fromJson({error: "Error"}), + /Undeserialisable error type: Error/); + Assert.throws(() => error.fromJson({}), + /Undeserialisable error type: undefined/); + + let e1 = new WebDriverError("1"); + deepEqual(e1, error.fromJson({error: "webdriver error", message: "1"})); + let e2 = new InvalidArgumentError("2"); + deepEqual(e2, error.fromJson({error: "invalid argument", message: "2"})); + + let e3 = new JavaScriptError("first", "second", "third", "fourth"); + let e3s = error.toJson(e3); + deepEqual(e3, error.fromJson(e3s)); + + run_next_test(); +}); + +add_test(function test_WebDriverError() { + let err = new WebDriverError("foo"); + equal("WebDriverError", err.name); + equal("foo", err.message); + equal("webdriver error", err.status); + equal(Error.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_ElementNotAccessibleError() { + let err = new ElementNotAccessibleError("foo"); + equal("ElementNotAccessibleError", err.name); + equal("foo", err.message); + equal("element not accessible", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_ElementNotVisibleError() { + let err = new ElementNotVisibleError("foo"); + equal("ElementNotVisibleError", err.name); + equal("foo", err.message); + equal("element not visible", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_InvalidArgumentError() { + let err = new InvalidArgumentError("foo"); + equal("InvalidArgumentError", err.name); + equal("foo", err.message); + equal("invalid argument", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_InvalidElementStateError() { + let err = new InvalidElementStateError("foo"); + equal("InvalidElementStateError", err.name); + equal("foo", err.message); + equal("invalid element state", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_InvalidSelectorError() { + let err = new InvalidSelectorError("foo"); + equal("InvalidSelectorError", err.name); + equal("foo", err.message); + equal("invalid selector", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_InvalidSessionIdError() { + let err = new InvalidSessionIdError("foo"); + equal("InvalidSessionIdError", err.name); + equal("foo", err.message); + equal("invalid session id", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_JavaScriptError() { + let err = new JavaScriptError("foo"); + equal("JavaScriptError", err.name); + equal("foo", err.message); + equal("javascript error", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + equal("undefined", new JavaScriptError(undefined).message); + // TODO(ato): Bug 1240550 + //equal("funcname @file", new JavaScriptError("message", "funcname", "file").stack); + equal("funcname @file, line line", + new JavaScriptError("message", "funcname", "file", "line").stack); + + // TODO(ato): More exhaustive tests for JS stack computation + + run_next_test(); +}); + +add_test(function test_NoAlertOpenError() { + let err = new NoAlertOpenError("foo"); + equal("NoAlertOpenError", err.name); + equal("foo", err.message); + equal("no such alert", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_NoSuchElementError() { + let err = new NoSuchElementError("foo"); + equal("NoSuchElementError", err.name); + equal("foo", err.message); + equal("no such element", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_NoSuchFrameError() { + let err = new NoSuchFrameError("foo"); + equal("NoSuchFrameError", err.name); + equal("foo", err.message); + equal("no such frame", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_NoSuchWindowError() { + let err = new NoSuchWindowError("foo"); + equal("NoSuchWindowError", err.name); + equal("foo", err.message); + equal("no such window", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_ScriptTimeoutError() { + let err = new ScriptTimeoutError("foo"); + equal("ScriptTimeoutError", err.name); + equal("foo", err.message); + equal("script timeout", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_SessionNotCreatedError() { + let err = new SessionNotCreatedError("foo"); + equal("SessionNotCreatedError", err.name); + equal("foo", err.message); + equal("session not created", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_StaleElementReferenceError() { + let err = new StaleElementReferenceError("foo"); + equal("StaleElementReferenceError", err.name); + equal("foo", err.message); + equal("stale element reference", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_TimeoutError() { + let err = new TimeoutError("foo"); + equal("TimeoutError", err.name); + equal("foo", err.message); + equal("timeout", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_UnableToSetCookieError() { + let err = new UnableToSetCookieError("foo"); + equal("UnableToSetCookieError", err.name); + equal("foo", err.message); + equal("unable to set cookie", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_UnknownCommandError() { + let err = new UnknownCommandError("foo"); + equal("UnknownCommandError", err.name); + equal("foo", err.message); + equal("unknown command", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_UnknownError() { + let err = new UnknownError("foo"); + equal("UnknownError", err.name); + equal("foo", err.message); + equal("unknown error", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); + +add_test(function test_UnsupportedOperationError() { + let err = new UnsupportedOperationError("foo"); + equal("UnsupportedOperationError", err.name); + equal("foo", err.message); + equal("unsupported operation", err.status); + equal(WebDriverError.prototype.toString(), Object.getPrototypeOf(err).toString()); + + run_next_test(); +}); diff --git a/testing/marionette/test_message.js b/testing/marionette/test_message.js new file mode 100644 index 0000000000..5ca4c6aa79 --- /dev/null +++ b/testing/marionette/test_message.js @@ -0,0 +1,204 @@ +/* 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/. */ + +const {utils: Cu} = Components; + +Cu.import("chrome://marionette/content/error.js"); +Cu.import("chrome://marionette/content/message.js"); + +add_test(function test_MessageOrigin() { + equal(0, MessageOrigin.Client); + equal(1, MessageOrigin.Server); + + run_next_test(); +}); + +add_test(function test_Message_fromMsg() { + let cmd = new Command(4, "foo"); + let resp = new Response(5, () => {}); + + ok(Message.fromMsg(cmd.toMsg()) instanceof Command); + ok(Message.fromMsg(resp.toMsg()) instanceof Response); + Assert.throws(() => Message.fromMsg([3, 4, 5, 6]), + /Unrecognised message type in packet/); + + run_next_test(); +}); + +add_test(function test_Command() { + let cmd = new Command(42, "foo", {bar: "baz"}); + equal(42, cmd.id); + equal("foo", cmd.name); + deepEqual({bar: "baz"}, cmd.parameters); + equal(null, cmd.onerror); + equal(null, cmd.onresult); + equal(MessageOrigin.Client, cmd.origin); + equal(false, cmd.sent); + + run_next_test(); +}); + +add_test(function test_Command_onresponse() { + let onerrorOk = false; + let onresultOk = false; + + let cmd = new Command(); + cmd.onerror = () => onerrorOk = true; + cmd.onresult = () => onresultOk = true; + + let errorResp = new Response(); + errorResp.error = new WebDriverError("foo"); + + let bodyResp = new Response(); + bodyResp.body = "bar"; + + cmd.onresponse(errorResp); + equal(true, onerrorOk); + equal(false, onresultOk); + + cmd.onresponse(bodyResp); + equal(true, onresultOk); + + run_next_test(); +}); + +add_test(function test_Command_fromMsg() { + let cmd = new Command(42, "bar", {bar: "baz"}); + let msg = cmd.toMsg(); + + equal(Command.TYPE, msg[0]); + equal(cmd.id, msg[1]); + equal(cmd.name, msg[2]); + equal(cmd.parameters, msg[3]); + + run_next_test(); +}); + +add_test(function test_Command_toString() { + let cmd = new Command(42, "foo", {bar: "baz"}); + equal(`Command {id: ${cmd.id}, ` + + `name: ${JSON.stringify(cmd.name)}, ` + + `parameters: ${JSON.stringify(cmd.parameters)}}`, + cmd.toString()); + + run_next_test(); +}); + +add_test(function test_Command_fromMsg() { + let c1 = new Command(42, "foo", {bar: "baz"}); + + let msg = c1.toMsg(); + let c2 = Command.fromMsg(msg); + + equal(c1.id, c2.id); + equal(c1.name, c2.name); + equal(c1.parameters, c2.parameters); + + run_next_test(); +}); + +add_test(function test_Command_TYPE() { + equal(0, Command.TYPE); + run_next_test(); +}); + +add_test(function test_Response() { + let handler = () => run_next_test(); + + let resp = new Response(42, handler); + equal(42, resp.id); + equal(null, resp.error); + ok("origin" in resp); + equal(MessageOrigin.Server, resp.origin); + equal(false, resp.sent); + equal(handler, resp.respHandler_); + + run_next_test(); +}); + +add_test(function test_Response_sendConditionally() { + let fired = false; + let resp = new Response(42, () => fired = true); + resp.sendConditionally(r => false); + equal(false, resp.sent); + equal(false, fired); + resp.sendConditionally(r => true); + equal(true, resp.sent); + equal(true, fired); + + run_next_test(); +}); + +add_test(function test_Response_send() { + let fired = false; + let resp = new Response(42, () => fired = true); + resp.send(); + equal(true, resp.sent); + equal(true, fired); + + run_next_test(); +}); + +add_test(function test_Response_sendError() { + let err = new WebDriverError(); + let resp = new Response(42, r => { + deepEqual(error.toJson(err), r.error); + equal(null, r.body); + equal(false, r.sent); + }); + + resp.sendError(err); + equal(true, resp.sent); + Assert.throws(() => resp.send(), /already been sent/); + + resp.sent = false; + Assert.throws(() => resp.sendError(new Error())); + + run_next_test(); +}); + +add_test(function test_Response_toMsg() { + let resp = new Response(42); + let msg = resp.toMsg(); + + equal(Response.TYPE, msg[0]); + equal(resp.id, msg[1]); + equal(resp.error, msg[2]); + equal(resp.body, msg[3]); + + run_next_test(); +}); + +add_test(function test_Response_toString() { + let resp = new Response(42); + resp.error = "foo"; + resp.body = "bar"; + + equal(`Response {id: ${resp.id}, ` + + `error: ${JSON.stringify(resp.error)}, ` + + `body: ${JSON.stringify(resp.body)}}`, + resp.toString()); + + run_next_test(); +}); + +add_test(function test_Response_fromMsg() { + let r1 = new Response(42); + r1.error = "foo"; + r1.body = "bar"; + + let msg = r1.toMsg(); + let r2 = Response.fromMsg(msg); + + equal(r1.id, r2.id); + equal(r1.error, r2.error); + equal(r1.body, r2.body); + + run_next_test(); +}); + +add_test(function test_Response_TYPE() { + equal(1, Response.TYPE); + run_next_test(); +}); diff --git a/testing/marionette/unit.ini b/testing/marionette/unit.ini new file mode 100644 index 0000000000..c8fc290e4f --- /dev/null +++ b/testing/marionette/unit.ini @@ -0,0 +1,12 @@ +# 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/. + +# xpcshell unit tests for Marionette + +[DEFAULT] +skip-if = appname == "thunderbird" + +[test_element.js] +[test_error.js] +[test_message.js] diff --git a/testing/mozharness/configs/b2g/desktop_automation_config.py b/testing/mozharness/configs/b2g/desktop_automation_config.py deleted file mode 100644 index 80c3c3f6b2..0000000000 --- a/testing/mozharness/configs/b2g/desktop_automation_config.py +++ /dev/null @@ -1,78 +0,0 @@ -# This is a template config file for b2g desktop unittest production. -import os - -config = { - # mozharness options - "application": "b2g", - "tooltool_cache": "/builds/tooltool_cache", - - "exes": { - 'python': '/tools/buildbot/bin/python', - 'virtualenv': ['/tools/buildbot/bin/python', '/tools/misc-python/virtualenv.py'], - 'tooltool.py': "/tools/tooltool.py", - }, - - "find_links": [ - "http://pypi.pvt.build.mozilla.org/pub", - "http://pypi.pub.build.mozilla.org/pub", - ], - "pip_index": False, - - "buildbot_json_path": "buildprops.json", - - "default_actions": [ - 'clobber', - 'read-buildbot-config', - 'download-and-extract', - 'create-virtualenv', - 'install', - 'run-tests', - ], - "download_symbols": "ondemand", - "download_minidump_stackwalk": True, - "default_blob_upload_servers": [ - "https://blobupload.elasticbeanstalk.com", - ], - "blob_uploader_auth_file": os.path.join(os.getcwd(), "oauth.txt"), - - # testsuite options - "run_file_names": { - "mochitest": "runtestsb2g.py", - "reftest": "runreftestb2g.py", - }, - "suite_definitions": { - "mochitest": { - "options": [ - "--total-chunks=%(total_chunks)s", - "--this-chunk=%(this_chunk)s", - "--profile=%(gaia_profile)s", - "--app=%(application)s", - "--desktop", - "--utility-path=%(utility_path)s", - "--certificate-path=%(cert_path)s", - "--symbols-path=%(symbols_path)s", - "--browser-arg=%(browser_arg)s", - "--quiet", - "--log-raw=%(raw_log_file)s", - "--log-errorsummary=%(error_summary_file)s", - "--screenshot-on-fail", - ], - "run_filename": "runtestsb2g.py", - "testsdir": "mochitest" - }, - "reftest": { - "options": [ - "--desktop", - "--profile=%(gaia_profile)s", - "--appname=%(application)s", - "--total-chunks=%(total_chunks)s", - "--this-chunk=%(this_chunk)s", - "--browser-arg=%(browser_arg)s", - "--symbols-path=%(symbols_path)s", - "%(test_manifest)s" - ], - "run_filename": "runreftestsb2g.py", - "testsdir": "reftest" - } - }, -} diff --git a/testing/mozharness/configs/b2g/emulator_automation_config.py b/testing/mozharness/configs/b2g/emulator_automation_config.py index efe8254997..7f83979666 100644 --- a/testing/mozharness/configs/b2g/emulator_automation_config.py +++ b/testing/mozharness/configs/b2g/emulator_automation_config.py @@ -1,4 +1,6 @@ # This is a template config file for b2g emulator unittest production. +# TODO: This could be removed after B2G ICS emulator buildbot builds is turned +# off, Bug 1209180. import os config = { diff --git a/testing/mozharness/configs/b2g/gaia_integration_config.py b/testing/mozharness/configs/b2g/gaia_integration_config.py deleted file mode 100644 index 3e95b93b1e..0000000000 --- a/testing/mozharness/configs/b2g/gaia_integration_config.py +++ /dev/null @@ -1,40 +0,0 @@ -# This is a template config file for b2g emulator unittest testing -import platform - -HG_SHARE_BASE_DIR = "/builds/hg-shared" - -if platform.system().lower() == 'darwin': - xre_url = "https://api.pub.build.mozilla.org/tooltool/sha512/4d8d7a37d90c34a2a2fda3066a8fe85c189b183d05389cb957fc6fed31f10a6924e50c1b84488ff61c015293803f58a3aed5d4819374d04c8e0ee2b9e3997278" -else: - xre_url = "https://api.pub.build.mozilla.org/tooltool/sha512/dc9503b21c87b5a469118746f99e4f41d73888972ce735fa10a80f6d218086da0e3da525d9a4cd8e4ea497ec199fef720e4a525873d77a1af304ac505e076462" - -config = { - # mozharness script options - "xre_url": xre_url, - - "vcs_share_base": HG_SHARE_BASE_DIR, - "exes": { - 'python': '/tools/buildbot/bin/python', - 'virtualenv': ['/tools/buildbot/bin/python', '/tools/misc-python/virtualenv.py'], - 'tooltool.py': "/tools/tooltool.py", - }, - - "find_links": [ - "http://pypi.pvt.build.mozilla.org/pub", - "http://pypi.pub.build.mozilla.org/pub", - ], - "pip_index": False, - - "buildbot_json_path": "buildprops.json", - - "default_actions": [ - 'clobber', - 'read-buildbot-config', - 'pull', - 'download-and-extract', - 'create-virtualenv', - 'install', - 'run-tests', - ], - "vcs_output_timeout": 1760, -} diff --git a/testing/mozharness/configs/b2g/gaia_unit_production_config.py b/testing/mozharness/configs/b2g/gaia_unit_production_config.py deleted file mode 100644 index 027dea9179..0000000000 --- a/testing/mozharness/configs/b2g/gaia_unit_production_config.py +++ /dev/null @@ -1,45 +0,0 @@ -# This is a template config file for b2g emulator unittest testing -import platform -import os - -HG_SHARE_BASE_DIR = "/builds/hg-shared" - -if platform.system().lower() == 'darwin': - xre_url = "https://api.pub.build.mozilla.org/tooltool/sha512/4d8d7a37d90c34a2a2fda3066a8fe85c189b183d05389cb957fc6fed31f10a6924e50c1b84488ff61c015293803f58a3aed5d4819374d04c8e0ee2b9e3997278" -else: - xre_url = "https://api.pub.build.mozilla.org/tooltool/sha512/dc9503b21c87b5a469118746f99e4f41d73888972ce735fa10a80f6d218086da0e3da525d9a4cd8e4ea497ec199fef720e4a525873d77a1af304ac505e076462" - -config = { - # mozharness script options - "xre_url": xre_url, - - "vcs_share_base": HG_SHARE_BASE_DIR, - "exes": { - 'python': '/tools/buildbot/bin/python', - 'virtualenv': ['/tools/buildbot/bin/python', '/tools/misc-python/virtualenv.py'], - 'tooltool.py': "/tools/tooltool.py", - }, - - "find_links": [ - "http://pypi.pvt.build.mozilla.org/pub", - "http://pypi.pub.build.mozilla.org/pub", - ], - "pip_index": False, - - "buildbot_json_path": "buildprops.json", - - "default_actions": [ - 'clobber', - 'read-buildbot-config', - 'pull', - 'download-and-extract', - 'create-virtualenv', - 'install', - 'run-tests', - ], - "default_blob_upload_servers": [ - "https://blobupload.elasticbeanstalk.com", - ], - "blob_uploader_auth_file": os.path.join(os.getcwd(), "oauth.txt"), - "vcs_output_timeout": 1760, -} diff --git a/testing/mozharness/configs/b2g/generic_config.py b/testing/mozharness/configs/b2g/generic_config.py deleted file mode 100644 index 329c9d7641..0000000000 --- a/testing/mozharness/configs/b2g/generic_config.py +++ /dev/null @@ -1,25 +0,0 @@ -# This is a config with generic releng related values -import os - -config = { - "exes": { - 'python': '/tools/buildbot/bin/python', - 'virtualenv': ['/tools/buildbot/bin/python', '/tools/misc-python/virtualenv.py'], - 'tooltool.py': "/tools/tooltool.py", - }, - - "find_links": [ - "http://pypi.pvt.build.mozilla.org/pub", - "http://pypi.pub.build.mozilla.org/pub", - ], - "pip_index": False, - - "buildbot_json_path": "buildprops.json", - - "download_symbols": "ondemand", - "download_minidump_stackwalk": True, - "default_blob_upload_servers": [ - "https://blobupload.elasticbeanstalk.com", - ], - "blob_uploader_auth_file": os.path.join(os.getcwd(), "oauth.txt"), -} diff --git a/testing/mozharness/configs/b2g/mulet_config.py b/testing/mozharness/configs/b2g/mulet_config.py deleted file mode 100644 index 548dd68aa9..0000000000 --- a/testing/mozharness/configs/b2g/mulet_config.py +++ /dev/null @@ -1,26 +0,0 @@ -# Call --cfg b2g/generic_config.py before this config -config = { - "default_actions": [ - 'clobber', - 'read-buildbot-config', - 'pull', - 'download-and-extract', - 'create-virtualenv', - 'install', - 'run-tests', - ], - # testsuite options - "reftest_options": [ - "--mulet", - "--profile=%(gaia_profile)s", - "--appname=%(application)s", - "--total-chunks=%(total_chunks)s", - "--this-chunk=%(this_chunk)s", - "--symbols-path=%(symbols_path)s", - "--enable-oop", - "%(test_manifest)s" - ], - "run_file_names": { - "reftest": "runreftestb2g.py", - }, -} diff --git a/testing/mozharness/configs/b2g/taskcluster_emulator_automation.py b/testing/mozharness/configs/b2g/taskcluster_emulator_automation.py new file mode 100644 index 0000000000..fa0919bc65 --- /dev/null +++ b/testing/mozharness/configs/b2g/taskcluster_emulator_automation.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +import os.path +config = { + # mozharness options + "application": "b2g", + "tooltool_cache": "/builds/tooltool_cache", + + "find_links": [ + "http://pypi.pvt.build.mozilla.org/pub", + "http://pypi.pub.build.mozilla.org/pub", + ], + "pip_index": False, + + "default_actions": [ + 'clobber', + 'download-and-extract', + 'create-virtualenv', + 'install', + 'run-tests', + ], + "download_symbols": "ondemand", + # We bake this directly into the tester image now. + "download_minidump_stackwalk": False, + "default_blob_upload_servers": [ + "https://blobupload.elasticbeanstalk.com", + ], + "blob_uploader_auth_file": os.path.join(os.getcwd(), "oauth.txt"), + + "run_file_names": { + "jsreftest": "runreftestb2g.py", + "mochitest": "runtestsb2g.py", + "mochitest-chrome": "runtestsb2g.py", + "reftest": "runreftestb2g.py", + "crashtest": "runreftestb2g.py", + "xpcshell": "runtestsb2g.py", + "cppunittest": "remotecppunittests.py", + "marionette": "runtests.py" + }, + "suite_definitions": { + "cppunittest": { + "options": [ + "--dm_trans=adb", + "--symbols-path=%(symbols_path)s", + "--xre-path=%(xre_path)s", + "--addEnv", + "LD_LIBRARY_PATH=/vendor/lib:/system/lib:/system/b2g", + "--with-b2g-emulator=%(b2gpath)s", + "--emulator=%(emulator)s", + "." + ], + "run_filename": "remotecppunittests.py", + "testsdir": "cppunittest" + }, + "crashtest": { + "options": [ + "--adbpath=%(adbpath)s", + "--b2gpath=%(b2gpath)s", + "--emulator=%(emulator)s", + "--emulator-res=800x1000", + "--logdir=%(logcat_dir)s", + "--remote-webserver=%(remote_webserver)s", + "--ignore-window-size", + "--xre-path=%(xre_path)s", + "--symbols-path=%(symbols_path)s", + "--busybox=%(busybox)s", + "--total-chunks=%(total_chunks)s", + "--this-chunk=%(this_chunk)s", + "--suite=crashtest", + ], + "tests": ["tests/testing/crashtest/crashtests.list",], + "run_filename": "runreftestb2g.py", + "testsdir": "reftest" + }, + "jsreftest": { + "options": [ + "--adbpath=%(adbpath)s", + "--b2gpath=%(b2gpath)s", + "--emulator=%(emulator)s", + "--emulator-res=800x1000", + "--logdir=%(logcat_dir)s", + "--remote-webserver=%(remote_webserver)s", + "--ignore-window-size", + "--xre-path=%(xre_path)s", + "--symbols-path=%(symbols_path)s", + "--busybox=%(busybox)s", + "--total-chunks=%(total_chunks)s", + "--this-chunk=%(this_chunk)s", + "--extra-profile-file=jsreftest/tests/user.js", + ], + "tests": ["jsreftest/tests/jstests.list",], + "run_filename": "remotereftest.py", + "testsdir": "reftest" + }, + "mochitest": { + "options": [ + "--adbpath=%(adbpath)s", + "--b2gpath=%(b2gpath)s", + "--emulator=%(emulator)s", + "--logdir=%(logcat_dir)s", + "--remote-webserver=%(remote_webserver)s", + "--xre-path=%(xre_path)s", + "--utility-path=%(utility_path)s", + "--symbols-path=%(symbols_path)s", + "--busybox=%(busybox)s", + "--total-chunks=%(total_chunks)s", + "--this-chunk=%(this_chunk)s", + "--quiet", + "--log-raw=%(raw_log_file)s", + "--log-errorsummary=%(error_summary_file)s", + "--certificate-path=%(certificate_path)s", + "--screenshot-on-fail", + ], + "tests": ["%(test_path)s"], + "run_filename": "runtestsb2g.py", + "testsdir": "mochitest" + }, + "mochitest-chrome": { + "options": [ + "--adbpath=%(adbpath)s", + "--b2gpath=%(b2gpath)s", + "--emulator=%(emulator)s", + "--logdir=%(logcat_dir)s", + "--remote-webserver=%(remote_webserver)s", + "--xre-path=%(xre_path)s", + "--utility-path=%(utility_path)s", + "--symbols-path=%(symbols_path)s", + "--busybox=%(busybox)s", + "--total-chunks=%(total_chunks)s", + "--this-chunk=%(this_chunk)s", + "--quiet", + "--chrome", + "--log-raw=%(raw_log_file)s", + "--log-errorsummary=%(error_summary_file)s", + "--certificate-path=%(certificate_path)s", + "--screenshot-on-fail", + ], + "tests": ["%(test_path)s"], + "run_filename": "runtestsb2g.py", + "testsdir": "mochitest" + }, + "reftest": { + "options": [ + "--adbpath=%(adbpath)s", + "--b2gpath=%(b2gpath)s", + "--emulator=%(emulator)s", + "--emulator-res=800x1000", + "--logdir=%(logcat_dir)s", + "--remote-webserver=%(remote_webserver)s", + "--ignore-window-size", + "--xre-path=%(xre_path)s", + "--symbols-path=%(symbols_path)s", + "--busybox=%(busybox)s", + "--total-chunks=%(total_chunks)s", + "--this-chunk=%(this_chunk)s", + "--enable-oop", + ], + "tests": ["tests/layout/reftests/reftest.list",], + "run_filename": "runreftestsb2g.py", + "testsdir": "reftest" + }, + "xpcshell": { + "options": [ + "--adbpath=%(adbpath)s", + "--b2gpath=%(b2gpath)s", + "--emulator=%(emulator)s", + "--logdir=%(logcat_dir)s", + "--manifest=tests/xpcshell.ini", + "--use-device-libs", + "--testing-modules-dir=%(modules_dir)s", + "--symbols-path=%(symbols_path)s", + "--busybox=%(busybox)s", + "--total-chunks=%(total_chunks)s", + "--this-chunk=%(this_chunk)s", + "--log-raw=%(raw_log_file)s", + "--log-errorsummary=%(error_summary_file)s", + ], + "run_filename": "runtestsb2g.py", + "testsdir": "xpcshell" + }, + "marionette": { + "options": [ + "--type=b2g", + "--log-raw=%(raw_log_file)s", + "--log-errorsummary=%(error_summary_file)s", + "--symbols-path=%(symbols_path)s", + "--logcat-dir=%(logcat_dir)s", + "--emulator=%(emulator)s", + "--homedir=%(homedir)s" + ], + "run_filename": "runtests.py", + "testsdir": "marionette/harness/marionette" + } + }, + "vcs_output_timeout": 1760, +} \ No newline at end of file diff --git a/testing/mozharness/configs/b2g/taskcluster_gaia_integration.py b/testing/mozharness/configs/b2g/taskcluster_gaia_integration.py new file mode 100644 index 0000000000..7776a593f8 --- /dev/null +++ b/testing/mozharness/configs/b2g/taskcluster_gaia_integration.py @@ -0,0 +1,28 @@ +# This is a template config file for b2g emulator unittest testing +import platform + +HG_SHARE_BASE_DIR = "/builds/hg-shared" + +config = { + # mozharness script options + "vcs_share_base": HG_SHARE_BASE_DIR, + + "find_links": [ + "http://pypi.pvt.build.mozilla.org/pub", + "http://pypi.pub.build.mozilla.org/pub", + ], + "pip_index": False, + + "download_symbols": "ondemand", + # We bake this directly into the tester image now. + "download_minidump_stackwalk": False, + + "default_actions": [ + 'clobber', + 'download-and-extract', + 'create-virtualenv', + 'install', + 'run-tests', + ], + "vcs_output_timeout": 1760, +} diff --git a/testing/mozharness/configs/b2g/taskcluster_gaia_unit_production.py b/testing/mozharness/configs/b2g/taskcluster_gaia_unit_production.py new file mode 100644 index 0000000000..c6876dc0fa --- /dev/null +++ b/testing/mozharness/configs/b2g/taskcluster_gaia_unit_production.py @@ -0,0 +1,32 @@ +# This is a template config file for b2g emulator unittest testing +import platform +import os + +HG_SHARE_BASE_DIR = "/builds/hg-shared" + +config = { + # mozharness script options + "vcs_share_base": HG_SHARE_BASE_DIR, + + "find_links": [ + "http://pypi.pvt.build.mozilla.org/pub", + "http://pypi.pub.build.mozilla.org/pub", + ], + "pip_index": False, + + "default_actions": [ + 'clobber', + 'download-and-extract', + 'create-virtualenv', + 'install', + 'run-tests', + ], + "download_symbols": "ondemand", + # We bake this directly into the tester image now. + "download_minidump_stackwalk": False, + "default_blob_upload_servers": [ + "https://blobupload.elasticbeanstalk.com", + ], + "blob_uploader_auth_file": os.path.join(os.getcwd(), "oauth.txt"), + "vcs_output_timeout": 1760, +} diff --git a/testing/mozharness/configs/b2g/taskcluster_mulet_automation.py b/testing/mozharness/configs/b2g/taskcluster_mulet_automation.py new file mode 100644 index 0000000000..e8f2978404 --- /dev/null +++ b/testing/mozharness/configs/b2g/taskcluster_mulet_automation.py @@ -0,0 +1,45 @@ +# This is a template config file for mulet unittest testing +import os + +config = { + "find_links": [ + "http://pypi.pvt.build.mozilla.org/pub", + "http://pypi.pub.build.mozilla.org/pub", + ], + "pip_index": False, + + "download_symbols": "ondemand", + # We bake this directly into the tester image now. + "download_minidump_stackwalk": False, + "default_blob_upload_servers": [ + "https://blobupload.elasticbeanstalk.com", + ], + "blob_uploader_auth_file": os.path.join(os.getcwd(), "oauth.txt"), + + "default_actions": [ + 'clobber', + 'download-and-extract', + 'create-virtualenv', + 'install', + 'run-tests', + ], + + # testsuite options + "suite_definitions": { + "reftest": { + "options": [ + "--mulet", + "--profile=%(gaia_profile)s", + "--appname=%(application)s", + "--total-chunks=%(total_chunks)s", + "--this-chunk=%(this_chunk)s", + "--symbols-path=%(symbols_path)s", + "--enable-oop", + ], + "tests": ["%(test_manifest)s"] + } + }, + "run_file_names": { + "reftest": "runreftestb2g.py", + }, +} diff --git a/testing/mozharness/configs/marionette/automation_emulator_config.py b/testing/mozharness/configs/marionette/automation_emulator_config.py index 346d4f4b4d..a219f73e8f 100644 --- a/testing/mozharness/configs/marionette/automation_emulator_config.py +++ b/testing/mozharness/configs/marionette/automation_emulator_config.py @@ -1,4 +1,6 @@ # This is a template config file for marionette production. +# TODO: This could be removed after B2G ICS emulator buildbot builds is turned +# off, Bug 1209180. import os diff --git a/testing/mozharness/mozharness/mozilla/testing/unittest.py b/testing/mozharness/mozharness/mozilla/testing/unittest.py index 7ce985390f..b51003c125 100755 --- a/testing/mozharness/mozharness/mozilla/testing/unittest.py +++ b/testing/mozharness/mozharness/mozilla/testing/unittest.py @@ -9,7 +9,7 @@ import os import re from mozharness.mozilla.testing.errors import TinderBoxPrintRe -from mozharness.base.log import OutputParser, WARNING, INFO, CRITICAL +from mozharness.base.log import OutputParser, WARNING, INFO, CRITICAL, ERROR from mozharness.mozilla.buildbot import TBPL_WARNING, TBPL_FAILURE, TBPL_RETRY from mozharness.mozilla.buildbot import TBPL_SUCCESS, TBPL_WORST_LEVEL_TUPLE @@ -50,6 +50,8 @@ class TestSummaryOutputParserHelper(OutputParser): self.passed = 0 self.todo = 0 self.last_line = None + self.tbpl_status = TBPL_SUCCESS + self.worst_log_level = INFO super(TestSummaryOutputParserHelper, self).__init__(**kwargs) def parse_single_line(self, line): @@ -63,7 +65,18 @@ class TestSummaryOutputParserHelper(OutputParser): # ignore bad values pass - def evaluate_parser(self): + def evaluate_parser(self, return_code, success_codes=None): + if return_code == 0 and self.passed > 0 and self.failed == 0: + self.tbpl_status = TBPL_SUCCESS + elif return_code == 10 and self.failed > 0: + self.tbpl_status = TBPL_WARNING + else: + self.tbpl_status = TBPL_FAILURE + self.worst_log_level = ERROR + + return (self.tbpl_status, self.worst_log_level) + + def print_summary(self, suite_name): # generate the TinderboxPrint line for TBPL emphasize_fail_text = '%s' failed = "0" @@ -74,10 +87,11 @@ class TestSummaryOutputParserHelper(OutputParser): failed = emphasize_fail_text % str(self.failed) self.tsummary = "%d/%s/%d" % (self.passed, failed, self.todo) - def print_summary(self, suite_name): - self.evaluate_parser() self.info("TinderboxPrint: %s: %s\n" % (suite_name, self.tsummary)) + def append_tinderboxprint_line(self, suite_name): + self.print_summary(suite_name) + class DesktopUnittestOutputParser(OutputParser): """ diff --git a/testing/mozharness/scripts/b2g_emulator_unittest.py b/testing/mozharness/scripts/b2g_emulator_unittest.py index 70a0c2cb05..63b90369c0 100755 --- a/testing/mozharness/scripts/b2g_emulator_unittest.py +++ b/testing/mozharness/scripts/b2g_emulator_unittest.py @@ -23,11 +23,12 @@ from mozharness.base.vcs.vcsbase import VCSMixin from mozharness.mozilla.blob_upload import BlobUploadMixin, blobupload_config_options from mozharness.mozilla.testing.errors import LogcatErrorList from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options +from mozharness.mozilla.testing.unittest import TestSummaryOutputParserHelper from mozharness.mozilla.buildbot import TBPL_SUCCESS class B2GEmulatorTest(TestingMixin, VCSMixin, BaseScript, BlobUploadMixin): - test_suites = ('jsreftest', 'reftest', 'mochitest', 'mochitest-chrome', 'xpcshell', 'crashtest', 'cppunittest') + test_suites = ('jsreftest', 'reftest', 'mochitest', 'mochitest-chrome', 'xpcshell', 'crashtest', 'cppunittest', 'marionette') config_options = [[ ["--type"], {"action": "store", @@ -182,6 +183,8 @@ class B2GEmulatorTest(TestingMixin, VCSMixin, BaseScript, BlobUploadMixin): dirs['abs_test_install_dir'], 'xpcshell') dirs['abs_cppunittest_dir'] = os.path.join( dirs['abs_test_install_dir'], 'cppunittest') + dirs['abs_marionette_dir'] = os.path.join( + dirs['abs_test_install_dir'], 'marionette', 'marionette') for key in dirs.keys(): if key not in abs_dirs: abs_dirs[key] = dirs[key] @@ -288,19 +291,15 @@ class B2GEmulatorTest(TestingMixin, VCSMixin, BaseScript, BlobUploadMixin): def preflight_run_tests(self): super(B2GEmulatorTest, self).preflight_run_tests() suite = self.config['test_suite'] + dirs = self.query_abs_dirs() # set default test manifest by suite if none specified - if not self.test_manifest: - if suite == 'reftest': - self.test_manifest = os.path.join('tests', 'layout', - 'reftests', 'reftest.list') - elif suite == 'xpcshell': - self.test_manifest = os.path.join('tests', 'xpcshell_b2g.ini') - elif suite == 'crashtest': - self.test_manifest = os.path.join('tests', 'testing', - 'crashtest', 'crashtests.list') - elif suite == 'jsreftest': - self.test_manifest = os.path.join('jsreftest', 'tests', - 'jstests.list') + if self.test_manifest: + if suite == 'marionette': + self.test_manifest = os.path.join(dirs['abs_test_install_dir'], + 'marionette', 'tests', + 'testing', 'marionette', + 'client', 'marionette', + 'tests', self.test_manifest) if not os.path.isfile(self.adb_path): self.fatal("The adb binary '%s' is not a valid file!" % self.adb_path) @@ -356,10 +355,15 @@ class B2GEmulatorTest(TestingMixin, VCSMixin, BaseScript, BlobUploadMixin): env = self.query_env(partial_env=env) success_codes = self._get_success_codes(suite_name) - parser = self.get_test_output_parser(suite_name, - config=self.config, - log_obj=self.log_obj, - error_list=error_list) + if suite_name == "marionette": + parser = TestSummaryOutputParserHelper(config=self.config, + log_obj=self.log_obj, + error_list=self.error_list) + else: + parser = self.get_test_output_parser(suite_name, + config=self.config, + log_obj=self.log_obj, + error_list=error_list) return_code = self.run_command(cmd, cwd=cwd, env=env, output_timeout=1000, output_parser=parser, diff --git a/testing/mozharness/scripts/marionette.py b/testing/mozharness/scripts/marionette.py index 856d5eebc4..278472792f 100755 --- a/testing/mozharness/scripts/marionette.py +++ b/testing/mozharness/scripts/marionette.py @@ -26,6 +26,8 @@ from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_opt from mozharness.mozilla.testing.unittest import TestSummaryOutputParserHelper from mozharness.mozilla.structuredlog import StructuredOutputParser +# TODO: we could remove emulator specific code after B2G ICS emulator buildbot +# builds is turned off, Bug 1209180. class MarionetteTest(TestingMixin, MercurialScript, BlobUploadMixin, TransferMixin, GaiaMixin): config_options = [[ ["--application"], @@ -202,7 +204,7 @@ class MarionetteTest(TestingMixin, MercurialScript, BlobUploadMixin, TransferMix dirs['abs_test_install_dir'], 'marionette', 'marionette') dirs['abs_marionette_tests_dir'] = os.path.join( dirs['abs_test_install_dir'], 'marionette', 'tests', 'testing', - 'marionette', 'client', 'marionette', 'tests') + 'marionette', 'harness', 'marionette', 'tests') dirs['abs_gecko_dir'] = os.path.join( abs_dirs['abs_work_dir'], 'gecko') dirs['abs_emulator_dir'] = os.path.join( diff --git a/testing/taskcluster/tasks/tests/b2g_build_test.yml b/testing/taskcluster/tasks/tests/b2g_build_test.yml index 8bedd43a71..8c593ff9b1 100644 --- a/testing/taskcluster/tasks/tests/b2g_build_test.yml +++ b/testing/taskcluster/tasks/tests/b2g_build_test.yml @@ -1,9 +1,9 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: - name: '[TC] Gaia Build Test' + name: '[TC] - Gaia Build Test' description: Gaia Build Test test run payload: @@ -17,7 +17,7 @@ task: --config-file ./mozharness_configs/gaia_integration_override.py --config-file ./mozharness_configs/remove_executables.py --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --download-symbols ondemand --gaia-repo https://hg.mozilla.org/integration/gaia-central --gaia-dir /home/worker @@ -29,6 +29,10 @@ task: expires: '{{#from_now}}1 year{{/from_now}}' extra: + treeherderEnv: + - production + - staging treeherder: + groupSymbol: "?" symbol: 'Gb' productName: b2g diff --git a/testing/taskcluster/tasks/tests/b2g_build_unit.yml b/testing/taskcluster/tasks/tests/b2g_build_unit.yml index 38e1122471..2c715aba94 100644 --- a/testing/taskcluster/tasks/tests/b2g_build_unit.yml +++ b/testing/taskcluster/tasks/tests/b2g_build_unit.yml @@ -1,9 +1,9 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: - name: '[TC] Gaia Build Unit Test' + name: '[TC] - Gaia Build Unit Test' description: Gaia Build Unit Test payload: @@ -18,7 +18,7 @@ task: --config-file ./mozharness_configs/remove_executables.py --download-symbols ondemand --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --gaia-repo https://hg.mozilla.org/integration/gaia-central --gaia-dir /home/worker artifacts: @@ -28,6 +28,10 @@ task: expires: '{{#from_now}}1 year{{/from_now}}' extra: + treeherderEnv: + - production + - staging treeherder: + groupSymbol: "?" symbol: 'Gbu' productName: b2g diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_cpp_unit.yml b/testing/taskcluster/tasks/tests/b2g_emulator_cpp_unit.yml index 481d2c54eb..4f7b5c77d2 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_cpp_unit.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_cpp_unit.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] CPP Unit Tests' @@ -12,13 +12,10 @@ task: - entrypoint - > python ./mozharness/scripts/b2g_emulator_unittest.py - --config-file ./mozharness/configs/b2g/emulator_automation_config.py - --config-file ./mozharness_configs/emulator_override.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py --test-suite cppunittest --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip artifacts: 'public/build': diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_crashtest.yml b/testing/taskcluster/tasks/tests/b2g_emulator_crashtest.yml index 584f95a0cf..96e54c9205 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_crashtest.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_crashtest.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Crashtest' @@ -13,13 +13,10 @@ task: - entrypoint - > python ./mozharness/scripts/b2g_emulator_unittest.py - --config-file ./mozharness/configs/b2g/emulator_automation_config.py - --config-file ./mozharness_configs/emulator_override.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py --test-suite crashtest --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip --this-chunk {{chunk}} --total-chunk {{total_chunks}} diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_js_reftest.yml b/testing/taskcluster/tasks/tests/b2g_emulator_js_reftest.yml index 78794aa6bb..e0008a4586 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_js_reftest.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_js_reftest.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] JSReftest' @@ -12,13 +12,10 @@ task: - entrypoint - > python ./mozharness/scripts/b2g_emulator_unittest.py - --config-file ./mozharness/configs/b2g/emulator_automation_config.py - --config-file ./mozharness_configs/emulator_override.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py --test-suite jsreftest --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip --this-chunk {{chunk}} --total-chunk {{total_chunks}} diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_marionette.yml b/testing/taskcluster/tasks/tests/b2g_emulator_marionette.yml index b0923f3ac2..b11a728bf4 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_marionette.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_marionette.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Marionette Framework Unit Tests' @@ -11,13 +11,13 @@ task: command: - entrypoint - > - python ./mozharness/scripts/marionette.py - --no-read-buildbot-config - --config-file ./mozharness/configs/marionette/automation_emulator_config.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + python ./mozharness/scripts/b2g_emulator_unittest.py + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py + --test-suite marionette + --test-manifest unit-tests.ini --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} + --xre-url https://api.pub.build.mozilla.org/tooltool/sha512/cefa8c00db04969d3a50e2a5509bd4ea1dc17d256a651a9518cb28dad72e87a1dbbcd3c88ef770be0edf0ab73d2d73925140df93618ffb7fab81b789d312f547 artifacts: 'public/build': type: directory diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_marionette_webapi.yml b/testing/taskcluster/tasks/tests/b2g_emulator_marionette_webapi.yml index 8f156fd8af..c58b9a0f50 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_marionette_webapi.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_marionette_webapi.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Marionette WebAPI Tests' @@ -11,14 +11,13 @@ task: command: - entrypoint - > - python ./mozharness/scripts/marionette.py - --no-read-buildbot-config - --config-file ./mozharness/configs/marionette/automation_emulator_config.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + python ./mozharness/scripts/b2g_emulator_unittest.py + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py + --test-suite marionette --test-manifest webapi-tests.ini --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} + --xre-url https://api.pub.build.mozilla.org/tooltool/sha512/cefa8c00db04969d3a50e2a5509bd4ea1dc17d256a651a9518cb28dad72e87a1dbbcd3c88ef770be0edf0ab73d2d73925140df93618ffb7fab81b789d312f547 artifacts: 'public/build': type: directory diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_mochitest.yml b/testing/taskcluster/tasks/tests/b2g_emulator_mochitest.yml index a3c402ba76..270bfaf833 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_mochitest.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_mochitest.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Mochitest' @@ -13,14 +13,10 @@ task: - entrypoint - > python ./mozharness/scripts/b2g_emulator_unittest.py - --config-file ./mozharness/configs/b2g/emulator_automation_config.py - --config-file ./mozharness_configs/gaia_integration_override.py - --config-file ./mozharness_configs/emulator_override.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py --test-suite mochitest --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip --this-chunk {{chunk}} --total-chunk {{total_chunks}} diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_mochitest_media.yml b/testing/taskcluster/tasks/tests/b2g_emulator_mochitest_media.yml index 8d5b34c76e..8a94f88da2 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_mochitest_media.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_mochitest_media.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Mochitest' @@ -12,14 +12,10 @@ task: - entrypoint - > python ./mozharness/scripts/b2g_emulator_unittest.py - --config-file ./mozharness/configs/b2g/emulator_automation_config.py - --config-file ./mozharness_configs/gaia_integration_override.py - --config-file ./mozharness_configs/emulator_override.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py --test-suite mochitest --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip dom/media/tests artifacts: diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_reftest.yml b/testing/taskcluster/tasks/tests/b2g_emulator_reftest.yml index 89cf19d58f..d9a7f5df3d 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_reftest.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_reftest.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Reftest' @@ -12,13 +12,10 @@ task: - entrypoint - > python ./mozharness/scripts/b2g_emulator_unittest.py - --config-file ./mozharness/configs/b2g/emulator_automation_config.py - --config-file ./mozharness_configs/emulator_override.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py --test-suite reftest --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip --this-chunk {{chunk}} --total-chunk {{total_chunks}} diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_xpcshell.yml b/testing/taskcluster/tasks/tests/b2g_emulator_xpcshell.yml index 2a9237763e..0576d9f3b8 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_xpcshell.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_xpcshell.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] XPCShell' @@ -19,7 +19,7 @@ task: --download-symbols ondemand --test-suite xpcshell --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip artifacts: 'public/build': diff --git a/testing/taskcluster/tasks/tests/b2g_emulator_xpcshell_chunked.yml b/testing/taskcluster/tasks/tests/b2g_emulator_xpcshell_chunked.yml index 602fe9844a..04b83fd35f 100644 --- a/testing/taskcluster/tasks/tests/b2g_emulator_xpcshell_chunked.yml +++ b/testing/taskcluster/tasks/tests/b2g_emulator_xpcshell_chunked.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] XPCShell' @@ -13,13 +13,10 @@ task: - entrypoint - > python ./mozharness/scripts/b2g_emulator_unittest.py - --config-file ./mozharness/configs/b2g/emulator_automation_config.py - --config-file ./mozharness_configs/emulator_override.py - --config-file ./mozharness_configs/remove_executables.py - --download-symbols ondemand + --config-file ./mozharness/configs/b2g/taskcluster_emulator_automation.py --test-suite xpcshell --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip --this-chunk {{chunk}} --total-chunk {{total_chunks}} diff --git a/testing/taskcluster/tasks/tests/b2g_gaia_js_integration_tests.yml b/testing/taskcluster/tasks/tests/b2g_gaia_js_integration_tests.yml index 00d2201060..ed503c5f34 100644 --- a/testing/taskcluster/tasks/tests/b2g_gaia_js_integration_tests.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_js_integration_tests.yml @@ -1,9 +1,9 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: - name: '[TC] Gaia JS Integration Test' + name: '[TC] - Gaia JS Integration Test' description: Gaia JS Integration Test run {{chunk}} payload: @@ -17,7 +17,7 @@ task: --config-file ./mozharness_configs/gaia_integration_override.py --config-file ./mozharness_configs/remove_executables.py --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --download-symbols ondemand --total-chunk {{total_chunks}} --this-chunk {{chunk}} @@ -31,9 +31,12 @@ task: extra: chunks: - total: 20 + total: 30 + treeherderEnv: + - production + - staging treeherder: groupName: Gaia JS Integration Test - groupSymbol: tc-Gij + groupSymbol: Gij symbol: '{{chunk}}' productName: b2g diff --git a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_accessibility.yml b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_accessibility.yml index 38ccc14608..10f2735977 100644 --- a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_accessibility.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_accessibility.yml @@ -3,7 +3,7 @@ $inherits: from: 'tasks/test.yml' task: metadata: - name: '[TC] Gaia Python Accessibility Integration Tests' + name: '[TC] - Gaia Python Accessibility Integration Tests' description: Gaia Python Accessibility Integration Tests run {{chunk}} payload: @@ -17,7 +17,7 @@ task: --config-file ./mozharness_configs/gaia_integration_override.py --config-file ./mozharness_configs/remove_executables.py --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --download-symbols ondemand --gip-suite accessibility --gaia-repo https://hg.mozilla.org/integration/gaia-central @@ -30,8 +30,11 @@ task: expires: '{{#from_now}}1 year{{/from_now}}' extra: + treeherderEnv: + - production + - staging treeherder: groupName: Gaia Python Integration Tests - groupSymbol: tc-Gip + groupSymbol: Gip symbol: 'a' productName: b2g diff --git a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_functional.yml b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_functional.yml index 04cdc5de20..d7abca3ec4 100644 --- a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_functional.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_functional.yml @@ -3,7 +3,7 @@ $inherits: from: 'tasks/test.yml' task: metadata: - name: '[TC] Gaia Python Functional Integration Tests' + name: '[TC] - Gaia Python Functional Integration Tests' description: Gaia Python Functional Integration Tests run {{chunk}} payload: @@ -17,7 +17,7 @@ task: --config-file ./mozharness_configs/gaia_integration_override.py --config-file ./mozharness_configs/remove_executables.py --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --download-symbols ondemand --gip-suite functional --total-chunk {{total_chunks}} @@ -34,9 +34,11 @@ task: extra: chunks: total: 3 - + treeherderEnv: + - production + - staging treeherder: groupName: Gaia Python Integration Tests - groupSymbol: tc-Gip + groupSymbol: Gip symbol: 'f{{chunk}}' productName: b2g diff --git a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_unit.yml b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_unit.yml index 34892a7a9c..1c6e58721a 100644 --- a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_unit.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_unit.yml @@ -3,7 +3,7 @@ $inherits: from: 'tasks/test.yml' task: metadata: - name: '[TC] Gaia Python Integration Unit Tests' + name: '[TC] - Gaia Python Integration Unit Tests' description: Gaia Python Integration Unit Tests run {{chunk}} payload: @@ -17,7 +17,7 @@ task: --config-file ./mozharness_configs/gaia_integration_override.py --config-file ./mozharness_configs/remove_executables.py --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --download-symbols ondemand --gip-suite unit --gaia-repo https://hg.mozilla.org/integration/gaia-central @@ -30,8 +30,11 @@ task: expires: '{{#from_now}}1 year{{/from_now}}' extra: + treeherderEnv: + - production + - staging treeherder: groupName: Gaia Python Integration Tests - groupSymbol: tc-Gip + groupSymbol: Gip symbol: 'u' productName: b2g diff --git a/testing/taskcluster/tasks/tests/b2g_gaia_unit.yml b/testing/taskcluster/tasks/tests/b2g_gaia_unit.yml index 544b9b3e79..68e6f3bb0e 100644 --- a/testing/taskcluster/tasks/tests/b2g_gaia_unit.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_unit.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Gaia Unit Test' @@ -18,7 +18,7 @@ task: --config-file ./mozharness_configs/remove_executables.py --download-symbols ondemand --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --gaia-repo https://hg.mozilla.org/integration/gaia-central --gaia-dir /home/worker --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip @@ -29,6 +29,10 @@ task: expires: '{{#from_now}}1 year{{/from_now}}' extra: + treeherderEnv: + - production + - staging treeherder: symbol: 'Gu' + groupSymbol: "?" productName: b2g diff --git a/testing/taskcluster/tasks/tests/b2g_gaia_unit_oop.yml b/testing/taskcluster/tasks/tests/b2g_gaia_unit_oop.yml index d622fffc40..5c67720edc 100644 --- a/testing/taskcluster/tasks/tests/b2g_gaia_unit_oop.yml +++ b/testing/taskcluster/tasks/tests/b2g_gaia_unit_oop.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Gaia Unit Test OOP' @@ -12,12 +12,8 @@ task: - ./bin/pull_gaia.sh && - > python ./mozharness/scripts/gaia_unit.py - --no-read-buildbot-config - --config-file ./mozharness/configs/b2g/gaia_unit_production_config.py - --config-file ./mozharness_configs/gaia_integration_override.py - --config-file ./mozharness_configs/remove_executables.py + --config-file ./mozharness/configs/b2g/taskcluster_gaia_unit_production.py --browser-arg -oop - --download-symbols ondemand --installer-url {{build_url}} --test-url {{tests_url}} --gaia-repo https://hg.mozilla.org/integration/gaia-central diff --git a/testing/taskcluster/tasks/tests/b2g_gip_oop.yml b/testing/taskcluster/tasks/tests/b2g_gip_oop.yml index 59b4f6f2ca..2cc74b48b4 100644 --- a/testing/taskcluster/tasks/tests/b2g_gip_oop.yml +++ b/testing/taskcluster/tasks/tests/b2g_gip_oop.yml @@ -18,7 +18,7 @@ task: --config-file ./mozharness_configs/remove_executables.py --app-arg -oop --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --download-symbols ondemand --gaia-repo https://hg.mozilla.org/integration/gaia-central --gaia-dir /home/worker diff --git a/testing/taskcluster/tasks/tests/b2g_linter.yml b/testing/taskcluster/tasks/tests/b2g_linter.yml index 3d2f4177a4..e1fbc53b18 100644 --- a/testing/taskcluster/tasks/tests/b2g_linter.yml +++ b/testing/taskcluster/tasks/tests/b2g_linter.yml @@ -1,9 +1,9 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: - name: '[TC] Gaia Linter' + name: '[TC] - Gaia Linter' description: Linter Test payload: @@ -17,7 +17,7 @@ task: --config-file ./mozharness_configs/gaia_integration_override.py --download-symbols ondemand --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --gaia-repo https://hg.mozilla.org/integration/gaia-central --gaia-dir /home/worker artifacts: @@ -27,6 +27,10 @@ task: expires: '{{#from_now}}1 year{{/from_now}}' extra: + treeherderEnv: + - production + - staging treeherder: + groupSymbol: "?" symbol: 'Li' productName: 'b2g' diff --git a/testing/taskcluster/tasks/tests/b2g_mochitest.yml b/testing/taskcluster/tasks/tests/b2g_mochitest.yml index 4ba18aaa1a..20464becc2 100644 --- a/testing/taskcluster/tasks/tests/b2g_mochitest.yml +++ b/testing/taskcluster/tasks/tests/b2g_mochitest.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] B2G Mochitests {{chunk}}' diff --git a/testing/taskcluster/tasks/tests/b2g_mochitest_oop.yml b/testing/taskcluster/tasks/tests/b2g_mochitest_oop.yml index 125978c05f..c523ca3ad4 100644 --- a/testing/taskcluster/tasks/tests/b2g_mochitest_oop.yml +++ b/testing/taskcluster/tasks/tests/b2g_mochitest_oop.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Mochitest OOP' diff --git a/testing/taskcluster/tasks/tests/b2g_reftests.yml b/testing/taskcluster/tasks/tests/b2g_reftests.yml index fb65fa414f..e77c4c16b4 100644 --- a/testing/taskcluster/tasks/tests/b2g_reftests.yml +++ b/testing/taskcluster/tasks/tests/b2g_reftests.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Reftest' diff --git a/testing/taskcluster/tasks/tests/b2g_reftests_sanity_oop.yml b/testing/taskcluster/tasks/tests/b2g_reftests_sanity_oop.yml index e1a5a65207..1de9a9d26a 100644 --- a/testing/taskcluster/tasks/tests/b2g_reftests_sanity_oop.yml +++ b/testing/taskcluster/tasks/tests/b2g_reftests_sanity_oop.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Reftest Sanity OOP' diff --git a/testing/taskcluster/tasks/tests/b2g_unittest_base.yml b/testing/taskcluster/tasks/tests/b2g_unittest_base.yml new file mode 100644 index 0000000000..bf8afdd512 --- /dev/null +++ b/testing/taskcluster/tasks/tests/b2g_unittest_base.yml @@ -0,0 +1,23 @@ +--- +$inherits: + from: 'tasks/test.yml' +task: + scopes: + - 'docker-worker:cache:tc-vcs' + - 'docker-worker:cache:linux-cache' + - 'docker-worker:capability:device:loopbackVideo' + - 'docker-worker:capability:device:loopbackAudio' + payload: + cache: + # So pip installs are cached... + linux-cache: /home/worker/.cache + tc-vcs: '/home/worker/.tc-vcs' + capabilities: + devices: + loopbackVideo: true + loopbackAudio: true + env: + GAIA_HEAD_REPOSITORY: '{{{gaia_head_repository}}}' + GAIA_BASE_REPOSITORY: '{{{gaia_base_repository}}}' + GAIA_REF: '{{{gaia_ref}}}' + GAIA_REV: '{{{gaia_rev}}}' diff --git a/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_functional.yml b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_functional.yml index 1602888f31..0febf1c7f2 100644 --- a/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_functional.yml +++ b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_functional.yml @@ -5,11 +5,16 @@ task: metadata: name: '[TC] Gaia Python Functional Integration Tests' description: Gaia Python Functional Integration Tests run {{chunk}} - workerType: testdroid-device + workerType: flame-kk-1-sim payload: - env: - DEVICE_CAPABILITIES: '{"type":"flame","memory":"319","sims": "1","build":"{{{img_url}}}"}' + capabilities: + devices: + phone: + type: 'flame' + memory: '319' + sims: '1' + build: '{{{img_url}}}' features: testdroidProxy: true maxRunTime: 7200 diff --git a/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_functional_dsds.yml b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_functional_dsds.yml index a4b9f5e463..10bf2f8e25 100644 --- a/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_functional_dsds.yml +++ b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_functional_dsds.yml @@ -5,11 +5,16 @@ task: metadata: name: '[TC] Gaia Python Integration Functional DSDS Tests' description: Gaia Python Integration Functional DSDS Tests run - workerType: testdroid-device + workerType: flame-kk-2-sim payload: - env: - DEVICE_CAPABILITIES: '{"type":"flame","memory":"319","sims": "2","build":"{{{img_url}}}"}' + capabilities: + devices: + phone: + type: 'flame' + memory: '319' + sims: '1' + build: '{{{img_url}}}' features: testdroidProxy: true maxRunTime: 7200 diff --git a/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_sanity.yml b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_sanity.yml index ddffc8b170..e3bafc6b86 100644 --- a/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_sanity.yml +++ b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_sanity.yml @@ -5,12 +5,17 @@ task: metadata: name: '[TC] Gaia Python Integration Sanity Tests' description: Gaia Python Integration Sanity Tests run - workerType: testdroid-device + workerType: flame-kk-1-sim retries: 0 payload: - env: - DEVICE_CAPABILITIES: '{"type":"flame","memory":"319","sims": "1","build":"{{{img_url}}}"}' + capabilities: + devices: + phone: + type: 'flame' + memory: '319' + sims: '1' + build: '{{{img_url}}}' features: testdroidProxy: true maxRunTime: 7200 diff --git a/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_unit.yml b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_unit.yml index d0496b7b51..42bb8238dc 100644 --- a/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_unit.yml +++ b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_unit.yml @@ -5,11 +5,16 @@ task: metadata: name: '[TC] Gaia Python Integration Unit Tests' description: Gaia Python Integration Tests run - workerType: testdroid-device + workerType: flame-kk-0-sim payload: - env: - DEVICE_CAPABILITIES: '{"type":"flame","memory":"319","sims": "0","build":"{{{img_url}}}"}' + capabilities: + devices: + phone: + type: 'flame' + memory: '319' + sims: '0' + build: '{{{img_url}}}' features: testdroidProxy: true maxRunTime: 7200 diff --git a/testing/taskcluster/tasks/tests/fx_linux64_mochitest_bc.yml b/testing/taskcluster/tasks/tests/fx_linux64_mochitest_bc.yml new file mode 100644 index 0000000000..194ada935b --- /dev/null +++ b/testing/taskcluster/tasks/tests/fx_linux64_mochitest_bc.yml @@ -0,0 +1,26 @@ +--- +$inherits: + from: 'tasks/tests/fx_unittest_base.yml' +task: + scopes: + - 'docker-worker:capability:device:loopbackVideo' + - 'docker-worker:capability:device:loopbackAudio' + metadata: + name: '[TC] Linux64 mochitest-browser-chrome M(bc{{chunk}})' + description: Mochitest browser-chrome run {{chunk}} + payload: + capabilities: + devices: + loopbackVideo: true + loopbackAudio: true + extra: + chunks: + total: 7 + suite: + name: mochitest + flavor: browser-chrome-chunked + treeherder: + groupName: Desktop mochitests + groupSymbol: tc-M + symbol: bc{{chunk}} + diff --git a/testing/taskcluster/tasks/tests/fx_linux64_mochitest_dt.yml b/testing/taskcluster/tasks/tests/fx_linux64_mochitest_dt.yml new file mode 100644 index 0000000000..616c18a888 --- /dev/null +++ b/testing/taskcluster/tasks/tests/fx_linux64_mochitest_dt.yml @@ -0,0 +1,25 @@ +--- +$inherits: + from: 'tasks/tests/fx_unittest_base.yml' +task: + scopes: + - 'docker-worker:capability:device:loopbackVideo' + - 'docker-worker:capability:device:loopbackAudio' + metadata: + name: '[TC] Linux64 mochitest-devtools-chrome M(dt{{chunk}})' + description: Mochitest devtools-chrome run {{chunk}} + payload: + capabilities: + devices: + loopbackVideo: true + loopbackAudio: true + extra: + chunks: + total: 2 + suite: + name: mochitest + flavor: mochitest-devtools-chrome + treeherder: + groupName: Desktop mochitests + groupSymbol: tc-M + symbol: dt{{chunk}} diff --git a/testing/taskcluster/tasks/tests/fx_linux64_mochitest_gl.yml b/testing/taskcluster/tasks/tests/fx_linux64_mochitest_gl.yml new file mode 100644 index 0000000000..1a36fc3c33 --- /dev/null +++ b/testing/taskcluster/tasks/tests/fx_linux64_mochitest_gl.yml @@ -0,0 +1,22 @@ +$inherits: + from: 'tasks/tests/fx_unittest_base.yml' +task: + scopes: + - 'docker-worker:capability:device:loopbackVideo' + - 'docker-worker:capability:device:loopbackAudio' + metadata: + name: '[TC] Linux64 mochitest-gl M(gl)' + description: Mochitest webgl run + payload: + capabilities: + devices: + loopbackVideo: true + loopbackAudio: true + extra: + suite: + name: mochitest + flavor: mochitest-gl + treeherder: + groupName: Desktop mochitests + groupSymbol: tc-M + symbol: gl diff --git a/testing/taskcluster/tasks/tests/fx_linux64_mochitest_plain.yml b/testing/taskcluster/tasks/tests/fx_linux64_mochitest_plain.yml new file mode 100644 index 0000000000..ff89911bf2 --- /dev/null +++ b/testing/taskcluster/tasks/tests/fx_linux64_mochitest_plain.yml @@ -0,0 +1,25 @@ +--- +$inherits: + from: 'tasks/tests/fx_unittest_base.yml' +task: + scopes: + - 'docker-worker:capability:device:loopbackVideo' + - 'docker-worker:capability:device:loopbackAudio' + metadata: + name: '[TC] Linux64 mochitest-plain {{chunk}}' + description: Mochitest plain run {{chunk}} + payload: + capabilities: + devices: + loopbackVideo: true + loopbackAudio: true + extra: + chunks: + total: 5 + suite: + name: mochitest + flavor: plain-chunked + treeherder: + groupName: Desktop mochitests + groupSymbol: tc-M + symbol: {{chunk}} diff --git a/testing/taskcluster/tasks/tests/fx_linux64_reftest.yml b/testing/taskcluster/tasks/tests/fx_linux64_reftest.yml new file mode 100644 index 0000000000..49e09a100a --- /dev/null +++ b/testing/taskcluster/tasks/tests/fx_linux64_reftest.yml @@ -0,0 +1,17 @@ +--- +$inherits: + from: 'tasks/tests/fx_unittest_base.yml' +task: + metadata: + name: '[TC] Linux64 reftest {{chunk}}' + description: Reftest run {{chunk}} + extra: + chunks: + total: 4 + suite: + name: reftest + flavor: reftest + treeherder: + groupName: Desktop reftest + groupSymbol: tc-R + symbol: {{chunk}} diff --git a/testing/taskcluster/tasks/tests/fx_linux64_xpcshell.yml b/testing/taskcluster/tasks/tests/fx_linux64_xpcshell.yml new file mode 100644 index 0000000000..7c4c85136f --- /dev/null +++ b/testing/taskcluster/tasks/tests/fx_linux64_xpcshell.yml @@ -0,0 +1,17 @@ +--- +$inherits: + from: 'tasks/tests/fx_unittest_base.yml' +task: + metadata: + name: '[TC] Linux64 xpcshell {{chunk}}' + description: XPCShell run {{chunk}} + extra: + chunks: + total: 2 + suite: + name: xpcshell + flavor: xpcshell + treeherder: + groupName: Desktop xpcshell tests + groupSymbol: tc-X + symbol: {{chunk}} diff --git a/testing/taskcluster/tasks/tests/fx_unittest_base.yml b/testing/taskcluster/tasks/tests/fx_unittest_base.yml new file mode 100644 index 0000000000..7d6238c66a --- /dev/null +++ b/testing/taskcluster/tasks/tests/fx_unittest_base.yml @@ -0,0 +1,34 @@ +--- +$inherits: + from: 'tasks/test.yml' +task: + payload: + image: '{{#docker_image}}desktop-test{{/docker_image}}' + command: + - bash + - /home/worker/bin/test.sh + - --no-read-buildbot-config + - --installer-url={{build_url}} + - --test-packages-url={{test_packages_url}} + - --download-symbols=ondemand + - --{{suite}}-suite={{flavor}} + - --total-chunk={{total_chunks}} + - --this-chunk={{chunk}} + env: + GECKO_HEAD_REPOSITORY: '{{{head_repository}}}' + GECKO_HEAD_REV: '{{{head_rev}}}' + MOZHARNESS_SCRIPT: 'mozharness/scripts/desktop_unittest.py' + # TODO move linux_unittest.py to a platform specific config + MOZHARNESS_CONFIG: > + mozharness/configs/unittests/linux_unittest.py + mozharness/configs/remove_executables.py + artifacts: + 'public/build': + type: directory + path: '/home/worker/artifacts/' + expires: '{{#from_now}}1 year{{/from_now}}' + + extra: + treeherderEnv: + - production + - staging diff --git a/testing/taskcluster/tasks/tests/mulet_build_test.yml b/testing/taskcluster/tasks/tests/mulet_build_test.yml new file mode 100644 index 0000000000..ee130f0b1b --- /dev/null +++ b/testing/taskcluster/tasks/tests/mulet_build_test.yml @@ -0,0 +1,35 @@ +--- +$inherits: + from: 'tasks/tests/b2g_unittest_base.yml' +task: + metadata: + name: '[TC] - Gaia Build Test' + description: Gaia Build Test test run + + payload: + command: + - entrypoint # entrypoint ensures we are running in xvfb + - ./bin/pull_gaia.sh && + - > + python ./mozharness/scripts/gaia_build_integration.py + --application firefox + --config-file ./mozharness/configs/b2g/taskcluster_gaia_integration.py + --installer-url {{build_url}} + --test-packages-url {{test_packages_url}} + --gaia-repo https://hg.mozilla.org/integration/gaia-central + --gaia-dir /home/worker + --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip + artifacts: + 'public/build': + type: directory + path: '/home/worker/artifacts/' + expires: '{{#from_now}}1 year{{/from_now}}' + + extra: + treeherderEnv: + - production + - staging + treeherder: + groupSymbol: "?" + symbol: 'Gb' + productName: b2g diff --git a/testing/taskcluster/tasks/tests/mulet_build_unit.yml b/testing/taskcluster/tasks/tests/mulet_build_unit.yml new file mode 100644 index 0000000000..34de098214 --- /dev/null +++ b/testing/taskcluster/tasks/tests/mulet_build_unit.yml @@ -0,0 +1,34 @@ +--- +$inherits: + from: 'tasks/tests/b2g_unittest_base.yml' +task: + metadata: + name: '[TC] - Gaia Build Unit Test' + description: Gaia Build Unit Test + + payload: + command: + - entrypoint + - ./bin/pull_gaia.sh && + - > + python ./mozharness/scripts/gaia_build_unit.py + --application firefox + --config-file ./mozharness/configs/b2g/taskcluster_gaia_integration.py + --installer-url {{build_url}} + --test-packages-url {{test_packages_url}} + --gaia-repo https://hg.mozilla.org/integration/gaia-central + --gaia-dir /home/worker + artifacts: + 'public/build': + type: directory + path: '/home/worker/artifacts/' + expires: '{{#from_now}}1 year{{/from_now}}' + + extra: + treeherderEnv: + - production + - staging + treeherder: + groupSymbol: "?" + symbol: 'Gbu' + productName: b2g diff --git a/testing/taskcluster/tasks/tests/mulet_gaia_js_integration_tests.yml b/testing/taskcluster/tasks/tests/mulet_gaia_js_integration_tests.yml index 2f1df302cb..5d1c225b10 100644 --- a/testing/taskcluster/tasks/tests/mulet_gaia_js_integration_tests.yml +++ b/testing/taskcluster/tasks/tests/mulet_gaia_js_integration_tests.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Mulet Gaia JS Integration Test' @@ -13,13 +13,9 @@ task: - > python ./mozharness/scripts/gaia_integration.py --application firefox - --no-read-buildbot-config - --config-file b2g/gaia_integration_config.py - --config-file ./mozharness_configs/gaia_integration_override.py - --config-file ./mozharness_configs/remove_executables.py + --config-file b2g/taskcluster_gaia_integration.py --installer-url {{build_url}} --test-url {{tests_url}} - --download-symbols ondemand --total-chunk {{total_chunks}} --this-chunk {{chunk}} --gaia-repo {{gaia_head_repository}} diff --git a/testing/taskcluster/tasks/tests/mulet_gaia_unit.yml b/testing/taskcluster/tasks/tests/mulet_gaia_unit.yml new file mode 100644 index 0000000000..ba200fe8d8 --- /dev/null +++ b/testing/taskcluster/tasks/tests/mulet_gaia_unit.yml @@ -0,0 +1,39 @@ +--- +$inherits: + from: 'tasks/tests/b2g_unittest_base.yml' +task: + metadata: + name: '[TC] Mulet Gaia Unit Test' + description: Mulet Gaia Unit Test + + payload: + command: + - entrypoint + - ./bin/pull_gaia.sh && + - > + python ./mozharness/scripts/gaia_unit.py + --application firefox + --config-file b2g/taskcluster_gaia_unit_production.py + --installer-url {{build_url}} + --test-packages-url {{test_packages_url}} + --gaia-repo {{gaia_head_repository}} + --gaia-dir /home/worker + --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip + --total-chunk={{total_chunks}} + --this-chunk={{chunk}} + artifacts: + 'public/build': + type: directory + path: '/home/worker/artifacts/' + expires: '{{#from_now}}1 year{{/from_now}}' + + extra: + treeherderEnv: + - production + - staging + chunks: + total: 10 + treeherder: + groupSymbol: Gu + groupName: Mulet gaia unit tests + symbol: {{chunk}} diff --git a/testing/taskcluster/tasks/tests/mulet_gaia_unit_oop.yml b/testing/taskcluster/tasks/tests/mulet_gaia_unit_oop.yml new file mode 100644 index 0000000000..ae72a48138 --- /dev/null +++ b/testing/taskcluster/tasks/tests/mulet_gaia_unit_oop.yml @@ -0,0 +1,39 @@ +--- +$inherits: + from: 'tasks/tests/b2g_unittest_base.yml' +task: + metadata: + name: '[TC] Mulet Gaia Unit Test OOP' + description: Mulet Gaia Unit Test OOP + + payload: + command: + - entrypoint + - ./bin/pull_gaia.sh && + - > + python ./mozharness/scripts/gaia_unit.py + --application firefox + --config-file b2g/taskcluster_gaia_unit_production.py + --browser-arg -oop + --installer-url {{build_url}} + --test-packages-url {{test_packages_url}} + --gaia-repo {{gaia_head_repository}} + --gaia-dir /home/worker + --xre-url https://queue.taskcluster.net/v1/task/wXAHAaxDQpqxoWF1iljJjg/runs/0/artifacts/public/cache/xulrunner-sdk-40.zip + --total-chunk={{total_chunks}} + --this-chunk={{chunk}} + artifacts: + 'public/build': + type: directory + path: '/home/worker/artifacts/' + expires: '{{#from_now}}1 year{{/from_now}}' + + extra: + treeherderEnv: + - staging + chunks: + total: 10 + treeherder: + groupSymbol: Gu-oop + groupName: Mulet Gaia unit tests OOP + symbol: {{chunk}} diff --git a/testing/taskcluster/tasks/tests/mulet_linter.yml b/testing/taskcluster/tasks/tests/mulet_linter.yml new file mode 100644 index 0000000000..e575156558 --- /dev/null +++ b/testing/taskcluster/tasks/tests/mulet_linter.yml @@ -0,0 +1,34 @@ +--- +$inherits: + from: 'tasks/tests/b2g_unittest_base.yml' +task: + metadata: + name: '[TC] - Gaia Linter' + description: Linter Test + + payload: + command: + - entrypoint + - ./bin/pull_gaia.sh && + - > + python ./mozharness/scripts/gaia_linter.py + --application firefox + --config-file ./mozharness/configs/b2g/taskcluster_gaia_integration.py + --installer-url {{build_url}} + --test-packages-url {{test_packages_url}} + --gaia-repo https://hg.mozilla.org/integration/gaia-central + --gaia-dir /home/worker + artifacts: + 'public/build': + type: directory + path: '/home/worker/artifacts/' + expires: '{{#from_now}}1 year{{/from_now}}' + + extra: + treeherderEnv: + - production + - staging + treeherder: + groupSymbol: "?" + symbol: 'Li' + productName: 'b2g' diff --git a/testing/taskcluster/tasks/tests/mulet_mochitests.yml b/testing/taskcluster/tasks/tests/mulet_mochitests.yml index 498e957c3d..f0edf7e3c4 100644 --- a/testing/taskcluster/tasks/tests/mulet_mochitests.yml +++ b/testing/taskcluster/tasks/tests/mulet_mochitests.yml @@ -1,11 +1,10 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Mulet Mochitests ({{chunk}})' description: Mulet Mochitest run {{chunk}} - payload: command: - entrypoint # entrypoint ensures we are running in xvfb @@ -13,10 +12,9 @@ task: python ./mozharness/scripts/desktop_unittest.py --no-read-buildbot-config --config-file ./mozharness/configs/unittests/linux_unittest.py - --config-file ./mozharness_configs/linux_mulet_config.py --config-file ./mozharness_configs/remove_executables.py --installer-url {{build_url}} - --test-url {{tests_url}} + --test-packages-url {{test_packages_url}} --download-symbols ondemand --mochitest-suite plain-chunked --total-chunk={{total_chunks}} diff --git a/testing/taskcluster/tasks/tests/mulet_reftests.yml b/testing/taskcluster/tasks/tests/mulet_reftests.yml index af84197446..b9b9cdd272 100644 --- a/testing/taskcluster/tasks/tests/mulet_reftests.yml +++ b/testing/taskcluster/tasks/tests/mulet_reftests.yml @@ -1,6 +1,6 @@ --- $inherits: - from: 'tasks/test.yml' + from: 'tasks/tests/b2g_unittest_base.yml' task: metadata: name: '[TC] Reftest' @@ -13,13 +13,10 @@ task: - entrypoint - ./bin/pull_gaia.sh && - > - python ./mozharness/scripts/mulet_unittest.py --no-read-buildbot-config - --config-file ./mozharness/configs/b2g/generic_config.py - --config-file ./mozharness/configs/b2g/mulet_config.py - --config-file ./mozharness_configs/remove_executables.py + python ./mozharness/scripts/mulet_unittest.py + --config-file ./mozharness/configs/b2g/taskcluster_mulet_automation.py --installer-url {{build_url}} - --test-url {{tests_url}} - --download-symbols ondemand + --test-packages-url {{test_packages_url}} --test-suite reftest --test-manifest tests/layout/reftests/reftest.list --this-chunk {{chunk}} diff --git a/testing/testsuite-targets.mk b/testing/testsuite-targets.mk index 0fddcf1883..8f29ea5de2 100644 --- a/testing/testsuite-targets.mk +++ b/testing/testsuite-targets.mk @@ -563,18 +563,17 @@ stage-luciddream: make-stage-dir MARIONETTE_DIR=$(PKG_STAGE)/marionette stage-marionette: make-stage-dir $(NSINSTALL) -D $(MARIONETTE_DIR)/tests - $(NSINSTALL) -D $(MARIONETTE_DIR)/transport - $(NSINSTALL) -D $(MARIONETTE_DIR)/driver - @(cd $(topsrcdir)/testing/marionette/client && tar --exclude marionette/tests $(TAR_CREATE_FLAGS) - *) | (cd $(MARIONETTE_DIR)/ && tar -xf -) - @(cd $(topsrcdir)/testing/marionette/driver && tar $(TAR_CREATE_FLAGS) - *) | (cd $(MARIONETTE_DIR)/driver && tar -xf -) - $(PYTHON) $(topsrcdir)/testing/marionette/client/marionette/tests/print-manifest-dirs.py \ + $(NSINSTALL) -D $(MARIONETTE_DIR)/client + @(cd $(topsrcdir)/testing/marionette/harness && tar --exclude marionette/tests $(TAR_CREATE_FLAGS) - *) | (cd $(MARIONETTE_DIR)/ && tar -xf -) + @(cd $(topsrcdir)/testing/marionette/client && tar $(TAR_CREATE_FLAGS) - *) | (cd $(MARIONETTE_DIR)/client && tar -xf -) + $(PYTHON) $(topsrcdir)/testing/marionette/harness/marionette/tests/print-manifest-dirs.py \ $(topsrcdir) \ - $(topsrcdir)/testing/marionette/client/marionette/tests/unit-tests.ini \ + $(topsrcdir)/testing/marionette/harness/marionette/tests/unit-tests.ini \ | (cd $(topsrcdir) && xargs tar $(TAR_CREATE_FLAGS) -) \ | (cd $(MARIONETTE_DIR)/tests && tar -xf -) - $(PYTHON) $(topsrcdir)/testing/marionette/client/marionette/tests/print-manifest-dirs.py \ + $(PYTHON) $(topsrcdir)/testing/marionette/harness/marionette/tests/print-manifest-dirs.py \ $(topsrcdir) \ - $(topsrcdir)/testing/marionette/client/marionette/tests/webapi-tests.ini \ + $(topsrcdir)/testing/marionette/harness/marionette/tests/webapi-tests.ini \ | (cd $(topsrcdir) && xargs tar $(TAR_CREATE_FLAGS) -) \ | (cd $(MARIONETTE_DIR)/tests && tar -xf -) diff --git a/testing/web-platform/meta/web-animations/animation-effect-timing/duration.html.ini b/testing/web-platform/meta/web-animations/animation-effect-timing/duration.html.ini deleted file mode 100644 index 33c8e8093e..0000000000 --- a/testing/web-platform/meta/web-animations/animation-effect-timing/duration.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[duration.html] - type: testharness - [set duration auto] - expected: FAIL - bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1237173 - [set duration -100] - expected: FAIL - bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1237173 - [set duration abc] - expected: FAIL - bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1237173 - [set duration string 100] - expected: FAIL - bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1237173 diff --git a/testing/web-platform/tests/web-animations/animatable/animate.html b/testing/web-platform/tests/web-animations/animatable/animate.html index 9293d82a10..b0434eda0d 100644 --- a/testing/web-platform/tests/web-animations/animatable/animate.html +++ b/testing/web-platform/tests/web-animations/animatable/animate.html @@ -140,6 +140,5 @@ test(function(t) { 'The returned Animation targets to the correct object'); }, 'CSSPseudoElement.animate() creates an Animation object targeting ' + 'to the correct CSSPseudoElement object'); - diff --git a/testing/web-platform/tests/web-animations/animation-effect-timing/duration.html b/testing/web-platform/tests/web-animations/animation-effect-timing/duration.html index 671cc1215d..7d4df264eb 100644 --- a/testing/web-platform/tests/web-animations/animation-effect-timing/duration.html +++ b/testing/web-platform/tests/web-animations/animation-effect-timing/duration.html @@ -27,37 +27,18 @@ test(function(t) { var div = createDiv(t); var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); anim.effect.timing.duration = 'auto'; - assert_equals(anim.effect.timing.duration, 0, 'set duration \'auto\''); + assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\''); assert_equals(anim.effect.getComputedTiming().duration, 0, 'getComputedTiming() after set duration \'auto\''); }, 'set duration auto'); test(function(t) { var div = createDiv(t); - var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); - anim.effect.timing.duration = -100; - assert_equals(anim.effect.timing.duration, 0, 'set duration -100'); + var anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 'auto' }); + assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\''); assert_equals(anim.effect.getComputedTiming().duration, 0, - 'getComputedTiming() after set duration -100'); -}, 'set duration -100'); - -test(function(t) { - var div = createDiv(t); - var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); - anim.effect.timing.duration = 'abc'; - assert_equals(anim.effect.timing.duration, 0, 'set duration \'abc\''); - assert_equals(anim.effect.getComputedTiming().duration, 0, - 'getComputedTiming() after set duration \'abc\''); -}, 'set duration abc'); - -test(function(t) { - var div = createDiv(t); - var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); - anim.effect.timing.duration = '100'; - assert_equals(anim.effect.timing.duration, 0, 'set duration \'100\''); - assert_equals(anim.effect.getComputedTiming().duration, 0, - 'getComputedTiming() after set duration \'100\''); -}, 'set duration string 100'); + 'getComputedTiming() after set duration \'auto\''); +}, 'set auto duration in animate as object'); test(function(t) { var div = createDiv(t); @@ -68,6 +49,102 @@ test(function(t) { 'getComputedTiming() after set duration Infinity'); }, 'set duration Infinity'); +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, -1); + }); +}, 'set negative duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, -Infinity); + }); +}, 'set negative Infinity duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, NaN); + }); +}, 'set NaN duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: -1 }); + }); +}, 'set negative duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: -Infinity }); + }); +}, 'set negative Infinity duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: NaN }); + }); +}, 'set NaN duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: 'abc' }); + }); +}, 'set abc string duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: '100' }); + }); +}, 'set 100 string duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = -1; + }); +}, 'set negative duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = -Infinity; + }); +}, 'set negative Infinity duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = NaN; + }); +}, 'set NaN duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = 'abc'; + }); +}, 'set duration abc'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = '100'; + }); +}, 'set duration string 100'); + diff --git a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html index 832b2471ee..6b2c97c42b 100644 --- a/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html +++ b/testing/web-platform/tests/web-animations/keyframe-effect/constructor.html @@ -493,30 +493,9 @@ var gKeyframeEffectOptionTests = [ { desc: "+Infinity", input: Infinity, expected: { duration: Infinity } }, - { desc: "-Infinity", - input: -Infinity, - expected: { duration: -Infinity } }, - { desc: "NaN", - input: NaN, - expected: { duration: NaN } }, - { desc: "a negative value", - input: -1, - expected: { duration: -1 } }, { desc: "an Infinity duration", input: { duration: Infinity }, expected: { duration: Infinity } }, - { desc: "a negative Infinity duration", - input: { duration: -Infinity }, - expected: { duration: -Infinity } }, - { desc: "a NaN duration", - input: { duration: NaN }, - expected: { duration: NaN } }, - { desc: "a negative duration", - input: { duration: -1 }, - expected: { duration: -1 } }, - { desc: "a string duration", - input: { duration: "merrychristmas" }, - expected: { duration: "merrychristmas" } }, { desc: "an auto duration", input: { duration: "auto" }, expected: { duration: "auto" } }, @@ -567,6 +546,40 @@ gKeyframeEffectOptionTests.forEach(function(stest) { }, "a KeyframeEffectReadOnly constructed by " + stest.desc); }); +var gInvalidKeyframeEffectOptionTests = [ + { desc: "-Infinity", + input: -Infinity, + expected: { name: "TypeError" } }, + { desc: "NaN", + input: NaN, + expected: { name: "TypeError" } }, + { desc: "a negative value", + input: -1, + expected: { name: "TypeError" } }, + { desc: "a negative Infinity duration", + input: { duration: -Infinity }, + expected: { name: "TypeError" } }, + { desc: "a NaN duration", + input: { duration: NaN }, + expected: { name: "TypeError" } }, + { desc: "a negative duration", + input: { duration: -1 }, + expected: { name: "TypeError" } }, + { desc: "a string duration", + input: { duration: "merrychristmas" }, + expected: { name: "TypeError" } } +]; + +gInvalidKeyframeEffectOptionTests.forEach(function(stest) { + test(function(t) { + assert_throws(stest.expected, function() { + new KeyframeEffectReadOnly(target, + {left: ["10px", "20px"]}, + stest.input); + }); + }, "Invalid KeyframeEffectReadOnly option by " + stest.desc); +}); + test(function(t) { var effect = new KeyframeEffect(target, { left: ["10px", "20px"] }); diff --git a/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html index 7412824e7e..caddd4a2c5 100644 --- a/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html +++ b/testing/web-platform/tests/web-animations/keyframe-effect/getComputedTiming.html @@ -54,30 +54,9 @@ var gGetComputedTimingTests = [ { desc: "+Infinity", input: Infinity, expected: { duration: Infinity } }, - { desc: "-Infinity", - input: -Infinity, - expected: { duration: 0 } }, - { desc: "NaN", - input: NaN, - expected: { duration: 0 } }, - { desc: "a negative value", - input: -1, - expected: { duration: 0 } }, { desc: "an Infinity duration", input: { duration: Infinity }, expected: { duration: Infinity } }, - { desc: "a negative Infinity duration", - input: { duration: -Infinity }, - expected: { duration: 0 } }, - { desc: "a NaN duration", - input: { duration: NaN }, - expected: { duration: 0 } }, - { desc: "a negative duration", - input: { duration: -1}, - expected: { duration: 0 } }, - { desc: "a string duration", - input: { duration: "merrychristmas"}, - expected: { duration: 0 } }, { desc: "an auto duration", input: { duration: "auto" }, expected: { duration: 0 } }, @@ -175,15 +154,9 @@ var gActiveDurationTests = [ { desc: "an infinite duration and zero iteration count", input: { duration: Infinity, iterations: 0 }, expected: 0 }, - { desc: "an invalid duration and default iteration count", - input: { duration: "cabbage" }, - expected: 0 }, { desc: "a non-zero duration and invalid iteration count", input: { duration: 1000, iterations: "cabbage" }, - expected: 1000 }, - { desc: "an invalid duration and invalid iteration count", - input: { duration: "cabbage", iterations: "cabbage" }, - expected: 0 } + expected: 1000 } ]; gActiveDurationTests.forEach(function(stest) { diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 5827a8e2b7..32097afaab 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -487,6 +487,14 @@ "n_values": 10, "description": "Use of SpiderMonkey's deprecated language extensions in add-ons: ForEach=0, DestructuringForIn=1 (obsolete), LegacyGenerator=2, ExpressionClosure=3, LetBlock=4 (obsolete), LetExpression=5 (obsolete), NoSuchMethod=6 (obsolete), FlagsArgument=7, RegExpSourceProp=8 (obsolete), RestoredRegExpStatics=9 (obsolete), BlockScopeFunRedecl=10" }, + "JS_DEFINE_GETTER_SETTER_THIS_NULL_UNDEFINED": { + "alert_emails": ["jdemooij@mozilla.com"], + "bug_numbers": [1249123], + "expires_in_version": "55", + "kind": "boolean", + "releaseChannelCollection": "opt-out", + "description": "__defineGetter__ or __defineSetter__ invoked with 1 = null/undefined or 0 = everything else as |this| value" + }, "XUL_CACHE_DISABLED": { "expires_in_version": "default", "kind": "flag", diff --git a/toolkit/content/tests/browser/browser.ini b/toolkit/content/tests/browser/browser.ini index ca7894c420..d771a71c84 100644 --- a/toolkit/content/tests/browser/browser.ini +++ b/toolkit/content/tests/browser/browser.ini @@ -8,19 +8,14 @@ support-files = skip-if = buildapp == 'mulet' || e10s # Relies on drop to be handled in the parent process [browser_bug295977_autoscroll_overflow.js] [browser_bug594509.js] -skip-if = e10s # Bug ?????? - intermittent crash of child process reported when run under e10s [browser_bug982298.js] -skip-if = e10s # Bug 1064580 [browser_bug1198465.js] [browser_contentTitle.js] [browser_default_image_filename.js] -skip-if = e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly [browser_f7_caret_browsing.js] skip-if = e10s [browser_findbar.js] -skip-if = e10s # Disabled for e10s: Bug ?????? - seems to be a timing issue with RemoteFinder.jsm messages coming later than the tests expect. [browser_input_file_tooltips.js] -skip-if = e10s # Bug ?????? - test directly manipulates content (TypeError: doc.createElement is not a function) [browser_isSynthetic.js] support-files = empty.png @@ -37,3 +32,7 @@ support-files = file_redirect.html file_redirect_to.html [browser_bug1170531.js] +[browser_saveImageURL.js] +support-files = + image.jpg + image_page.html diff --git a/toolkit/content/tests/browser/browser_input_file_tooltips.js b/toolkit/content/tests/browser/browser_input_file_tooltips.js index e7b1a3d793..7f11956f57 100644 --- a/toolkit/content/tests/browser/browser_input_file_tooltips.js +++ b/toolkit/content/tests/browser/browser_input_file_tooltips.js @@ -1,29 +1,53 @@ -function test() -{ - let data = [ - { value: "/tmp", result: "tmp" }, - { title: "foo", result: "foo" }, - { result: "No file selected." }, - { multiple: true, result: "No files selected." }, - { required: true, result: "Please select a file." } - ]; - let doc = gBrowser.contentDocument; - let tooltip = document.getElementById("aHTMLTooltip"); +let tempFile; +add_task(function* setup() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve); + }); + tempFile = createTempFile(); + registerCleanupFunction(function() { + tempFile.remove(true); + }); +}); - for (let test of data) { - let input = doc.createElement('input'); +add_task(function* test_singlefile_selected() { + yield do_test({value: true, result: "testfile_bug1251809"}); +}); + +add_task(function* test_title_set() { + yield do_test({title: "foo", result: "foo"}); +}); + +add_task(function* test_nofile_selected() { + yield do_test({result: "No file selected."}); +}); + +add_task(function* test_multipleset_nofile_selected() { + yield do_test({multiple: true, result: "No files selected."}); +}); + +add_task(function* test_requiredset() { + yield do_test({required: true, result: "Please select a file."}); +}); + +function* do_test(test) { + info(`starting test ${JSON.stringify(test)}`); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser); + + yield new Promise(resolve => { + EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 300, 300, resolve); + }); + + ContentTask.spawn(tab.linkedBrowser, test, function*(test) { + let doc = content.document; + let input = doc.createElement("input"); doc.body.appendChild(input); - input.type = 'file'; + input.id = "test_input"; + input.setAttribute("style", "position: absolute; top: 0; left: 0;"); + input.type = "file"; if (test.title) { - input.setAttribute('title', test.title); - } - if (test.value) { - if (test.value == "/tmp" && navigator.platform.indexOf('Win') != -1) { - test.value = "C:\\Temp"; - test.result = "Temp"; - } - input.value = test.value; + input.setAttribute("title", test.title); } if (test.multiple) { input.multiple = true; @@ -31,8 +55,57 @@ function test() if (test.required) { input.required = true; } + }); - ok(tooltip.fillInPageTooltip(input)); - is(tooltip.getAttribute('label'), test.result); + if (test.value) { + let MockFilePicker = SpecialPowers.MockFilePicker; + MockFilePicker.init(window); + MockFilePicker.returnValue = MockFilePicker.returnOK; + MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", [], false); + MockFilePicker.returnFiles = [tempFile]; + + try { + // Open the File Picker dialog (MockFilePicker) to select + // the files for the test. + yield BrowserTestUtils.synthesizeMouseAtCenter("#test_input", {}, tab.linkedBrowser); + yield ContentTask.spawn(tab.linkedBrowser, {}, function*() { + let input = content.document.querySelector("#test_input"); + yield ContentTaskUtils.waitForCondition(() => input.files.length, + "The input should have at least one file selected"); + info(`The input has ${input.files.length} file(s) selected.`); + }); + } finally { + MockFilePicker.cleanup(); + } } + + let awaitTooltipOpen = new Promise(resolve => { + let tooltipId = Services.appinfo.browserTabsRemoteAutostart ? + "remoteBrowserTooltip" : + "aHTMLTooltip"; + let tooltip = document.getElementById(tooltipId); + tooltip.addEventListener("popupshown", function onpopupshown(event) { + tooltip.removeEventListener("popupshown", onpopupshown); + resolve(event.target); + }); + }); + yield new Promise(resolve => { + EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 100, 5, resolve); + }); + yield new Promise(resolve => setTimeout(resolve, 100)); + yield new Promise(resolve => { + EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 110, 15, resolve); + }); + let tooltip = yield awaitTooltipOpen; + + is(tooltip.getAttribute("label"), test.result, "tooltip label should match expectation"); + + yield BrowserTestUtils.removeTab(tab); +} + +function createTempFile() { + let file = FileUtils.getDir("TmpD", [], false); + file.append("testfile_bug1251809"); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + return file; } diff --git a/toolkit/content/tests/browser/browser_saveImageURL.js b/toolkit/content/tests/browser/browser_saveImageURL.js new file mode 100644 index 0000000000..cf846c6baf --- /dev/null +++ b/toolkit/content/tests/browser/browser_saveImageURL.js @@ -0,0 +1,64 @@ +"use strict"; + +const IMAGE_PAGE = "https://example.com/browser/toolkit/content/tests/browser/image_page.html"; +const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser"; + +MockFilePicker.init(window); +MockFilePicker.returnValue = MockFilePicker.returnCancel; + +function waitForFilePicker() { + return new Promise((resolve) => { + MockFilePicker.showCallback = () => { + MockFilePicker.showCallback = null; + ok(true, "Saw the file picker"); + resolve(); + } + }) +} + +/** + * Test that saveImageURL works when we pass in the aIsContentWindowPrivate + * argument instead of a document. This is the preferred API. + */ +add_task(function* preferred_API() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: IMAGE_PAGE, + }, function*(browser) { + let url = yield ContentTask.spawn(browser, null, function*() { + let image = content.document.getElementById("image"); + return image.href; + }); + + saveImageURL(url, "image.jpg", null, true, false, null, null, null, null, false); + yield waitForFilePicker(); + }); +}); + +/** + * Test that saveImageURL will still work when passed a document instead + * of the aIsContentWindowPrivate argument. This is the deprecated API, and + * will not work in apps using remote browsers having PREF_UNSAFE_FORBIDDEN + * set to true. + */ +add_task(function* deprecated_API() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: IMAGE_PAGE, + }, function*(browser) { + yield pushPrefs([PREF_UNSAFE_FORBIDDEN, false]); + + let url = yield ContentTask.spawn(browser, null, function*() { + let image = content.document.getElementById("image"); + return image.href; + }); + + // Now get the document directly from content. If we run this test with + // e10s-enabled, this will be a CPOW, which is forbidden. We'll just + // pass the XUL document instead to test this interface. + let doc = document; + + saveImageURL(url, "image.jpg", null, true, false, null, doc, null, null); + yield waitForFilePicker(); + }); +}); diff --git a/toolkit/content/tests/browser/head.js b/toolkit/content/tests/browser/head.js index 2f66e42f21..07031452e3 100644 --- a/toolkit/content/tests/browser/head.js +++ b/toolkit/content/tests/browser/head.js @@ -1,5 +1,10 @@ "use strict"; +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); + /** * A wrapper for the findbar's method "close", which is not synchronous * because of animation. @@ -18,3 +23,9 @@ function closeFindbarAndWait(findbar) { findbar.close(); }); } + +function pushPrefs(...aPrefs) { + let deferred = Promise.defer(); + SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve); + return deferred.promise; +} diff --git a/toolkit/content/tests/browser/image.jpg b/toolkit/content/tests/browser/image.jpg new file mode 100644 index 0000000000..5031808ad2 Binary files /dev/null and b/toolkit/content/tests/browser/image.jpg differ diff --git a/toolkit/content/tests/browser/image_page.html b/toolkit/content/tests/browser/image_page.html new file mode 100644 index 0000000000..522a1d8cf9 --- /dev/null +++ b/toolkit/content/tests/browser/image_page.html @@ -0,0 +1,9 @@ + + + + +OHAI + + + + diff --git a/toolkit/content/widgets/popup.xml b/toolkit/content/widgets/popup.xml index 8833e5ab79..4faec3bea9 100644 --- a/toolkit/content/widgets/popup.xml +++ b/toolkit/content/widgets/popup.xml @@ -530,8 +530,9 @@ IsDirectory(&isDir); + MOZ_ASSERT(isDir); +#endif + + RefPtr directory = + Directory::Create(aWindow, aFile, Directory::eDOMRootDirectory); + MOZ_ASSERT(directory); + + directory.forget(aResult); + return NS_OK; + } + + nsCOMPtr blob = File::CreateFromFile(aWindow, aFile); + blob.forget(aResult); + return NS_OK; +} + +} // anonymous namespace + /** * A runnable to dispatch from the main thread to the main thread to display * the file picker while letting the showAsync method return right away. @@ -74,9 +105,9 @@ class nsBaseFilePickerEnumerator : public nsISimpleEnumerator public: NS_DECL_ISUPPORTS - explicit nsBaseFilePickerEnumerator(nsPIDOMWindow* aParent, - nsISimpleEnumerator* iterator, - int16_t aMode) + nsBaseFilePickerEnumerator(nsPIDOMWindow* aParent, + nsISimpleEnumerator* iterator, + int16_t aMode) : mIterator(iterator) , mParent(aParent) , mMode(aMode) @@ -98,32 +129,10 @@ public: return NS_ERROR_FAILURE; } - RefPtr domFile = File::CreateFromFile(mParent, localFile); - - // Right now we're on the main thread of the chrome process. We need - // to call SetIsDirectory on the BlobImpl, but it's preferable not to - // call nsIFile::IsDirectory to determine what argument to pass since - // IsDirectory does synchronous I/O. It's true that since we've just - // been called synchronously directly after nsIFilePicker::Show blocked - // the main thread while the picker was being shown and the OS did file - // system access, doing more I/O to stat the selected files probably - // wouldn't be the end of the world. However, we can simply check - // mMode and avoid calling IsDirectory. - // - // In future we may take advantage of OS X's ability to allow both - // files and directories to be picked at the same time, so we do assert - // in debug builds that the mMode trick produces the correct results. - // If we do add that support maybe it's better to use IsDirectory - // directly, but in an nsRunnable punted off to a background thread. -#ifdef DEBUG - bool isDir; - localFile->IsDirectory(&isDir); - MOZ_ASSERT(isDir == (mMode == nsIFilePicker::modeGetFolder)); -#endif - domFile->Impl()->SetIsDirectory(mMode == nsIFilePicker::modeGetFolder); - - nsCOMPtr(domFile).forget(aResult); - return NS_OK; + return LocalFileToDirectoryOrBlob(mParent, + mMode == nsIFilePicker::modeGetFolder, + localFile, + aResult); } NS_IMETHOD @@ -350,10 +359,10 @@ nsBaseFilePicker::GetDomFileOrDirectory(nsISupports** aValue) auto* innerParent = mParent ? mParent->GetCurrentInnerWindow() : nullptr; - RefPtr domFile = File::CreateFromFile(innerParent, localFile); - domFile->Impl()->SetIsDirectory(mMode == nsIFilePicker::modeGetFolder); - nsCOMPtr(domFile).forget(aValue); - return NS_OK; + return LocalFileToDirectoryOrBlob(innerParent, + mMode == nsIFilePicker::modeGetFolder, + localFile, + aValue); } NS_IMETHODIMP diff --git a/widget/nsFilePickerProxy.cpp b/widget/nsFilePickerProxy.cpp index 4c0db8c041..bbd6ba6ab2 100644 --- a/widget/nsFilePickerProxy.cpp +++ b/widget/nsFilePickerProxy.cpp @@ -7,6 +7,7 @@ #include "nsFilePickerProxy.h" #include "nsComponentManagerUtils.h" #include "nsIFile.h" +#include "mozilla/dom/Directory.h" #include "mozilla/dom/File.h" #include "mozilla/dom/TabChild.h" #include "mozilla/dom/ipc/BlobChild.h" @@ -145,11 +146,11 @@ nsFilePickerProxy::Open(nsIFilePickerShownCallback* aCallback) } bool -nsFilePickerProxy::Recv__delete__(const MaybeInputFiles& aFiles, +nsFilePickerProxy::Recv__delete__(const MaybeInputData& aData, const int16_t& aResult) { - if (aFiles.type() == MaybeInputFiles::TInputFiles) { - const InfallibleTArray& blobs = aFiles.get_InputFiles().blobsChild(); + if (aData.type() == MaybeInputData::TInputBlobs) { + const InfallibleTArray& blobs = aData.get_InputBlobs().blobsChild(); for (uint32_t i = 0; i < blobs.Length(); ++i) { BlobChild* actor = static_cast(blobs[i]); RefPtr blobImpl = actor->GetBlobImpl(); @@ -162,8 +163,24 @@ nsFilePickerProxy::Recv__delete__(const MaybeInputFiles& aFiles, RefPtr file = File::Create(mParent, blobImpl); MOZ_ASSERT(file); - mFilesOrDirectories.AppendElement(file); + OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement(); + element->SetAsFile() = file; } + } else if (aData.type() == MaybeInputData::TInputDirectory) { + nsCOMPtr file; + NS_ConvertUTF16toUTF8 path(aData.get_InputDirectory().directoryPath()); + nsresult rv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return true; + } + + RefPtr directory = + Directory::Create(mParent, file, + Directory::eDOMRootDirectory); + MOZ_ASSERT(directory); + + OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement(); + element->SetAsDirectory() = directory; } if (mCallback) { @@ -183,8 +200,16 @@ nsFilePickerProxy::GetDomFileOrDirectory(nsISupports** aValue) } MOZ_ASSERT(mFilesOrDirectories.Length() == 1); - nsCOMPtr blob = mFilesOrDirectories[0].get(); - blob.forget(aValue); + + if (mFilesOrDirectories[0].IsFile()) { + nsCOMPtr blob = mFilesOrDirectories[0].GetAsFile().get(); + blob.forget(aValue); + return NS_OK; + } + + MOZ_ASSERT(mFilesOrDirectories[0].IsDirectory()); + RefPtr directory = mFilesOrDirectories[0].GetAsDirectory(); + directory.forget(aValue); return NS_OK; } @@ -195,8 +220,9 @@ class SimpleEnumerator final : public nsISimpleEnumerator public: NS_DECL_ISUPPORTS - explicit SimpleEnumerator(const nsTArray>& aFiles) - : mFiles(aFiles) + explicit + SimpleEnumerator(const nsTArray& aFilesOrDirectories) + : mFilesOrDirectories(aFilesOrDirectories) , mIndex(0) {} @@ -204,17 +230,26 @@ public: HasMoreElements(bool* aRetvalue) override { MOZ_ASSERT(aRetvalue); - *aRetvalue = mIndex < mFiles.Length(); + *aRetvalue = mIndex < mFilesOrDirectories.Length(); return NS_OK; } NS_IMETHOD - GetNext(nsISupports** aSupports) override + GetNext(nsISupports** aValue) override { - NS_ENSURE_TRUE(mIndex < mFiles.Length(), NS_ERROR_FAILURE); + NS_ENSURE_TRUE(mIndex < mFilesOrDirectories.Length(), NS_ERROR_FAILURE); - nsCOMPtr blob = mFiles[mIndex++].get(); - blob.forget(aSupports); + uint32_t index = mIndex++; + + if (mFilesOrDirectories[index].IsFile()) { + nsCOMPtr blob = mFilesOrDirectories[index].GetAsFile().get(); + blob.forget(aValue); + return NS_OK; + } + + MOZ_ASSERT(mFilesOrDirectories[index].IsDirectory()); + RefPtr directory = mFilesOrDirectories[index].GetAsDirectory(); + directory.forget(aValue); return NS_OK; } @@ -222,7 +257,7 @@ private: ~SimpleEnumerator() {} - nsTArray> mFiles; + nsTArray mFilesOrDirectories; uint32_t mIndex; }; @@ -233,7 +268,8 @@ NS_IMPL_ISUPPORTS(SimpleEnumerator, nsISimpleEnumerator) NS_IMETHODIMP nsFilePickerProxy::GetDomFileOrDirectoryEnumerator(nsISimpleEnumerator** aDomfiles) { - RefPtr enumerator = new SimpleEnumerator(mFilesOrDirectories); + RefPtr enumerator = + new SimpleEnumerator(mFilesOrDirectories); enumerator.forget(aDomfiles); return NS_OK; } diff --git a/widget/nsFilePickerProxy.h b/widget/nsFilePickerProxy.h index c27b9ab834..7a71285f3e 100644 --- a/widget/nsFilePickerProxy.h +++ b/widget/nsFilePickerProxy.h @@ -13,17 +13,12 @@ #include "nsCOMArray.h" #include "mozilla/dom/PFilePickerChild.h" +#include "mozilla/dom/UnionTypes.h" class nsIWidget; class nsIFile; class nsPIDOMWindow; -namespace mozilla { -namespace dom { -class File; -} // namespace dom -} // namespace mozilla - /** This class creates a proxy file picker to be used in content processes. The file picker just collects the initialization data and when Show() is @@ -59,13 +54,13 @@ public: // PFilePickerChild virtual bool - Recv__delete__(const MaybeInputFiles& aFiles, const int16_t& aResult) override; + Recv__delete__(const MaybeInputData& aData, const int16_t& aResult) override; private: ~nsFilePickerProxy(); void InitNative(nsIWidget*, const nsAString&) override; - nsTArray> mFilesOrDirectories; + nsTArray mFilesOrDirectories; nsCOMPtr mCallback; int16_t mSelectedType;