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