1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "HTMLFrameSetElement.h"
8 #include "mozilla/dom/HTMLFrameSetElementBinding.h"
9 #include "mozilla/dom/Document.h"
10 #include "mozilla/dom/EventHandlerBinding.h"
11 #include "nsGlobalWindow.h"
12 #include "mozilla/UniquePtrExtensions.h"
13 #include "nsAttrValueOrString.h"
14
15 NS_IMPL_NS_NEW_HTML_ELEMENT(FrameSet)
16
17 namespace mozilla::dom {
18
19 HTMLFrameSetElement::~HTMLFrameSetElement() = default;
20
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)21 JSObject* HTMLFrameSetElement::WrapNode(JSContext* aCx,
22 JS::Handle<JSObject*> aGivenProto) {
23 return HTMLFrameSetElement_Binding::Wrap(aCx, this, aGivenProto);
24 }
25
NS_IMPL_ELEMENT_CLONE(HTMLFrameSetElement)26 NS_IMPL_ELEMENT_CLONE(HTMLFrameSetElement)
27
28 nsresult HTMLFrameSetElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
29 const nsAttrValueOrString* aValue,
30 bool aNotify) {
31 /* The main goal here is to see whether the _number_ of rows or
32 * columns has changed. If it has, we need to reframe; otherwise
33 * we want to reflow.
34 * Ideally, the style hint would be changed back to reflow after the reframe
35 * has been performed. Unfortunately, however, the reframe will be performed
36 * by the call to MutationObservers::AttributeChanged, which occurs *after*
37 * AfterSetAttr is called, leaving us with no convenient way of changing the
38 * value back to reflow afterwards. However,
39 * MutationObservers::AttributeChanged is effectively the only consumer of
40 * this value, so as long as we always set the value correctly here, we should
41 * be fine.
42 */
43 mCurrentRowColHint = NS_STYLE_HINT_REFLOW;
44 if (aNamespaceID == kNameSpaceID_None) {
45 if (aName == nsGkAtoms::rows) {
46 if (aValue) {
47 int32_t oldRows = mNumRows;
48 ParseRowCol(aValue->String(), mNumRows, &mRowSpecs);
49
50 if (mNumRows != oldRows) {
51 mCurrentRowColHint = nsChangeHint_ReconstructFrame;
52 }
53 }
54 } else if (aName == nsGkAtoms::cols) {
55 if (aValue) {
56 int32_t oldCols = mNumCols;
57 ParseRowCol(aValue->String(), mNumCols, &mColSpecs);
58
59 if (mNumCols != oldCols) {
60 mCurrentRowColHint = nsChangeHint_ReconstructFrame;
61 }
62 }
63 }
64 }
65
66 return nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName, aValue,
67 aNotify);
68 }
69
GetRowSpec(int32_t * aNumValues,const nsFramesetSpec ** aSpecs)70 nsresult HTMLFrameSetElement::GetRowSpec(int32_t* aNumValues,
71 const nsFramesetSpec** aSpecs) {
72 MOZ_ASSERT(aNumValues, "Must have a pointer to an integer here!");
73 MOZ_ASSERT(aSpecs, "Must have a pointer to an array of nsFramesetSpecs");
74 *aNumValues = 0;
75 *aSpecs = nullptr;
76
77 if (!mRowSpecs) {
78 const nsAttrValue* value = GetParsedAttr(nsGkAtoms::rows);
79 if (value && value->Type() == nsAttrValue::eString) {
80 nsresult rv = ParseRowCol(value->GetStringValue(), mNumRows, &mRowSpecs);
81 NS_ENSURE_SUCCESS(rv, rv);
82 }
83
84 if (!mRowSpecs) { // we may not have had an attr or had an empty attr
85 mRowSpecs = MakeUnique<nsFramesetSpec[]>(1);
86 mNumRows = 1;
87 mRowSpecs[0].mUnit = eFramesetUnit_Relative;
88 mRowSpecs[0].mValue = 1;
89 }
90 }
91
92 *aSpecs = mRowSpecs.get();
93 *aNumValues = mNumRows;
94 return NS_OK;
95 }
96
GetColSpec(int32_t * aNumValues,const nsFramesetSpec ** aSpecs)97 nsresult HTMLFrameSetElement::GetColSpec(int32_t* aNumValues,
98 const nsFramesetSpec** aSpecs) {
99 MOZ_ASSERT(aNumValues, "Must have a pointer to an integer here!");
100 MOZ_ASSERT(aSpecs, "Must have a pointer to an array of nsFramesetSpecs");
101 *aNumValues = 0;
102 *aSpecs = nullptr;
103
104 if (!mColSpecs) {
105 const nsAttrValue* value = GetParsedAttr(nsGkAtoms::cols);
106 if (value && value->Type() == nsAttrValue::eString) {
107 nsresult rv = ParseRowCol(value->GetStringValue(), mNumCols, &mColSpecs);
108 NS_ENSURE_SUCCESS(rv, rv);
109 }
110
111 if (!mColSpecs) { // we may not have had an attr or had an empty attr
112 mColSpecs = MakeUnique<nsFramesetSpec[]>(1);
113 mNumCols = 1;
114 mColSpecs[0].mUnit = eFramesetUnit_Relative;
115 mColSpecs[0].mValue = 1;
116 }
117 }
118
119 *aSpecs = mColSpecs.get();
120 *aNumValues = mNumCols;
121 return NS_OK;
122 }
123
ParseAttribute(int32_t aNamespaceID,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aMaybeScriptedPrincipal,nsAttrValue & aResult)124 bool HTMLFrameSetElement::ParseAttribute(int32_t aNamespaceID,
125 nsAtom* aAttribute,
126 const nsAString& aValue,
127 nsIPrincipal* aMaybeScriptedPrincipal,
128 nsAttrValue& aResult) {
129 if (aNamespaceID == kNameSpaceID_None) {
130 if (aAttribute == nsGkAtoms::bordercolor) {
131 return aResult.ParseColor(aValue);
132 }
133 if (aAttribute == nsGkAtoms::frameborder) {
134 return nsGenericHTMLElement::ParseFrameborderValue(aValue, aResult);
135 }
136 if (aAttribute == nsGkAtoms::border) {
137 return aResult.ParseIntWithBounds(aValue, 0, 100);
138 }
139 }
140
141 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
142 aMaybeScriptedPrincipal, aResult);
143 }
144
GetAttributeChangeHint(const nsAtom * aAttribute,int32_t aModType) const145 nsChangeHint HTMLFrameSetElement::GetAttributeChangeHint(
146 const nsAtom* aAttribute, int32_t aModType) const {
147 nsChangeHint retval =
148 nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
149 if (aAttribute == nsGkAtoms::rows || aAttribute == nsGkAtoms::cols) {
150 retval |= mCurrentRowColHint;
151 }
152 return retval;
153 }
154
155 /**
156 * Translate a "rows" or "cols" spec into an array of nsFramesetSpecs
157 */
ParseRowCol(const nsAString & aValue,int32_t & aNumSpecs,UniquePtr<nsFramesetSpec[]> * aSpecs)158 nsresult HTMLFrameSetElement::ParseRowCol(const nsAString& aValue,
159 int32_t& aNumSpecs,
160 UniquePtr<nsFramesetSpec[]>* aSpecs) {
161 if (aValue.IsEmpty()) {
162 aNumSpecs = 0;
163 *aSpecs = nullptr;
164 return NS_OK;
165 }
166
167 static const char16_t sAster('*');
168 static const char16_t sPercent('%');
169 static const char16_t sComma(',');
170
171 nsAutoString spec(aValue);
172 // remove whitespace (Bug 33699) and quotation marks (bug 224598)
173 // also remove leading/trailing commas (bug 31482)
174 spec.StripChars(" \n\r\t\"\'");
175 spec.Trim(",");
176
177 // Count the commas. Don't count more than X commas (bug 576447).
178 static_assert(NS_MAX_FRAMESET_SPEC_COUNT * sizeof(nsFramesetSpec) < (1 << 30),
179 "Too many frameset specs allowed to allocate");
180 int32_t commaX = spec.FindChar(sComma);
181 int32_t count = 1;
182 while (commaX != kNotFound && count < NS_MAX_FRAMESET_SPEC_COUNT) {
183 count++;
184 commaX = spec.FindChar(sComma, commaX + 1);
185 }
186
187 auto specs = MakeUniqueFallible<nsFramesetSpec[]>(count);
188 if (!specs) {
189 *aSpecs = nullptr;
190 aNumSpecs = 0;
191 return NS_ERROR_OUT_OF_MEMORY;
192 }
193
194 // Pre-grab the compat mode; we may need it later in the loop.
195 bool isInQuirks = InNavQuirksMode(OwnerDoc());
196
197 // Parse each comma separated token
198
199 int32_t start = 0;
200 int32_t specLen = spec.Length();
201
202 for (int32_t i = 0; i < count; i++) {
203 // Find our comma
204 commaX = spec.FindChar(sComma, start);
205 NS_ASSERTION(i == count - 1 || commaX != kNotFound,
206 "Failed to find comma, somehow");
207 int32_t end = (commaX == kNotFound) ? specLen : commaX;
208
209 // Note: If end == start then it means that the token has no
210 // data in it other than a terminating comma (or the end of the spec).
211 // So default to a fixed width of 0.
212 specs[i].mUnit = eFramesetUnit_Fixed;
213 specs[i].mValue = 0;
214 if (end > start) {
215 int32_t numberEnd = end;
216 char16_t ch = spec.CharAt(numberEnd - 1);
217 if (sAster == ch) {
218 specs[i].mUnit = eFramesetUnit_Relative;
219 numberEnd--;
220 } else if (sPercent == ch) {
221 specs[i].mUnit = eFramesetUnit_Percent;
222 numberEnd--;
223 // check for "*%"
224 if (numberEnd > start) {
225 ch = spec.CharAt(numberEnd - 1);
226 if (sAster == ch) {
227 specs[i].mUnit = eFramesetUnit_Relative;
228 numberEnd--;
229 }
230 }
231 }
232
233 // Translate value to an integer
234 nsAutoString token;
235 spec.Mid(token, start, numberEnd - start);
236
237 // Treat * as 1*
238 if ((eFramesetUnit_Relative == specs[i].mUnit) && (0 == token.Length())) {
239 specs[i].mValue = 1;
240 } else {
241 // Otherwise just convert to integer.
242 nsresult err;
243 specs[i].mValue = token.ToInteger(&err);
244 if (NS_FAILED(err)) {
245 specs[i].mValue = 0;
246 }
247 }
248
249 // Treat 0* as 1* in quirks mode (bug 40383)
250 if (isInQuirks) {
251 if ((eFramesetUnit_Relative == specs[i].mUnit) &&
252 (0 == specs[i].mValue)) {
253 specs[i].mValue = 1;
254 }
255 }
256
257 // Catch zero and negative frame sizes for Nav compatibility
258 // Nav resized absolute and relative frames to "1" and
259 // percent frames to an even percentage of the width
260 //
261 // if (isInQuirks && (specs[i].mValue <= 0)) {
262 // if (eFramesetUnit_Percent == specs[i].mUnit) {
263 // specs[i].mValue = 100 / count;
264 // } else {
265 // specs[i].mValue = 1;
266 // }
267 //} else {
268
269 // In standards mode, just set negative sizes to zero
270 if (specs[i].mValue < 0) {
271 specs[i].mValue = 0;
272 }
273 start = end + 1;
274 }
275 }
276
277 aNumSpecs = count;
278 // Transfer ownership to caller here
279 *aSpecs = std::move(specs);
280
281 return NS_OK;
282 }
283
IsEventAttributeNameInternal(nsAtom * aName)284 bool HTMLFrameSetElement::IsEventAttributeNameInternal(nsAtom* aName) {
285 return nsContentUtils::IsEventAttributeName(
286 aName, EventNameType_HTML | EventNameType_HTMLBodyOrFramesetOnly);
287 }
288
289 #define EVENT(name_, id_, type_, struct_) /* nothing; handled by the shim */
290 // nsGenericHTMLElement::GetOnError returns
291 // already_AddRefed<EventHandlerNonNull> while other getters return
292 // EventHandlerNonNull*, so allow passing in the type to use here.
293 #define WINDOW_EVENT_HELPER(name_, type_) \
294 type_* HTMLFrameSetElement::GetOn##name_() { \
295 if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
296 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
297 return globalWin->GetOn##name_(); \
298 } \
299 return nullptr; \
300 } \
301 void HTMLFrameSetElement::SetOn##name_(type_* handler) { \
302 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
303 if (!win) { \
304 return; \
305 } \
306 \
307 nsGlobalWindowInner* globalWin = nsGlobalWindowInner::Cast(win); \
308 return globalWin->SetOn##name_(handler); \
309 }
310 #define WINDOW_EVENT(name_, id_, type_, struct_) \
311 WINDOW_EVENT_HELPER(name_, EventHandlerNonNull)
312 #define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_) \
313 WINDOW_EVENT_HELPER(name_, OnBeforeUnloadEventHandlerNonNull)
314 #include "mozilla/EventNameList.h" // IWYU pragma: keep
315 #undef BEFOREUNLOAD_EVENT
316 #undef WINDOW_EVENT
317 #undef WINDOW_EVENT_HELPER
318 #undef EVENT
319
320 } // namespace mozilla::dom
321