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