diff --git a/dom/base/WindowFeatures.cpp b/dom/base/WindowFeatures.cpp new file mode 100644 index 000000000000..0d1c3172f39f --- /dev/null +++ b/dom/base/WindowFeatures.cpp @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WindowFeatures.h" + +#include "nsINode.h" // IsSpaceCharacter +#include "nsContentUtils.h" // nsContentUtils +#include "nsDependentSubstring.h" // Substring +#include "nsReadableUtils.h" // ToLowerCase + +#ifdef DEBUG +/* static */ +bool WindowFeatures::IsLowerCase(const char* text) { + nsAutoCString before(text); + nsAutoCString after; + ToLowerCase(before, after); + return before == after; +} +#endif + +static bool IsFeatureSeparator(char aChar) { + // https://html.spec.whatwg.org/multipage/window-object.html#feature-separator + // A code point is a feature separator if it is ASCII whitespace, U+003D (=), + // or U+002C (,). + return IsSpaceCharacter(aChar) || aChar == '=' || aChar == ','; +} + +template +void AdvanceWhile(IterT& aPosition, const IterT& aEnd, CondT aCondition) { + // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + // + // Step 2. While `position` doesn’t point past the end of `input` and the + // code point at `position` within `input` meets the condition condition: + while (aCondition(*aPosition) && aPosition < aEnd) { + // Step 2.1. Append that code point to the end of `result`. + // (done by caller) + + // Step 2.2. Advance `position` by 1. + ++aPosition; + } +} + +template +nsTDependentSubstring CollectSequence(IterT& aPosition, const IterT& aEnd, + CondT aCondition) { + // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + // To collect a sequence of code points meeting a condition `condition` from + // a string `input`, given a position variable `position` tracking the + // position of the calling algorithm within `input`: + + // Step 1. Let `result` be the empty string. + auto start = aPosition; + + // Step 2. + AdvanceWhile(aPosition, aEnd, aCondition); + + // Step 3. Return `result`. + return Substring(start, aPosition); +} + +static void NormalizeName(nsAutoCString& aName) { + // https://html.spec.whatwg.org/multipage/window-object.html#normalizing-the-feature-name + if (aName == "screenx") { + aName = "left"; + } else if (aName == "screeny") { + aName = "top"; + } else if (aName == "innerwidth") { + aName = "width"; + } else if (aName == "innerheight") { + aName = "height"; + } +} + +/* static */ +int32_t WindowFeatures::ParseIntegerWithFallback(const nsCString& aValue) { + // https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-parse-boolean + // + // Step 3. Let `parsed` be the result of parsing value as an integer. + nsContentUtils::ParseHTMLIntegerResultFlags parseResult; + int32_t parsed = nsContentUtils::ParseHTMLInteger(aValue, &parseResult); + + // Step 4. If `parsed` is an error, then set it to 0. + // + // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-integers + // + // eParseHTMLInteger_NonStandard is not tested given: + // * Step 4 allows leading whitespaces + // * Step 6 allows a plus sign + // * Step 8 does not disallow leading zeros + // * Steps 6 and 9 allow `-0` + // + // eParseHTMLInteger_DidNotConsumeAllInput is not tested given: + // * Step 8 collects digits and ignores remaining part + // + if (parseResult & nsContentUtils::eParseHTMLInteger_Error) { + parsed = 0; + } + + return parsed; +} + +/* static */ +bool WindowFeatures::ParseBool(const nsCString& aValue) { + // https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-parse-boolean + // To parse a boolean feature given a string `value`: + + // Step 1. If `value` is the empty string, then return true. + if (aValue.IsEmpty()) { + return true; + } + + // Step 2. If `value` is a case-sensitive match for "yes", then return true. + if (aValue == "yes") { + return true; + } + + // Steps 3-4. + int32_t parsed = ParseIntegerWithFallback(aValue); + + // Step 5. Return false if `parsed` is 0, and true otherwise. + return parsed != 0; +} + +bool WindowFeatures::Tokenize(const nsACString& aFeatures) { + // https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-tokenize + // To tokenize the `features` argument: + + // Step 1. Let `tokenizedFeatures` be a new ordered map. + // (implicit) + + // Step 2. Let `position` point at the first code point of features. + auto position = aFeatures.BeginReading(); + + // Step 3. While `position` is not past the end of `features`: + auto end = aFeatures.EndReading(); + while (position < end) { + // Step 3.1. Let `name` be the empty string. + // (implicit) + + // Step 3.2. Let `value` be the empty string. + nsAutoCString value; + + // Step 3.3. Collect a sequence of code points that are feature separators + // from `features` given `position`. This skips past leading separators + // before the name. + // + // NOTE: Do not collect given this value is unused. + AdvanceWhile(position, end, IsFeatureSeparator); + + // Step 3.4. Collect a sequence of code points that are not feature + // separators from `features` given `position`. Set `name` to the collected + // characters, converted to ASCII lowercase. + nsAutoCString name(CollectSequence( + position, end, [](char c) { return !IsFeatureSeparator(c); })); + ToLowerCase(name); + + // Step 3.5. Set `name` to the result of normalizing the feature name + // `name`. + NormalizeName(name); + + // Step 3.6. While `position` is not past the end of `features` and the + // code point at `position` in features is not U+003D (=): + // + // Step 3.6.1. If the code point at `position` in features is U+002C (,), + // or if it is not a feature separator, then break. + // + // Step 3.6.2. Advance `position` by 1. + // + // NOTE: This skips to the first U+003D (=) but does not skip past a U+002C + // (,) or a non-separator. + // + // The above means skip all whitespaces. + AdvanceWhile(position, end, [](char c) { return IsSpaceCharacter(c); }); + + // Step 3.7. If the code point at `position` in `features` is a feature + // separator: + if (position < end && IsFeatureSeparator(*position)) { + // Step 3.7.1. While `position` is not past the end of `features` and the + // code point at `position` in `features` is a feature separator: + // + // Step 3.7.1.1. If the code point at `position` in `features` is + // U+002C (,), then break. + // + // Step 3.7.1.2. Advance `position` by 1. + // + // NOTE: This skips to the first non-separator but does not skip past a + // U+002C (,). + AdvanceWhile(position, end, + [](char c) { return IsFeatureSeparator(c) && c != ','; }); + + // Step 3.7.2. Collect a sequence of code points that are not feature + // separators code points from `features` given `position`. Set `value` to + // the collected code points, converted to ASCII lowercase. + value = CollectSequence(position, end, + [](char c) { return !IsFeatureSeparator(c); }); + ToLowerCase(value); + } + + // Step 3.8. If `name` is not the empty string, then set + // `tokenizedFeatures[name]` to `value`. + if (!name.IsEmpty()) { + if (!tokenizedFeatures_.put(name, value)) { + return false; + } + } + } + + // Step 4. Return `tokenizedFeatures`. + return true; +} + +void WindowFeatures::Stringify(nsAutoCString& aOutput) { + MOZ_ASSERT(aOutput.IsEmpty()); + + for (auto r = tokenizedFeatures_.all(); !r.empty(); r.popFront()) { + if (!aOutput.IsEmpty()) { + aOutput.Append(','); + } + + const nsCString& name = r.front().key(); + const nsCString& value = r.front().value(); + + aOutput.Append(name); + + if (!value.IsEmpty()) { + aOutput.Append('='); + aOutput.Append(value); + } + } +} diff --git a/dom/base/WindowFeatures.h b/dom/base/WindowFeatures.h new file mode 100644 index 000000000000..634533c9fe95 --- /dev/null +++ b/dom/base/WindowFeatures.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_WindowFeatures_h +#define mozilla_dom_WindowFeatures_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/HashTable.h" // mozilla::HashMap + +#include "nsStringFwd.h" // nsCString, nsACString, nsAutoCString, nsLiteralCString +#include "nsTStringHasher.h" // mozilla::DefaultHasher + +namespace mozilla { +namespace dom { + +// Represents `tokenizedFeatures` in +// https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-tokenize +// with accessor methods for values. +class WindowFeatures { + public: + WindowFeatures() = default; + + WindowFeatures(const WindowFeatures& aOther) = delete; + WindowFeatures& operator=(const WindowFeatures& aOther) = delete; + + WindowFeatures(WindowFeatures&& aOther) = delete; + WindowFeatures& operator=(WindowFeatures&& aOther) = delete; + + // Tokenizes `aFeatures` and stores the result map in member field. + // This should be called at the begining, only once. + // + // Returns true if successfully tokenized, false otherwise. + bool Tokenize(const nsACString& aFeatures); + + // Returns true if the `aName` feature is specified. + template + bool Exists(const char (&aName)[N]) const { + MOZ_ASSERT(IsLowerCase(aName)); + nsLiteralCString name(aName); + return tokenizedFeatures_.has(name); + } + + // Returns string value of `aName` feature. + // The feature must exist. + template + const nsCString& Get(const char (&aName)[N]) const { + MOZ_ASSERT(IsLowerCase(aName)); + nsLiteralCString name(aName); + auto p = tokenizedFeatures_.lookup(name); + MOZ_ASSERT(p.found()); + + return p->value(); + } + + // Returns integer value of `aName` feature. + // The feature must exist. + template + int32_t GetInt(const char (&aName)[N]) const { + const nsCString& value = Get(aName); + return ParseIntegerWithFallback(value); + } + + // Returns bool value of `aName` feature. + // The feature must exist. + template + bool GetBool(const char (&aName)[N]) const { + const nsCString& value = Get(aName); + return ParseBool(value); + } + + // Returns bool value of `aName` feature. + // If the feature doesn't exist, returns `aDefault`. + // + // If `aPresenceFlag` is provided and the feature exists, it's set to `true`. + // (note that the value isn't overwritten if the feature doesn't exist) + template + bool GetBoolWithDefault(const char (&aName)[N], bool aDefault, + bool* aPresenceFlag = nullptr) const { + MOZ_ASSERT(IsLowerCase(aName)); + nsLiteralCString name(aName); + auto p = tokenizedFeatures_.lookup(name); + if (p.found()) { + if (aPresenceFlag) { + *aPresenceFlag = true; + } + return ParseBool(p->value()); + } + return aDefault; + } + + // Remove the feature from the map. + template + void Remove(const char (&aName)[N]) { + MOZ_ASSERT(IsLowerCase(aName)); + nsLiteralCString name(aName); + tokenizedFeatures_.remove(name); + } + + // Returns true if there was no feature specified, or all features are + // removed by `Remove`. + // + // Note that this can be true even if `aFeatures` parameter of `Tokenize` + // is not empty, in case it contains no feature with non-empty name. + bool IsEmpty() const { return tokenizedFeatures_.empty(); } + + // Stringify the map into `aOutput`. + // The result can be parsed again with `Tokenize`. + void Stringify(nsAutoCString& aOutput); + + private: +#ifdef DEBUG + // Returns true if `text` does not contain any character that gets modified by + // `ToLowerCase`. + static bool IsLowerCase(const char* text); +#endif + + static int32_t ParseIntegerWithFallback(const nsCString& aValue); + static bool ParseBool(const nsCString& aValue); + + // A map from feature name to feature value. + // If value is not provided, it's empty string. + mozilla::HashMap tokenizedFeatures_; +}; + +} // namespace dom +} // namespace mozilla + +#endif // #ifndef mozilla_dom_WindowFeatures_h diff --git a/dom/base/moz.build b/dom/base/moz.build index 0411561e121b..1016f7db4496 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -253,6 +253,7 @@ EXPORTS.mozilla.dom += [ 'UserActivation.h', 'ViewportMetaData.h', 'VisualViewport.h', + 'WindowFeatures.h', 'WindowOrientationObserver.h', 'WindowProxyHolder.h', ] @@ -425,6 +426,7 @@ UNIFIED_SOURCES += [ 'ViewportMetaData.cpp', 'VisualViewport.cpp', 'WindowDestroyedEvent.cpp', + 'WindowFeatures.cpp', 'WindowNamedPropertiesHandler.cpp', 'WindowOrientationObserver.cpp', 'XPathGenerator.cpp', diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 2c8855b05706..d0b32b1e720d 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -1181,11 +1181,14 @@ nsContentUtils::InternalSerializeAutocompleteAttribute( } // Parse an integer according to HTML spec -int32_t nsContentUtils::ParseHTMLInteger(const nsAString& aValue, - ParseHTMLIntegerResultFlags* aResult) { +template +int32_t nsContentUtils::ParseHTMLIntegerImpl( + const StringT& aValue, ParseHTMLIntegerResultFlags* aResult) { + using CharT = typename StringT::char_type; + int result = eParseHTMLInteger_NoFlags; - nsAString::const_iterator iter, end; + typename StringT::const_iterator iter, end; aValue.BeginReading(iter); aValue.EndReading(end); @@ -1201,11 +1204,11 @@ int32_t nsContentUtils::ParseHTMLInteger(const nsAString& aValue, } int sign = 1; - if (*iter == char16_t('-')) { + if (*iter == CharT('-')) { sign = -1; result |= eParseHTMLInteger_Negative; ++iter; - } else if (*iter == char16_t('+')) { + } else if (*iter == CharT('+')) { result |= eParseHTMLInteger_NonStandard; ++iter; } @@ -1216,7 +1219,7 @@ int32_t nsContentUtils::ParseHTMLInteger(const nsAString& aValue, // Check for leading zeros first. uint64_t leadingZeros = 0; while (iter != end) { - if (*iter != char16_t('0')) { + if (*iter != CharT('0')) { break; } @@ -1226,8 +1229,8 @@ int32_t nsContentUtils::ParseHTMLInteger(const nsAString& aValue, } while (iter != end) { - if (*iter >= char16_t('0') && *iter <= char16_t('9')) { - value = (value * 10) + (*iter - char16_t('0')) * sign; + if (*iter >= CharT('0') && *iter <= CharT('9')) { + value = (value * 10) + (*iter - CharT('0')) * sign; ++iter; if (!value.isValid()) { result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorOverflow; @@ -1257,6 +1260,17 @@ int32_t nsContentUtils::ParseHTMLInteger(const nsAString& aValue, return value.isValid() ? value.value() : 0; } +// Parse an integer according to HTML spec +int32_t nsContentUtils::ParseHTMLInteger(const nsAString& aValue, + ParseHTMLIntegerResultFlags* aResult) { + return ParseHTMLIntegerImpl(aValue, aResult); +} + +int32_t nsContentUtils::ParseHTMLInteger(const nsACString& aValue, + ParseHTMLIntegerResultFlags* aResult) { + return ParseHTMLIntegerImpl(aValue, aResult); +} + #define SKIP_WHITESPACE(iter, end_iter, end_res) \ while ((iter) != (end_iter) && nsCRT::IsAsciiSpace(*(iter))) { \ ++(iter); \ diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 285b0147d4eb..ec0d74d66c58 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -614,7 +614,12 @@ class nsContentUtils { enum ParseHTMLIntegerResultFlags { eParseHTMLInteger_NoFlags = 0, // eParseHTMLInteger_NonStandard is set if the string representation of the - // integer was not the canonical one (e.g. had extra leading '+' or '0'). + // integer was not the canonical one, but matches at least one of the + // following: + // * had leading whitespaces + // * had '+' sign + // * had leading '0' + // * was '-0' eParseHTMLInteger_NonStandard = 1 << 0, eParseHTMLInteger_DidNotConsumeAllInput = 1 << 1, // Set if one or more error flags were set. @@ -626,7 +631,15 @@ class nsContentUtils { }; static int32_t ParseHTMLInteger(const nsAString& aValue, ParseHTMLIntegerResultFlags* aResult); + static int32_t ParseHTMLInteger(const nsACString& aValue, + ParseHTMLIntegerResultFlags* aResult); + private: + template + static int32_t ParseHTMLIntegerImpl(const StringT& aValue, + ParseHTMLIntegerResultFlags* aResult); + + public: /** * Parse a margin string of format 'top, right, bottom, left' into * an nsIntMargin. diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index c160b600d288..99311b0b1d72 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -41,6 +41,7 @@ #include "mozilla/dom/TimeoutHandler.h" #include "mozilla/dom/TimeoutManager.h" #include "mozilla/dom/WindowContext.h" +#include "mozilla/dom/WindowFeatures.h" // WindowFeatures #include "mozilla/dom/WindowProxyHolder.h" #include "mozilla/IntegerPrintfMacros.h" #if defined(MOZ_WIDGET_ANDROID) @@ -76,7 +77,6 @@ #include "jsfriendapi.h" #include "js/PropertySpec.h" #include "js/Wrapper.h" -#include "nsCharSeparatedTokenizer.h" #include "nsReadableUtils.h" #include "nsJSEnvironment.h" #include "mozilla/dom/ScriptSettings.h" @@ -7019,34 +7019,32 @@ nsresult nsGlobalWindowOuter::OpenInternal( NS_ASSERTION(mDocShell, "Must have docshell here"); - nsAutoCString options; + NS_ConvertUTF16toUTF8 optionsUtf8(aOptions); + + WindowFeatures features; + if (!features.Tokenize(optionsUtf8)) { + return NS_ERROR_FAILURE; + } + bool forceNoOpener = aForceNoOpener; + if (features.Exists("noopener")) { + forceNoOpener = features.GetBool("noopener"); + features.Remove("noopener"); + } + bool forceNoReferrer = false; - // Unlike other window flags, "noopener" comes from splitting on commas with - // HTML whitespace trimming... - nsCharSeparatedTokenizerTemplate tok( - aOptions, ','); - while (tok.hasMoreTokens()) { - auto nextTok = tok.nextToken(); - if (nextTok.EqualsLiteral("noopener")) { - forceNoOpener = true; - continue; - } - if (StaticPrefs::dom_window_open_noreferrer_enabled() && - nextTok.LowerCaseEqualsLiteral("noreferrer")) { - forceNoReferrer = true; + if (features.Exists("noreferrer")) { + forceNoReferrer = features.GetBool("noreferrer"); + if (forceNoReferrer) { // noreferrer implies noopener forceNoOpener = true; - continue; } - // Want to create a copy of the options without 'noopener' because having - // 'noopener' in the options affects other window features. - if (!options.IsEmpty()) { - options.Append(','); - } - AppendUTF16toUTF8(nextTok, options); + features.Remove("noreferrer"); } + nsAutoCString options; + features.Stringify(options); + // If current's top-level browsing context's active document's // cross-origin-opener-policy is "same-origin" or "same-origin + COEP" then // if currentDoc's origin is not same origin with currentDoc's top-level diff --git a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-innerheight-innerwidth.html.ini b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-innerheight-innerwidth.html.ini index dbf12eceab84..3445ae8c8eb6 100644 --- a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-innerheight-innerwidth.html.ini +++ b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-innerheight-innerwidth.html.ini @@ -3,10 +3,14 @@ if webrender and not debug: bug 1425588 if (os == "android") and e10s: bug 1550895 (frequently fails on geckoview) ["innerwidth==401" should set width of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL ["innerheight==402" should set height of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL ["INNERHEIGHT=402" should set height of opened window] expected: diff --git a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-noopener.html.ini b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-noopener.html.ini index 4930c51c2112..1be9eb56b198 100644 --- a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-noopener.html.ini +++ b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-noopener.html.ini @@ -1,20 +1,2 @@ [open-features-tokenization-noopener.html] disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1364696 - [tokenization should skip window features separators before `name`] - expected: FAIL - - [feature `name` should be converted to ASCII lowercase] - expected: FAIL - - [after `name`, tokenization should skip window features separators that are not "=" or ","] - expected: FAIL - - [Tokenizing should ignore window feature separators except "," after initial "=" and before value] - expected: FAIL - - [Tokenizing should read characters until first window feature separator as `value`] - expected: FAIL - - ["noopener" should be based on name (key), not value] - expected: FAIL - diff --git a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-noreferrer.html.ini b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-noreferrer.html.ini index 67a6ebaab506..7756183174fe 100644 --- a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-noreferrer.html.ini +++ b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-noreferrer.html.ini @@ -1,22 +1,3 @@ [open-features-tokenization-noreferrer.html] expected: if webrender and (os == "linux"): ["OK", "TIMEOUT", "CRASH"] - - [Tokenizing "noreferrer" should ignore window feature separators except "," after initial "=" and before value] - expected: FAIL - - [Tokenizing "noreferrer" should read characters until first window feature separator as `value`] - expected: FAIL - - [After "noreferrer", tokenization should skip window features separators that are not "=" or ","] - expected: FAIL - - [Integer values other than 0 should activate the feature] - expected: FAIL - - [Tokenization of "noreferrer" should skip window features separators before feature] - expected: FAIL - - [Feature "noreferrer" should be converted to ASCII lowercase] - expected: FAIL - diff --git a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-screenx-screeny.html.ini b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-screenx-screeny.html.ini index f34745d53747..bb38619bc8a6 100644 --- a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-screenx-screeny.html.ini +++ b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-screenx-screeny.html.ini @@ -2,69 +2,5 @@ disabled: if webrender and not debug: bug 1425588 if verify and (os == "linux") and not debug: fails in verify mode - ["screenx==141" should set left position of opened window] - expected: FAIL - - ["screeny==142" should set top position of opened window] - expected: FAIL - - ["screenx=141" should set left position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - ["screeny=142" should set top position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - ["\\nscreenx= 141" should set left position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - [" screeny = 142" should set top position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - ["screenX=141" should set left position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - [",screenx=141,," should set left position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - [",screeny=142,," should set top position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - ["screenY=142" should set top position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - [" screenx = 141" should set left position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - ["SCREENX=141" should set left position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - ["SCREENY=142" should set top position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL - - ["\\nscreeny= 142" should set top position of opened window] - expected: - if (os == "android") and not e10s: FAIL - if (os == "android") and e10s: FAIL + if os == "android": frequently hits timeout diff --git a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-top-left.html.ini b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-top-left.html.ini index 25380f83e3a5..98e6ebb3425a 100644 --- a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-top-left.html.ini +++ b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-top-left.html.ini @@ -3,13 +3,19 @@ if webrender and not debug: bug 1425588 if verify and (os == "linux") and not debug: fails in verify mode ["left==141" should set left position of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL ["top==142" should set top position of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL ["top=152==left=152" should set top and left position of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL [",left=141,," should set left position of opened window] expected: diff --git a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-width-height.html.ini b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-width-height.html.ini index 9385f41fa1c6..1d32c8c9897d 100644 --- a/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-width-height.html.ini +++ b/testing/web-platform/meta/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-width-height.html.ini @@ -3,16 +3,27 @@ if webrender and not debug: bug 1425588 if (os == "android") and e10s: bug 1550895 (frequently fails on geckoview) ["width==401" should set width of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL ["height==402" should set height of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL + if devedition and os == "win" and bits == 32: FAIL # bug 1540551 ["height==402 width = 401" should set height and width of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL + if devedition and os == "win" and bits == 32: FAIL # bug 1540551 [",height=402,,width==401" should set height and width of opened window] - expected: FAIL + expected: + if (os == "android") and not e10s: FAIL + if (os == "android") and e10s: FAIL + if devedition and os == "win" and bits == 32: FAIL # bug 1540551 ["\\nheight= 402" should set height of opened window] expected: diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp index b6054a02b968..ee5d410a46b5 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp +++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp @@ -317,9 +317,7 @@ struct SizeSpec { mOuterHeightSpecified(false), mInnerWidthSpecified(false), mInnerHeightSpecified(false), - mLockAspectRatio(false), - mUseDefaultWidth(false), - mUseDefaultHeight(false) {} + mLockAspectRatio(false) {} int32_t mLeft; int32_t mTop; @@ -336,11 +334,6 @@ struct SizeSpec { bool mInnerHeightSpecified; bool mLockAspectRatio; - // If these booleans are true, don't look at the corresponding width values - // even if they're specified -- they'll be bogus - bool mUseDefaultWidth; - bool mUseDefaultHeight; - bool PositionSpecified() const { return mLeftSpecified || mTopSpecified; } bool SizeSpecified() const { return WidthSpecified() || HeightSpecified(); } @@ -411,8 +404,7 @@ static bool CheckUserContextCompatibility(nsIDocShell* aDocShell) { return subjectPrincipal->GetUserContextId() == userContextId; } -nsresult nsWindowWatcher::CreateChromeWindow(const nsACString& aFeatures, - nsIWebBrowserChrome* aParentChrome, +nsresult nsWindowWatcher::CreateChromeWindow(nsIWebBrowserChrome* aParentChrome, uint32_t aChromeFlags, nsIOpenWindowInfo* aOpenWindowInfo, nsIWebBrowserChrome** aResult) { @@ -444,21 +436,18 @@ nsresult nsWindowWatcher::CreateChromeWindow(const nsACString& aFeatures, * the size. * * @param aFeatures - * The features string that was used to open the window. + * The features that was used to open the window. * @param aTreeOwner * The nsIDocShellTreeOwner of the newly opened window. If null, * this function is a no-op. */ void nsWindowWatcher::MaybeDisablePersistence( - const nsACString& aFeatures, nsIDocShellTreeOwner* aTreeOwner) { + const SizeSpec& sizeSpec, nsIDocShellTreeOwner* aTreeOwner) { if (!aTreeOwner) { return; } - // At the moment, the strings "height=" or "width=" never happen - // outside a size specification, so we can do this the Q&D way. - if (PL_strcasestr(aFeatures.BeginReading(), "width=") || - PL_strcasestr(aFeatures.BeginReading(), "height=")) { + if (sizeSpec.SizeSpecified()) { aTreeOwner->SetPersistence(false, false, false); } } @@ -523,10 +512,13 @@ nsWindowWatcher::OpenWindowWithRemoteTab(nsIRemoteTab* aRemoteTab, return NS_ERROR_UNEXPECTED; } - SizeSpec sizeSpec; - CalcSizeSpec(aFeatures, sizeSpec); + WindowFeatures features; + features.Tokenize(aFeatures); - uint32_t chromeFlags = CalculateChromeFlagsForChild(aFeatures, sizeSpec); + SizeSpec sizeSpec; + CalcSizeSpec(features, sizeSpec); + + uint32_t chromeFlags = CalculateChromeFlagsForChild(features, sizeSpec); if (isPrivateBrowsingWindow) { chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; @@ -543,7 +535,7 @@ nsWindowWatcher::OpenWindowWithRemoteTab(nsIRemoteTab* aRemoteTab, nsCOMPtr parentChrome(do_GetInterface(parentTreeOwner)); nsCOMPtr newWindowChrome; - CreateChromeWindow(aFeatures, parentChrome, chromeFlags, aOpenWindowInfo, + CreateChromeWindow(parentChrome, chromeFlags, aOpenWindowInfo, getter_AddRefs(newWindowChrome)); if (NS_WARN_IF(!newWindowChrome)) { @@ -574,7 +566,7 @@ nsWindowWatcher::OpenWindowWithRemoteTab(nsIRemoteTab* aRemoteTab, // that will also run with out-of-process tabs. MOZ_ASSERT(chromeContext->UseRemoteTabs()); - MaybeDisablePersistence(aFeatures, chromeTreeOwner); + MaybeDisablePersistence(sizeSpec, chromeTreeOwner); SizeOpenedWindow(chromeTreeOwner, parentWindowOuter, false, sizeSpec, Some(aOpenerFullZoom)); @@ -606,7 +598,6 @@ nsresult nsWindowWatcher::OpenWindowInternal( uint32_t chromeFlags; nsAutoString name; // string version of aName - nsAutoCString features; // string version of aFeatures nsCOMPtr uriToLoad; // from aUrl, if any nsCOMPtr parentTreeOwner; // from the parent window, if any @@ -644,11 +635,13 @@ nsresult nsWindowWatcher::OpenWindowInternal( name.SetIsVoid(true); } + WindowFeatures features; + nsAutoCString featuresStr; if (aFeatures) { - features.Assign(aFeatures); - features.StripWhitespace(); + featuresStr.Assign(aFeatures); + features.Tokenize(featuresStr); } else { - features.SetIsVoid(true); + featuresStr.SetIsVoid(true); } RefPtr parentBC( @@ -839,8 +832,8 @@ nsresult nsWindowWatcher::OpenWindowInternal( if (provider) { rv = provider->ProvideWindow(openWindowInfo, chromeFlags, aCalledFromJS, sizeSpec.WidthSpecified(), uriToLoad, name, - features, aForceNoOpener, aForceNoReferrer, - aLoadState, &windowIsNew, + featuresStr, aForceNoOpener, + aForceNoReferrer, aLoadState, &windowIsNew, getter_AddRefs(newBC)); if (NS_SUCCEEDED(rv) && newBC) { @@ -936,9 +929,8 @@ nsresult nsWindowWatcher::OpenWindowInternal( completely honest: we clear that indicator if the opener is chrome, so that the downstream consumer can treat the indicator to mean simply that the new window is subject to popup control. */ - rv = CreateChromeWindow(features, parentChrome, chromeFlags, - openWindowInfo, getter_AddRefs(newChrome)); - + rv = CreateChromeWindow(parentChrome, chromeFlags, openWindowInfo, + getter_AddRefs(newChrome)); if (parentTopInnerWindow) { parentTopInnerWindow->Resume(); } @@ -1031,7 +1023,7 @@ nsresult nsWindowWatcher::OpenWindowInternal( if (isNewToplevelWindow) { nsCOMPtr newTreeOwner; newDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner)); - MaybeDisablePersistence(features, newTreeOwner); + MaybeDisablePersistence(sizeSpec, newTreeOwner); } if (aDialog && aArgv) { @@ -1649,41 +1641,43 @@ nsresult nsWindowWatcher::URIfromURL(const char* aURL, return NS_NewURI(aURI, aURL, baseURI); } -#define NS_CALCULATE_CHROME_FLAG_FOR(feature, flag) \ - chromeFlags |= \ - WinHasOption(aFeatures, (feature), 0, &presenceFlag) ? (flag) : 0; // static uint32_t nsWindowWatcher::CalculateChromeFlagsHelper( - uint32_t aInitialFlags, const nsACString& aFeatures, - const SizeSpec& aSizeSpec, bool& presenceFlag, bool aHasChromeParent) { + uint32_t aInitialFlags, const WindowFeatures& aFeatures, + const SizeSpec& aSizeSpec, bool* presenceFlag, bool aHasChromeParent) { uint32_t chromeFlags = aInitialFlags; - // NS_CALCULATE_CHROME_FLAG_FOR requires aFeatures, presenceFlag, and - // chromeFlags to be in scope. + if (aFeatures.GetBoolWithDefault("titlebar", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + } + if (aFeatures.GetBoolWithDefault("close", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + } + if (aFeatures.GetBoolWithDefault("toolbar", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_TOOLBAR; + } + if (aFeatures.GetBoolWithDefault("location", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_LOCATIONBAR; + } + if (aFeatures.GetBoolWithDefault("personalbar", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR; + } + if (aFeatures.GetBoolWithDefault("status", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_STATUSBAR; + } + if (aFeatures.GetBoolWithDefault("menubar", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_MENUBAR; + } + if (aFeatures.GetBoolWithDefault("resizable", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RESIZE; + } + if (aFeatures.GetBoolWithDefault("minimizable", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_MIN; + } - NS_CALCULATE_CHROME_FLAG_FOR("titlebar", - nsIWebBrowserChrome::CHROME_TITLEBAR); - NS_CALCULATE_CHROME_FLAG_FOR("close", - nsIWebBrowserChrome::CHROME_WINDOW_CLOSE); - NS_CALCULATE_CHROME_FLAG_FOR("toolbar", nsIWebBrowserChrome::CHROME_TOOLBAR); - NS_CALCULATE_CHROME_FLAG_FOR("location", - nsIWebBrowserChrome::CHROME_LOCATIONBAR); - NS_CALCULATE_CHROME_FLAG_FOR("personalbar", - nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR); - NS_CALCULATE_CHROME_FLAG_FOR("status", nsIWebBrowserChrome::CHROME_STATUSBAR); - NS_CALCULATE_CHROME_FLAG_FOR("menubar", nsIWebBrowserChrome::CHROME_MENUBAR); - NS_CALCULATE_CHROME_FLAG_FOR("resizable", - nsIWebBrowserChrome::CHROME_WINDOW_RESIZE); - NS_CALCULATE_CHROME_FLAG_FOR("minimizable", - nsIWebBrowserChrome::CHROME_WINDOW_MIN); - - // default scrollbar to "on," unless explicitly turned off - bool scrollbarsPresent = false; - if (WinHasOption(aFeatures, "scrollbars", 1, &scrollbarsPresent) || - !scrollbarsPresent) { + if (aFeatures.GetBoolWithDefault("scrollbars", true, presenceFlag)) { chromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS; } - presenceFlag = presenceFlag || scrollbarsPresent; if (aHasChromeParent) { return chromeFlags; @@ -1739,37 +1733,32 @@ uint32_t nsWindowWatcher::EnsureFlagsSafeForContent(uint32_t aChromeFlags, } // static -bool nsWindowWatcher::ShouldOpenPopup(const nsACString& aFeatures, +bool nsWindowWatcher::ShouldOpenPopup(const WindowFeatures& aFeatures, const SizeSpec& aSizeSpec) { - if (aFeatures.IsVoid()) { + if (aFeatures.IsEmpty()) { return false; } // Follow Google Chrome's behavior that opens a popup depending on // the following features. - bool unused; - if (!WinHasOption(aFeatures, "location", 0, &unused) && - !WinHasOption(aFeatures, "toolbar", 0, &unused)) { + if (!aFeatures.GetBoolWithDefault("location", false) && + !aFeatures.GetBoolWithDefault("toolbar", false)) { return true; } - if (!WinHasOption(aFeatures, "menubar", 0, &unused)) { + if (!aFeatures.GetBoolWithDefault("menubar", false)) { return true; } - // `resizable` defaults to true. - // Should open popup only when explicitly specified to 0. - bool resizablePresent = false; - if (!WinHasOption(aFeatures, "resizable", 0, &resizablePresent) && - resizablePresent) { + if (!aFeatures.GetBoolWithDefault("resizable", true)) { return true; } - if (!WinHasOption(aFeatures, "scrollbars", 0, &unused)) { + if (!aFeatures.GetBoolWithDefault("scrollbars", false)) { return true; } - if (!WinHasOption(aFeatures, "status", 0, &unused)) { + if (!aFeatures.GetBoolWithDefault("status", false)) { return true; } @@ -1791,15 +1780,13 @@ bool nsWindowWatcher::ShouldOpenPopup(const nsACString& aFeatures, */ // static uint32_t nsWindowWatcher::CalculateChromeFlagsForChild( - const nsACString& aFeatures, const SizeSpec& aSizeSpec) { - if (aFeatures.IsVoid()) { + const WindowFeatures& aFeatures, const SizeSpec& aSizeSpec) { + if (aFeatures.IsEmpty()) { return nsIWebBrowserChrome::CHROME_ALL; } - bool presenceFlag = false; - uint32_t chromeFlags = - CalculateChromeFlagsHelper(nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, - aFeatures, aSizeSpec, presenceFlag); + uint32_t chromeFlags = CalculateChromeFlagsHelper( + nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, aFeatures, aSizeSpec); return EnsureFlagsSafeForContent(chromeFlags); } @@ -1817,7 +1804,7 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForChild( */ // static uint32_t nsWindowWatcher::CalculateChromeFlagsForParent( - mozIDOMWindowProxy* aParent, const nsACString& aFeatures, + mozIDOMWindowProxy* aParent, const WindowFeatures& aFeatures, const SizeSpec& aSizeSpec, bool aDialog, bool aChromeURL, bool aHasChromeParent, bool aCalledFromJS) { MOZ_ASSERT(XRE_IsParentProcess()); @@ -1827,7 +1814,7 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForParent( // The features string is made void by OpenWindowInternal // if nullptr was originally passed as the features string. - if (aFeatures.IsVoid()) { + if (aFeatures.IsEmpty()) { chromeFlags = nsIWebBrowserChrome::CHROME_ALL; if (aDialog) { chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | @@ -1846,29 +1833,29 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForParent( in the standards-compliant window.(normal)open. */ bool presenceFlag = false; - if (aDialog && WinHasOption(aFeatures, "all", 0, &presenceFlag)) { + if (aDialog && aFeatures.GetBoolWithDefault("all", false, &presenceFlag)) { chromeFlags = nsIWebBrowserChrome::CHROME_ALL; } /* Next, allow explicitly named options to override the initial settings */ chromeFlags = CalculateChromeFlagsHelper(chromeFlags, aFeatures, aSizeSpec, - presenceFlag, aHasChromeParent); + &presenceFlag, aHasChromeParent); // Determine whether the window is a private browsing window - chromeFlags |= WinHasOption(aFeatures, "private", 0, &presenceFlag) - ? nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW - : 0; - chromeFlags |= WinHasOption(aFeatures, "non-private", 0, &presenceFlag) - ? nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW - : 0; + if (aFeatures.GetBoolWithDefault("private", false, &presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + } + if (aFeatures.GetBoolWithDefault("non-private", false, &presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW; + } // Determine whether the window should have remote tabs. bool remote = BrowserTabsRemoteAutostart(); if (remote) { - remote = !WinHasOption(aFeatures, "non-remote", 0, &presenceFlag); + remote = !aFeatures.GetBoolWithDefault("non-remote", false, &presenceFlag); } else { - remote = WinHasOption(aFeatures, "remote", 0, &presenceFlag); + remote = aFeatures.GetBoolWithDefault("remote", false, &presenceFlag); } if (remote) { @@ -1879,18 +1866,19 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForParent( bool fission = StaticPrefs::fission_autostart(); if (fission) { - fission = !WinHasOption(aFeatures, "non-fission", 0, &presenceFlag); + fission = + !aFeatures.GetBoolWithDefault("non-fission", false, &presenceFlag); } else { - fission = WinHasOption(aFeatures, "fission", 0, &presenceFlag); + fission = aFeatures.GetBoolWithDefault("fission", false, &presenceFlag); } if (fission) { chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW; } - chromeFlags |= WinHasOption(aFeatures, "popup", 0, &presenceFlag) - ? nsIWebBrowserChrome::CHROME_WINDOW_POPUP - : 0; + if (aFeatures.GetBoolWithDefault("popup", false, &presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_POPUP; + } /* OK. Normal browser windows, in spite of a stated pattern of turning off @@ -1901,15 +1889,15 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForParent( // default titlebar and closebox to "on," if not mentioned at all if (!(chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_POPUP)) { - if (!PL_strcasestr(aFeatures.BeginReading(), "titlebar")) { + if (!aFeatures.Exists("titlebar")) { chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; } - if (!PL_strcasestr(aFeatures.BeginReading(), "close")) { + if (!aFeatures.Exists("close")) { chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; } } - if (aDialog && !aFeatures.IsVoid() && !presenceFlag) { + if (aDialog && !aFeatures.IsEmpty() && !presenceFlag) { chromeFlags = nsIWebBrowserChrome::CHROME_DEFAULT; } @@ -1917,35 +1905,35 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForParent( with the features that are more operating hints than appearance instructions. (Note modality implies dependence.) */ - if (WinHasOption(aFeatures, "alwaysLowered", 0, nullptr) || - WinHasOption(aFeatures, "z-lock", 0, nullptr)) { + if (aFeatures.GetBoolWithDefault("alwayslowered", false) || + aFeatures.GetBoolWithDefault("z-lock", false)) { chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; - } else if (WinHasOption(aFeatures, "alwaysRaised", 0, nullptr)) { + } else if (aFeatures.GetBoolWithDefault("alwaysraised", false)) { chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RAISED; } - chromeFlags |= WinHasOption(aFeatures, "suppressanimation", 0, nullptr) - ? nsIWebBrowserChrome::CHROME_SUPPRESS_ANIMATION - : 0; - chromeFlags |= WinHasOption(aFeatures, "alwaysontop", 0, nullptr) - ? nsIWebBrowserChrome::CHROME_ALWAYS_ON_TOP - : 0; - chromeFlags |= WinHasOption(aFeatures, "chrome", 0, nullptr) - ? nsIWebBrowserChrome::CHROME_OPENAS_CHROME - : 0; - chromeFlags |= WinHasOption(aFeatures, "extrachrome", 0, nullptr) - ? nsIWebBrowserChrome::CHROME_EXTRA - : 0; - chromeFlags |= WinHasOption(aFeatures, "centerscreen", 0, nullptr) - ? nsIWebBrowserChrome::CHROME_CENTER_SCREEN - : 0; - chromeFlags |= WinHasOption(aFeatures, "dependent", 0, nullptr) - ? nsIWebBrowserChrome::CHROME_DEPENDENT - : 0; - chromeFlags |= WinHasOption(aFeatures, "modal", 0, nullptr) - ? (nsIWebBrowserChrome::CHROME_MODAL | - nsIWebBrowserChrome::CHROME_DEPENDENT) - : 0; + if (aFeatures.GetBoolWithDefault("suppressanimation", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_SUPPRESS_ANIMATION; + } + if (aFeatures.GetBoolWithDefault("alwaysontop", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_ALWAYS_ON_TOP; + } + if (aFeatures.GetBoolWithDefault("chrome", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + if (aFeatures.GetBoolWithDefault("extrachrome", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_EXTRA; + } + if (aFeatures.GetBoolWithDefault("centerscreen", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_CENTER_SCREEN; + } + if (aFeatures.GetBoolWithDefault("dependent", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_DEPENDENT; + } + if (aFeatures.GetBoolWithDefault("modal", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_DEPENDENT; + } /* On mobile we want to ignore the dialog window feature, since the mobile UI does not provide any affordance for dialog windows. This does not interfere @@ -1957,18 +1945,18 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForParent( &disableDialogFeature); if (!disableDialogFeature) { - chromeFlags |= WinHasOption(aFeatures, "dialog", 0, nullptr) - ? nsIWebBrowserChrome::CHROME_OPENAS_DIALOG - : 0; + if (aFeatures.GetBoolWithDefault("dialog", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } } /* and dialogs need to have the last word. assume dialogs are dialogs, and opened as chrome, unless explicitly told otherwise. */ if (aDialog) { - if (!PL_strcasestr(aFeatures.BeginReading(), "dialog")) { + if (!aFeatures.Exists("dialog")) { chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; } - if (!PL_strcasestr(aFeatures.BeginReading(), "chrome")) { + if (!aFeatures.Exists("chrome")) { chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME; } } @@ -1992,63 +1980,6 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForParent( return chromeFlags; } -// static -int32_t nsWindowWatcher::WinHasOption(const nsACString& aOptions, - const char* aName, int32_t aDefault, - bool* aPresenceFlag) { - if (aOptions.IsEmpty()) { - return 0; - } - - const char* options = aOptions.BeginReading(); - char* comma; - char* equal; - int32_t found = 0; - -#ifdef DEBUG - NS_ASSERTION(nsAutoCString(aOptions).FindCharInSet(" \n\r\t") == kNotFound, - "There should be no whitespace in this string!"); -#endif - - while (true) { - comma = PL_strchr(options, ','); - if (comma) { - *comma = '\0'; - } - equal = PL_strchr(options, '='); - if (equal) { - *equal = '\0'; - } - if (nsCRT::strcasecmp(options, aName) == 0) { - if (aPresenceFlag) { - *aPresenceFlag = true; - } - if (equal) - if (*(equal + 1) == '*') { - found = aDefault; - } else if (nsCRT::strcasecmp(equal + 1, "yes") == 0) { - found = 1; - } else { - found = atoi(equal + 1); - } - else { - found = 1; - } - } - if (equal) { - *equal = '='; - } - if (comma) { - *comma = ','; - } - if (found || !comma) { - break; - } - options = comma + 1; - } - return found; -} - already_AddRefed nsWindowWatcher::GetBrowsingContextByName( const nsAString& aName, bool aForceNoOpener, BrowsingContext* aCurrentContext) { @@ -2080,70 +2011,148 @@ already_AddRefed nsWindowWatcher::GetBrowsingContextByName( } // static -void nsWindowWatcher::CalcSizeSpec(const nsACString& aFeatures, +void nsWindowWatcher::CalcSizeSpec(const WindowFeatures& aFeatures, SizeSpec& aResult) { - // Parse position spec, if any, from aFeatures - bool present; - int32_t temp; + // https://drafts.csswg.org/cssom-view/#set-up-browsing-context-features + // To set up browsing context features for a browsing context `target` given + // a map `tokenizedFeatures`: - present = false; - if ((temp = WinHasOption(aFeatures, "left", 0, &present)) || present) { - aResult.mLeft = temp; - } else if ((temp = WinHasOption(aFeatures, "screenX", 0, &present)) || - present) { - aResult.mLeft = temp; + // Step 1. Let `x` be null. + // (implicit) + + // Step 2. Let `y` be null. + // (implicit) + + // Step 3. Let `width` be null. + // (implicit) + + // Step 4. Let `height` be null. + // (implicit) + + // Step 5. If `tokenizedFeatures["left"]` exists: + if (aFeatures.Exists("left")) { + // Step 5.1. Set `x` to the result of invoking the rules for parsing + // integers on `tokenizedFeatures["left"]`. + // + // Step 5.2. If `x` is an error, set `x` to 0. + int32_t x = aFeatures.GetInt("left"); + + // Step 5.3. Optionally, clamp `x` in a user-agent-defined manner so that + // the window does not move outside the Web-exposed available screen area. + // (done later) + + // Step 5.4. Optionally, move `target`’s window such that the window’s + // left edge is at the horizontal coordinate `x` relative to the left edge + // of the Web-exposed screen area, measured in CSS pixels of target. + // The positive axis is rightward. + aResult.mLeft = x; + aResult.mLeftSpecified = true; } - aResult.mLeftSpecified = present; - present = false; - if ((temp = WinHasOption(aFeatures, "top", 0, &present)) || present) { - aResult.mTop = temp; - } else if ((temp = WinHasOption(aFeatures, "screenY", 0, &present)) || - present) { - aResult.mTop = temp; + // Step 6. If `tokenizedFeatures["top"]` exists: + if (aFeatures.Exists("top")) { + // Step 6.1. Set `y` to the result of invoking the rules for parsing + // integers on `tokenizedFeatures["top"]`. + // + // Step 6.2. If `y` is an error, set `y` to 0. + int32_t y = aFeatures.GetInt("top"); + + // Step 6.3. Optionally, clamp `y` in a user-agent-defined manner so that + // the window does not move outside the Web-exposed available screen area. + // (done later) + + // Step 6.4. Optionally, move `target`’s window such that the window’s top + // edge is at the vertical coordinate `y` relative to the top edge of the + // Web-exposed screen area, measured in CSS pixels of target. The positive + // axis is downward. + aResult.mTop = y; + aResult.mTopSpecified = true; } - aResult.mTopSpecified = present; - // Parse size spec, if any. Chrome size overrides content size. - if ((temp = WinHasOption(aFeatures, "outerWidth", INT32_MIN, nullptr))) { - if (temp == INT32_MIN) { - aResult.mUseDefaultWidth = true; - } else { - aResult.mOuterWidth = temp; + // Non-standard extension. + // See bug 1623826 + if (aFeatures.Exists("outerwidth")) { + int32_t width = aFeatures.GetInt("outerwidth"); + if (width) { + aResult.mOuterWidth = width; + aResult.mOuterWidthSpecified = true; } - aResult.mOuterWidthSpecified = true; - } else if ((temp = WinHasOption(aFeatures, "width", INT32_MIN, nullptr)) || - (temp = - WinHasOption(aFeatures, "innerWidth", INT32_MIN, nullptr))) { - if (temp == INT32_MIN) { - aResult.mUseDefaultWidth = true; - } else { - aResult.mInnerWidth = temp; - } - aResult.mInnerWidthSpecified = true; } - if ((temp = WinHasOption(aFeatures, "outerHeight", INT32_MIN, nullptr))) { - if (temp == INT32_MIN) { - aResult.mUseDefaultHeight = true; - } else { - aResult.mOuterHeight = temp; + if (!aResult.mOuterWidthSpecified) { + // Step 7. If `tokenizedFeatures["width"]` exists: + if (aFeatures.Exists("width")) { + // Step 7.1. Set `width` to the result of invoking the rules for parsing + // integers on `tokenizedFeatures["width"]`. + // + // Step 7.2. If `width` is an error, set `width` to 0. + int32_t width = aFeatures.GetInt("width"); + + // Step 7.3. If `width` is not 0: + if (width) { + // Step 7.3.1. Optionally, clamp `width` in a user-agent-defined manner + // so that the window does not get too small or bigger than the + // Web-exposed available screen area. + // (done later) + + // Step 7.3.2. Optionally, size `target`’s window by moving its right + // edge such that the distance between the left and right edges of the + // viewport are `width` CSS pixels of target. + aResult.mInnerWidth = width; + aResult.mInnerWidthSpecified = true; + + // Step 7.3.3. Optionally, move target’s window in a user-agent-defined + // manner so that it does not grow outside the Web-exposed available + // screen area. + // (done later) + } } - aResult.mOuterHeightSpecified = true; - } else if ((temp = WinHasOption(aFeatures, "height", INT32_MIN, nullptr)) || - (temp = - WinHasOption(aFeatures, "innerHeight", INT32_MIN, nullptr))) { - if (temp == INT32_MIN) { - aResult.mUseDefaultHeight = true; - } else { - aResult.mInnerHeight = temp; - } - aResult.mInnerHeightSpecified = true; } - if (WinHasOption(aFeatures, "lockaspectratio", 0, nullptr)) { - aResult.mLockAspectRatio = true; + // Non-standard extension. + // See bug 1623826 + if (aFeatures.Exists("outerheight")) { + int32_t height = aFeatures.GetInt("outerheight"); + if (height) { + aResult.mOuterHeight = height; + aResult.mOuterHeightSpecified = true; + } } + + if (!aResult.mOuterHeightSpecified) { + // Step 8. If `tokenizedFeatures["height"]` exists: + if (aFeatures.Exists("height")) { + // Step 8.1. Set `height` to the result of invoking the rules for parsing + // integers on `tokenizedFeatures["height"]`. + // + // Step 8.2. If `height` is an error, set `height` to 0. + int32_t height = aFeatures.GetInt("height"); + + // Step 8.3. If `height` is not 0: + if (height) { + // Step 8.3.1. Optionally, clamp `height` in a user-agent-defined manner + // so that the window does not get too small or bigger than the + // Web-exposed available screen area. + // (done later) + + // Step 8.3.2. Optionally, size `target`’s window by moving its bottom + // edge such that the distance between the top and bottom edges of the + // viewport are `height` CSS pixels of target. + aResult.mInnerHeight = height; + aResult.mInnerHeightSpecified = true; + + // Step 8.3.3. Optionally, move target’s window in a user-agent-defined + // manner so that it does not grow outside the Web-exposed available + // screen area. + // (done later) + } + } + } + + // NOTE: The value is handled only on chrome-priv code. + // See nsWindowWatcher::SizeOpenedWindow. + aResult.mLockAspectRatio = + aFeatures.GetBoolWithDefault("lockaspectratio", false); } /* Size and position a new window according to aSizeSpec. This method @@ -2233,30 +2242,18 @@ void nsWindowWatcher::SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner, // Set up width if (aSizeSpec.mOuterWidthSpecified) { - if (!aSizeSpec.mUseDefaultWidth) { - width = NSToIntRound(aSizeSpec.mOuterWidth * openerZoom); - } // Else specified to default; just use our existing width + width = NSToIntRound(aSizeSpec.mOuterWidth * openerZoom); } else if (aSizeSpec.mInnerWidthSpecified) { sizeChromeWidth = false; - if (aSizeSpec.mUseDefaultWidth) { - width = width - chromeWidth; - } else { - width = NSToIntRound(aSizeSpec.mInnerWidth * openerZoom); - } + width = NSToIntRound(aSizeSpec.mInnerWidth * openerZoom); } // Set up height if (aSizeSpec.mOuterHeightSpecified) { - if (!aSizeSpec.mUseDefaultHeight) { - height = NSToIntRound(aSizeSpec.mOuterHeight * openerZoom); - } // Else specified to default; just use our existing height + height = NSToIntRound(aSizeSpec.mOuterHeight * openerZoom); } else if (aSizeSpec.mInnerHeightSpecified) { sizeChromeHeight = false; - if (aSizeSpec.mUseDefaultHeight) { - height = height - chromeHeight; - } else { - height = NSToIntRound(aSizeSpec.mInnerHeight * openerZoom); - } + height = NSToIntRound(aSizeSpec.mInnerHeight * openerZoom); } bool positionSpecified = aSizeSpec.PositionSpecified(); diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.h b/toolkit/components/windowwatcher/nsWindowWatcher.h index 44d5237df1d1..ce864f014310 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.h +++ b/toolkit/components/windowwatcher/nsWindowWatcher.h @@ -25,6 +25,7 @@ #include "nsIRemoteTab.h" #include "nsPIWindowWatcher.h" #include "nsTArray.h" +#include "mozilla/dom/WindowFeatures.h" // mozilla::dom::WindowFeatures class nsIURI; class nsIDocShellTreeItem; @@ -86,23 +87,20 @@ class nsWindowWatcher : public nsIWindowWatcher, static nsresult URIfromURL(const char* aURL, mozIDOMWindowProxy* aParent, nsIURI** aURI); - static bool ShouldOpenPopup(const nsACString& aFeatures, + static bool ShouldOpenPopup(const mozilla::dom::WindowFeatures& aFeatures, const SizeSpec& aSizeSpec); - static uint32_t CalculateChromeFlagsForChild(const nsACString& aFeaturesStr, - const SizeSpec& aSizeSpec); + static uint32_t CalculateChromeFlagsForChild( + const mozilla::dom::WindowFeatures& aFeatures, const SizeSpec& aSizeSpec); - static uint32_t CalculateChromeFlagsForParent(mozIDOMWindowProxy* aParent, - const nsACString& aFeaturesStr, - const SizeSpec& aSizeSpec, - bool aDialog, bool aChromeURL, - bool aHasChromeParent, - bool aCalledFromJS); + static uint32_t CalculateChromeFlagsForParent( + mozIDOMWindowProxy* aParent, + const mozilla::dom::WindowFeatures& aFeatures, const SizeSpec& aSizeSpec, + bool aDialog, bool aChromeURL, bool aHasChromeParent, bool aCalledFromJS); - static int32_t WinHasOption(const nsACString& aOptions, const char* aName, - int32_t aDefault, bool* aPresenceFlag); /* Compute the right SizeSpec based on aFeatures */ - static void CalcSizeSpec(const nsACString& aFeatures, SizeSpec& aResult); + static void CalcSizeSpec(const mozilla::dom::WindowFeatures& aFeatures, + SizeSpec& aResult); static void SizeOpenedWindow( nsIDocShellTreeOwner* aTreeOwner, mozIDOMWindowProxy* aParent, bool aIsCallerChrome, const SizeSpec& aSizeSpec, @@ -113,20 +111,18 @@ class nsWindowWatcher : public nsIWindowWatcher, nsIDocShellTreeOwner** aResult); private: - nsresult CreateChromeWindow(const nsACString& aFeatures, - nsIWebBrowserChrome* aParentChrome, + nsresult CreateChromeWindow(nsIWebBrowserChrome* aParentChrome, uint32_t aChromeFlags, nsIOpenWindowInfo* aOpenWindowInfo, nsIWebBrowserChrome** aResult); - void MaybeDisablePersistence(const nsACString& aFeatures, + void MaybeDisablePersistence(const SizeSpec& sizeSpec, nsIDocShellTreeOwner* aTreeOwner); - static uint32_t CalculateChromeFlagsHelper(uint32_t aInitialFlags, - const nsACString& aFeatures, - const SizeSpec& aSizeSpec, - bool& presenceFlag, - bool aHasChromeParent = false); + static uint32_t CalculateChromeFlagsHelper( + uint32_t aInitialFlags, const mozilla::dom::WindowFeatures& aFeatures, + const SizeSpec& aSizeSpec, bool* presenceFlag = nullptr, + bool aHasChromeParent = false); static uint32_t EnsureFlagsSafeForContent(uint32_t aChromeFlags, bool aChromeURL = false);