/src/mozilla-central/layout/forms/nsTextControlFrame.cpp
Line | Count | Source (jump to first uncovered line) |
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 "mozilla/DebugOnly.h" |
8 | | |
9 | | #include "gfxContext.h" |
10 | | #include "nsCOMPtr.h" |
11 | | #include "nsFontMetrics.h" |
12 | | #include "nsTextControlFrame.h" |
13 | | #include "nsIPlaintextEditor.h" |
14 | | #include "nsCaret.h" |
15 | | #include "nsCSSPseudoElements.h" |
16 | | #include "nsGenericHTMLElement.h" |
17 | | #include "nsIEditor.h" |
18 | | #include "nsTextFragment.h" |
19 | | #include "nsNameSpaceManager.h" |
20 | | #include "nsCheckboxRadioFrame.h" //for registering accesskeys |
21 | | |
22 | | #include "nsIContent.h" |
23 | | #include "nsPresContext.h" |
24 | | #include "nsGkAtoms.h" |
25 | | #include "nsLayoutUtils.h" |
26 | | #include "nsIPresShell.h" |
27 | | |
28 | | #include <algorithm> |
29 | | #include "nsRange.h" //for selection setting helper func |
30 | | #include "nsINode.h" |
31 | | #include "nsPIDOMWindow.h" //needed for notify selection changed to update the menus ect. |
32 | | #include "nsQueryObject.h" |
33 | | #include "nsILayoutHistoryState.h" |
34 | | |
35 | | #include "nsFocusManager.h" |
36 | | #include "mozilla/PresState.h" |
37 | | #include "nsAttrValueInlines.h" |
38 | | #include "mozilla/dom/Selection.h" |
39 | | #include "mozilla/TextEditRules.h" |
40 | | #include "nsContentUtils.h" |
41 | | #include "nsTextNode.h" |
42 | | #include "mozilla/dom/HTMLInputElement.h" |
43 | | #include "mozilla/dom/HTMLTextAreaElement.h" |
44 | | #include "mozilla/dom/ScriptSettings.h" |
45 | | #include "mozilla/dom/Text.h" |
46 | | #include "mozilla/MathAlgorithms.h" |
47 | | #include "nsFrameSelection.h" |
48 | | |
49 | | #define DEFAULT_COLUMN_WIDTH 20 |
50 | | |
51 | | using namespace mozilla; |
52 | | using namespace mozilla::dom; |
53 | | |
54 | | nsIFrame* |
55 | | NS_NewTextControlFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
56 | 0 | { |
57 | 0 | return new (aPresShell) nsTextControlFrame(aStyle); |
58 | 0 | } |
59 | | |
60 | | NS_IMPL_FRAMEARENA_HELPERS(nsTextControlFrame) |
61 | | |
62 | 0 | NS_QUERYFRAME_HEAD(nsTextControlFrame) |
63 | 0 | NS_QUERYFRAME_ENTRY(nsIFormControlFrame) |
64 | 0 | NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) |
65 | 0 | NS_QUERYFRAME_ENTRY(nsITextControlFrame) |
66 | 0 | NS_QUERYFRAME_ENTRY(nsIStatefulFrame) |
67 | 0 | NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) |
68 | | |
69 | | #ifdef ACCESSIBILITY |
70 | | a11y::AccType |
71 | | nsTextControlFrame::AccessibleType() |
72 | 0 | { |
73 | 0 | return a11y::eHTMLTextFieldType; |
74 | 0 | } |
75 | | #endif |
76 | | |
77 | | #ifdef DEBUG |
78 | | class EditorInitializerEntryTracker { |
79 | | public: |
80 | | explicit EditorInitializerEntryTracker(nsTextControlFrame &frame) |
81 | | : mFrame(frame) |
82 | | , mFirstEntry(false) |
83 | | { |
84 | | if (!mFrame.mInEditorInitialization) { |
85 | | mFrame.mInEditorInitialization = true; |
86 | | mFirstEntry = true; |
87 | | } |
88 | | } |
89 | | ~EditorInitializerEntryTracker() |
90 | | { |
91 | | if (mFirstEntry) { |
92 | | mFrame.mInEditorInitialization = false; |
93 | | } |
94 | | } |
95 | | bool EnteredMoreThanOnce() const { return !mFirstEntry; } |
96 | | private: |
97 | | nsTextControlFrame &mFrame; |
98 | | bool mFirstEntry; |
99 | | }; |
100 | | #endif |
101 | | |
102 | | class nsTextControlFrame::nsAnonDivObserver final : public nsStubMutationObserver |
103 | | { |
104 | | public: |
105 | | explicit nsAnonDivObserver(nsTextControlFrame& aFrame) |
106 | 0 | : mFrame(aFrame) {} |
107 | | NS_DECL_ISUPPORTS |
108 | | NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED |
109 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED |
110 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED |
111 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED |
112 | | |
113 | | private: |
114 | 0 | ~nsAnonDivObserver() {} |
115 | | nsTextControlFrame& mFrame; |
116 | | }; |
117 | | |
118 | | nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle) |
119 | | : nsContainerFrame(aStyle, kClassID) |
120 | | , mFirstBaseline(NS_INTRINSIC_WIDTH_UNKNOWN) |
121 | | , mEditorHasBeenInitialized(false) |
122 | | , mIsProcessing(false) |
123 | | #ifdef DEBUG |
124 | | , mInEditorInitialization(false) |
125 | | #endif |
126 | 0 | { |
127 | 0 | ClearCachedValue(); |
128 | 0 | } |
129 | | |
130 | | nsTextControlFrame::~nsTextControlFrame() |
131 | 0 | { |
132 | 0 | } |
133 | | |
134 | | void |
135 | | nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) |
136 | 0 | { |
137 | 0 | mScrollEvent.Revoke(); |
138 | 0 |
|
139 | 0 | DeleteProperty(TextControlInitializer()); |
140 | 0 |
|
141 | 0 | // Unbind the text editor state object from the frame. The editor will live |
142 | 0 | // on, but things like controllers will be released. |
143 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
144 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
145 | 0 | txtCtrl->UnbindFromFrame(this); |
146 | 0 |
|
147 | 0 | nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false); |
148 | 0 |
|
149 | 0 | if (mMutationObserver) { |
150 | 0 | mRootNode->RemoveMutationObserver(mMutationObserver); |
151 | 0 | mMutationObserver = nullptr; |
152 | 0 | } |
153 | 0 |
|
154 | 0 | // FIXME(emilio, bug 1400618): Do this after the child frames are destroyed. |
155 | 0 | aPostDestroyData.AddAnonymousContent(mRootNode.forget()); |
156 | 0 | aPostDestroyData.AddAnonymousContent(mPlaceholderDiv.forget()); |
157 | 0 | aPostDestroyData.AddAnonymousContent(mPreviewDiv.forget()); |
158 | 0 |
|
159 | 0 | nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); |
160 | 0 | } |
161 | | |
162 | | LogicalSize |
163 | | nsTextControlFrame::CalcIntrinsicSize(gfxContext* aRenderingContext, |
164 | | WritingMode aWM, |
165 | | float aFontSizeInflation) const |
166 | 0 | { |
167 | 0 | LogicalSize intrinsicSize(aWM); |
168 | 0 | // Get leading and the Average/MaxAdvance char width |
169 | 0 | nscoord lineHeight = 0; |
170 | 0 | nscoord charWidth = 0; |
171 | 0 | nscoord charMaxAdvance = 0; |
172 | 0 |
|
173 | 0 | RefPtr<nsFontMetrics> fontMet = |
174 | 0 | nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation); |
175 | 0 |
|
176 | 0 | lineHeight = |
177 | 0 | ReflowInput::CalcLineHeight(GetContent(), |
178 | 0 | Style(), |
179 | 0 | PresContext(), |
180 | 0 | NS_AUTOHEIGHT, |
181 | 0 | aFontSizeInflation); |
182 | 0 | charWidth = fontMet->AveCharWidth(); |
183 | 0 | charMaxAdvance = fontMet->MaxAdvance(); |
184 | 0 |
|
185 | 0 | // Set the width equal to the width in characters |
186 | 0 | int32_t cols = GetCols(); |
187 | 0 | intrinsicSize.ISize(aWM) = cols * charWidth; |
188 | 0 |
|
189 | 0 | // To better match IE, take the maximum character width(in twips) and remove |
190 | 0 | // 4 pixels add this on as additional padding(internalPadding). But only do |
191 | 0 | // this if we think we have a fixed-width font. |
192 | 0 | if (mozilla::Abs(charWidth - charMaxAdvance) > (unsigned)nsPresContext::CSSPixelsToAppUnits(1)) { |
193 | 0 | nscoord internalPadding = |
194 | 0 | std::max(0, charMaxAdvance - nsPresContext::CSSPixelsToAppUnits(4)); |
195 | 0 | nscoord t = nsPresContext::CSSPixelsToAppUnits(1); |
196 | 0 | // Round to a multiple of t |
197 | 0 | nscoord rest = internalPadding % t; |
198 | 0 | if (rest < t - rest) { |
199 | 0 | internalPadding -= rest; |
200 | 0 | } else { |
201 | 0 | internalPadding += t - rest; |
202 | 0 | } |
203 | 0 | // Now add the extra padding on (so that small input sizes work well) |
204 | 0 | intrinsicSize.ISize(aWM) += internalPadding; |
205 | 0 | } else { |
206 | 0 | // This is to account for the anonymous <br> having a 1 twip width |
207 | 0 | // in Full Standards mode, see BRFrame::Reflow and bug 228752. |
208 | 0 | if (PresContext()->CompatibilityMode() == eCompatibility_FullStandards) { |
209 | 0 | intrinsicSize.ISize(aWM) += 1; |
210 | 0 | } |
211 | 0 | } |
212 | 0 |
|
213 | 0 | // Increment width with cols * letter-spacing. |
214 | 0 | { |
215 | 0 | const nsStyleCoord& lsCoord = StyleText()->mLetterSpacing; |
216 | 0 | if (eStyleUnit_Coord == lsCoord.GetUnit()) { |
217 | 0 | nscoord letterSpacing = lsCoord.GetCoordValue(); |
218 | 0 | if (letterSpacing != 0) { |
219 | 0 | intrinsicSize.ISize(aWM) += cols * letterSpacing; |
220 | 0 | } |
221 | 0 | } |
222 | 0 | } |
223 | 0 |
|
224 | 0 | // Set the height equal to total number of rows (times the height of each |
225 | 0 | // line, of course) |
226 | 0 | intrinsicSize.BSize(aWM) = lineHeight * GetRows(); |
227 | 0 |
|
228 | 0 | // Add in the size of the scrollbars for textarea |
229 | 0 | if (IsTextArea()) { |
230 | 0 | nsIFrame* first = PrincipalChildList().FirstChild(); |
231 | 0 |
|
232 | 0 | nsIScrollableFrame *scrollableFrame = do_QueryFrame(first); |
233 | 0 | NS_ASSERTION(scrollableFrame, "Child must be scrollable"); |
234 | 0 |
|
235 | 0 | if (scrollableFrame) { |
236 | 0 | LogicalMargin scrollbarSizes(aWM, |
237 | 0 | scrollableFrame->GetDesiredScrollbarSizes(PresContext(), |
238 | 0 | aRenderingContext)); |
239 | 0 |
|
240 | 0 | intrinsicSize.ISize(aWM) += scrollbarSizes.IStartEnd(aWM); |
241 | 0 | intrinsicSize.BSize(aWM) += scrollbarSizes.BStartEnd(aWM); |
242 | 0 | } |
243 | 0 | } |
244 | 0 | return intrinsicSize; |
245 | 0 | } |
246 | | |
247 | | nsresult |
248 | | nsTextControlFrame::EnsureEditorInitialized() |
249 | 0 | { |
250 | 0 | // This method initializes our editor, if needed. |
251 | 0 |
|
252 | 0 | // This code used to be called from CreateAnonymousContent(), but |
253 | 0 | // when the editor set the initial string, it would trigger a |
254 | 0 | // PresShell listener which called FlushPendingNotifications() |
255 | 0 | // during frame construction. This was causing other form controls |
256 | 0 | // to display wrong values. Additionally, calling this every time |
257 | 0 | // a text frame control is instantiated means that we're effectively |
258 | 0 | // instantiating the editor for all text fields, even if they |
259 | 0 | // never get used. So, now this method is being called lazily only |
260 | 0 | // when we actually need an editor. |
261 | 0 |
|
262 | 0 | if (mEditorHasBeenInitialized) |
263 | 0 | return NS_OK; |
264 | 0 | |
265 | 0 | nsIDocument* doc = mContent->GetComposedDoc(); |
266 | 0 | NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); |
267 | 0 |
|
268 | 0 | AutoWeakFrame weakFrame(this); |
269 | 0 |
|
270 | 0 | // Flush out content on our document. Have to do this, because script |
271 | 0 | // blockers don't prevent the sink flushing out content and notifying in the |
272 | 0 | // process, which can destroy frames. |
273 | 0 | doc->FlushPendingNotifications(FlushType::ContentAndNotify); |
274 | 0 | NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_ERROR_FAILURE); |
275 | 0 |
|
276 | 0 | // Make sure that editor init doesn't do things that would kill us off |
277 | 0 | // (especially off the script blockers it'll create for its DOM mutations). |
278 | 0 | { |
279 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
280 | 0 | MOZ_ASSERT(txtCtrl, "Content not a text control element"); |
281 | 0 |
|
282 | 0 | // Hide selection changes during the initialization, as webpages should not |
283 | 0 | // be aware of these initializations |
284 | 0 | AutoHideSelectionChanges hideSelectionChanges(txtCtrl->GetConstFrameSelection()); |
285 | 0 |
|
286 | 0 | nsAutoScriptBlocker scriptBlocker; |
287 | 0 |
|
288 | 0 | // Time to mess with our security context... See comments in GetValue() |
289 | 0 | // for why this is needed. |
290 | 0 | mozilla::dom::AutoNoJSAPI nojsapi; |
291 | 0 |
|
292 | 0 | // Make sure that we try to focus the content even if the method fails |
293 | 0 | class EnsureSetFocus { |
294 | 0 | public: |
295 | 0 | explicit EnsureSetFocus(nsTextControlFrame* aFrame) |
296 | 0 | : mFrame(aFrame) {} |
297 | 0 | ~EnsureSetFocus() { |
298 | 0 | if (nsContentUtils::IsFocusedContent(mFrame->GetContent())) |
299 | 0 | mFrame->SetFocus(true, false); |
300 | 0 | } |
301 | 0 | private: |
302 | 0 | nsTextControlFrame *mFrame; |
303 | 0 | }; |
304 | 0 | EnsureSetFocus makeSureSetFocusHappens(this); |
305 | 0 |
|
306 | | #ifdef DEBUG |
307 | | // Make sure we are not being called again until we're finished. |
308 | | // If reentrancy happens, just pretend that we don't have an editor. |
309 | | const EditorInitializerEntryTracker tracker(*this); |
310 | | NS_ASSERTION(!tracker.EnteredMoreThanOnce(), |
311 | | "EnsureEditorInitialized has been called while a previous call was in progress"); |
312 | | #endif |
313 | |
|
314 | 0 | // Create an editor for the frame, if one doesn't already exist |
315 | 0 | nsresult rv = txtCtrl->CreateEditor(); |
316 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
317 | 0 | NS_ENSURE_STATE(weakFrame.IsAlive()); |
318 | 0 |
|
319 | 0 | // Set mEditorHasBeenInitialized so that subsequent calls will use the |
320 | 0 | // editor. |
321 | 0 | mEditorHasBeenInitialized = true; |
322 | 0 |
|
323 | 0 | if (weakFrame.IsAlive()) { |
324 | 0 | uint32_t position = 0; |
325 | 0 |
|
326 | 0 | // Set the selection to the end of the text field (bug 1287655), |
327 | 0 | // but only if the contents has changed (bug 1337392). |
328 | 0 | if (txtCtrl->ValueChanged()) { |
329 | 0 | nsAutoString val; |
330 | 0 | txtCtrl->GetTextEditorValue(val, true); |
331 | 0 | position = val.Length(); |
332 | 0 | } |
333 | 0 |
|
334 | 0 | SetSelectionEndPoints(position, position); |
335 | 0 | } |
336 | 0 | } |
337 | 0 | NS_ENSURE_STATE(weakFrame.IsAlive()); |
338 | 0 | return NS_OK; |
339 | 0 | } |
340 | | |
341 | | static already_AddRefed<Element> |
342 | | CreateEmptyDiv(const nsTextControlFrame& aOwnerFrame) |
343 | 0 | { |
344 | 0 | nsIDocument* doc = aOwnerFrame.PresContext()->Document(); |
345 | 0 | RefPtr<mozilla::dom::NodeInfo> nodeInfo = |
346 | 0 | doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr, |
347 | 0 | kNameSpaceID_XHTML, |
348 | 0 | nsINode::ELEMENT_NODE); |
349 | 0 |
|
350 | 0 | RefPtr<Element> element = NS_NewHTMLDivElement(nodeInfo.forget()); |
351 | 0 | return element.forget(); |
352 | 0 | } |
353 | | |
354 | | static already_AddRefed<Element> |
355 | | CreateEmptyDivWithTextNode(const nsTextControlFrame& aOwnerFrame) |
356 | 0 | { |
357 | 0 | RefPtr<Element> element = CreateEmptyDiv(aOwnerFrame); |
358 | 0 |
|
359 | 0 | // Create the text node for DIV |
360 | 0 | RefPtr<nsTextNode> textNode = |
361 | 0 | new nsTextNode(element->OwnerDoc()->NodeInfoManager()); |
362 | 0 | textNode->MarkAsMaybeModifiedFrequently(); |
363 | 0 |
|
364 | 0 | element->AppendChildTo(textNode, false); |
365 | 0 |
|
366 | 0 | return element.forget(); |
367 | 0 | } |
368 | | |
369 | | nsresult |
370 | | nsTextControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) |
371 | 0 | { |
372 | 0 | MOZ_ASSERT(mContent, "We should have a content!"); |
373 | 0 |
|
374 | 0 | AddStateBits(NS_FRAME_INDEPENDENT_SELECTION); |
375 | 0 |
|
376 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
377 | 0 | MOZ_ASSERT(txtCtrl, "Content not a text control element"); |
378 | 0 |
|
379 | 0 | nsresult rv = CreateRootNode(); |
380 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
381 | 0 |
|
382 | 0 | // Bind the frame to its text control |
383 | 0 | rv = txtCtrl->BindToFrame(this); |
384 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
385 | 0 |
|
386 | 0 | aElements.AppendElement(mRootNode); |
387 | 0 | CreatePlaceholderIfNeeded(); |
388 | 0 | if (mPlaceholderDiv) { |
389 | 0 | if (!IsSingleLineTextControl()) { |
390 | 0 | // For textareas, UpdateValueDisplay doesn't initialize the visibility |
391 | 0 | // status of the placeholder because it returns early, so we have to |
392 | 0 | // do that manually here. |
393 | 0 | txtCtrl->UpdateOverlayTextVisibility(true); |
394 | 0 | } |
395 | 0 | aElements.AppendElement(mPlaceholderDiv); |
396 | 0 | } |
397 | 0 | CreatePreviewIfNeeded(); |
398 | 0 | if (mPreviewDiv) { |
399 | 0 | aElements.AppendElement(mPreviewDiv); |
400 | 0 | } |
401 | 0 |
|
402 | 0 | rv = UpdateValueDisplay(false); |
403 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
404 | 0 |
|
405 | 0 | InitializeEagerlyIfNeeded(); |
406 | 0 | return NS_OK; |
407 | 0 | } |
408 | | |
409 | | bool |
410 | | nsTextControlFrame::ShouldInitializeEagerly() const |
411 | 0 | { |
412 | 0 | // textareas are eagerly initialized. |
413 | 0 | if (!IsSingleLineTextControl()) { |
414 | 0 | return true; |
415 | 0 | } |
416 | 0 | |
417 | 0 | // Also, input elements which have a cached selection should get eager |
418 | 0 | // editor initialization. |
419 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
420 | 0 | if (txtCtrl->HasCachedSelection()) { |
421 | 0 | return true; |
422 | 0 | } |
423 | 0 | |
424 | 0 | // So do input text controls with spellcheck=true |
425 | 0 | if (auto* htmlElement = nsGenericHTMLElement::FromNode(mContent)) { |
426 | 0 | if (htmlElement->Spellcheck()) { |
427 | 0 | return true; |
428 | 0 | } |
429 | 0 | } |
430 | 0 | |
431 | 0 | return false; |
432 | 0 | } |
433 | | |
434 | | void |
435 | | nsTextControlFrame::InitializeEagerlyIfNeeded() |
436 | 0 | { |
437 | 0 | MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), |
438 | 0 | "Someone forgot a script blocker?"); |
439 | 0 | if (!ShouldInitializeEagerly()) { |
440 | 0 | return; |
441 | 0 | } |
442 | 0 | |
443 | 0 | EditorInitializer* initializer = new EditorInitializer(this); |
444 | 0 | SetProperty(TextControlInitializer(), initializer); |
445 | 0 | nsContentUtils::AddScriptRunner(initializer); |
446 | 0 | } |
447 | | |
448 | | nsresult |
449 | | nsTextControlFrame::CreateRootNode() |
450 | 0 | { |
451 | 0 | MOZ_ASSERT(!mRootNode); |
452 | 0 |
|
453 | 0 | mRootNode = CreateEmptyDiv(*this); |
454 | 0 | mRootNode->SetIsNativeAnonymousRoot(); |
455 | 0 |
|
456 | 0 | mMutationObserver = new nsAnonDivObserver(*this); |
457 | 0 | mRootNode->AddMutationObserver(mMutationObserver); |
458 | 0 |
|
459 | 0 | // Make our root node editable |
460 | 0 | mRootNode->SetFlags(NODE_IS_EDITABLE); |
461 | 0 |
|
462 | 0 | // Set the necessary classes on the text control. We use class values instead |
463 | 0 | // of a 'style' attribute so that the style comes from a user-agent style |
464 | 0 | // sheet and is still applied even if author styles are disabled. |
465 | 0 | nsAutoString classValue; |
466 | 0 | classValue.AppendLiteral("anonymous-div"); |
467 | 0 |
|
468 | 0 | if (!IsSingleLineTextControl()) { |
469 | 0 | // We can't just inherit the overflow because setting visible overflow will |
470 | 0 | // crash when the number of lines exceeds the height of the textarea and |
471 | 0 | // setting -moz-hidden-unscrollable overflow (NS_STYLE_OVERFLOW_CLIP) |
472 | 0 | // doesn't paint the caret for some reason. |
473 | 0 | const nsStyleDisplay* disp = StyleDisplay(); |
474 | 0 | if (disp->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE && |
475 | 0 | disp->mOverflowX != NS_STYLE_OVERFLOW_CLIP) { |
476 | 0 | classValue.AppendLiteral(" inherit-overflow"); |
477 | 0 | } |
478 | 0 | classValue.AppendLiteral(" inherit-scroll-behavior"); |
479 | 0 | } |
480 | 0 | nsresult rv = mRootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, |
481 | 0 | classValue, false); |
482 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
483 | 0 |
|
484 | 0 | return NS_OK; |
485 | 0 | } |
486 | | |
487 | | void |
488 | | nsTextControlFrame::CreatePlaceholderIfNeeded() |
489 | 0 | { |
490 | 0 | MOZ_ASSERT(!mPlaceholderDiv); |
491 | 0 |
|
492 | 0 | // Do we need a placeholder node? |
493 | 0 | nsAutoString placeholderTxt; |
494 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, |
495 | 0 | placeholderTxt); |
496 | 0 | if (IsTextArea()) { // <textarea>s preserve newlines... |
497 | 0 | nsContentUtils::PlatformToDOMLineBreaks(placeholderTxt); |
498 | 0 | } else { // ...<input>s don't |
499 | 0 | nsContentUtils::RemoveNewlines(placeholderTxt); |
500 | 0 | } |
501 | 0 |
|
502 | 0 | if (placeholderTxt.IsEmpty()) { |
503 | 0 | return; |
504 | 0 | } |
505 | 0 | |
506 | 0 | mPlaceholderDiv = CreateEmptyDivWithTextNode(*this); |
507 | 0 | // Associate ::placeholder pseudo-element with the placeholder node. |
508 | 0 | mPlaceholderDiv->SetPseudoElementType(CSSPseudoElementType::placeholder); |
509 | 0 | mPlaceholderDiv->GetFirstChild()->AsText()->SetText(placeholderTxt, false); |
510 | 0 | } |
511 | | |
512 | | void |
513 | | nsTextControlFrame::CreatePreviewIfNeeded() |
514 | 0 | { |
515 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
516 | 0 | if (!txtCtrl->IsPreviewEnabled()) { |
517 | 0 | return; |
518 | 0 | } |
519 | 0 | |
520 | 0 | mPreviewDiv = CreateEmptyDivWithTextNode(*this); |
521 | 0 | mPreviewDiv->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, |
522 | 0 | NS_LITERAL_STRING("preview-div"), false); |
523 | 0 | } |
524 | | |
525 | | void |
526 | | nsTextControlFrame::AppendAnonymousContentTo( |
527 | | nsTArray<nsIContent*>& aElements, |
528 | | uint32_t aFilter) |
529 | 0 | { |
530 | 0 | aElements.AppendElement(mRootNode); |
531 | 0 |
|
532 | 0 | if (mPlaceholderDiv && !(aFilter & nsIContent::eSkipPlaceholderContent)) { |
533 | 0 | aElements.AppendElement(mPlaceholderDiv); |
534 | 0 | } |
535 | 0 |
|
536 | 0 | if (mPreviewDiv) { |
537 | 0 | aElements.AppendElement(mPreviewDiv); |
538 | 0 | } |
539 | 0 | } |
540 | | |
541 | | nscoord |
542 | | nsTextControlFrame::GetPrefISize(gfxContext* aRenderingContext) |
543 | 0 | { |
544 | 0 | nscoord result = 0; |
545 | 0 | DISPLAY_PREF_INLINE_SIZE(this, result); |
546 | 0 | float inflation = nsLayoutUtils::FontSizeInflationFor(this); |
547 | 0 | WritingMode wm = GetWritingMode(); |
548 | 0 | result = CalcIntrinsicSize(aRenderingContext, wm, inflation).ISize(wm); |
549 | 0 | return result; |
550 | 0 | } |
551 | | |
552 | | nscoord |
553 | | nsTextControlFrame::GetMinISize(gfxContext* aRenderingContext) |
554 | 0 | { |
555 | 0 | // Our min inline size is just our preferred width if we have auto inline size |
556 | 0 | nscoord result; |
557 | 0 | DISPLAY_MIN_INLINE_SIZE(this, result); |
558 | 0 | result = GetPrefISize(aRenderingContext); |
559 | 0 | return result; |
560 | 0 | } |
561 | | |
562 | | LogicalSize |
563 | | nsTextControlFrame::ComputeAutoSize(gfxContext* aRenderingContext, |
564 | | WritingMode aWM, |
565 | | const LogicalSize& aCBSize, |
566 | | nscoord aAvailableISize, |
567 | | const LogicalSize& aMargin, |
568 | | const LogicalSize& aBorder, |
569 | | const LogicalSize& aPadding, |
570 | | ComputeSizeFlags aFlags) |
571 | 0 | { |
572 | 0 | float inflation = nsLayoutUtils::FontSizeInflationFor(this); |
573 | 0 | LogicalSize autoSize = CalcIntrinsicSize(aRenderingContext, aWM, inflation); |
574 | 0 |
|
575 | 0 | // Note: nsContainerFrame::ComputeAutoSize only computes the inline-size (and |
576 | 0 | // only for 'auto'), the block-size it returns is always NS_UNCONSTRAINEDSIZE. |
577 | 0 | const nsStyleCoord& iSizeCoord = StylePosition()->ISize(aWM); |
578 | 0 | if (iSizeCoord.GetUnit() == eStyleUnit_Auto) { |
579 | 0 | if (aFlags & ComputeSizeFlags::eIClampMarginBoxMinSize) { |
580 | 0 | // CalcIntrinsicSize isn't aware of grid-item margin-box clamping, so we |
581 | 0 | // fall back to nsContainerFrame's ComputeAutoSize to handle that. |
582 | 0 | // XXX maybe a font-inflation issue here? (per the assertion below). |
583 | 0 | autoSize.ISize(aWM) = |
584 | 0 | nsContainerFrame::ComputeAutoSize(aRenderingContext, aWM, |
585 | 0 | aCBSize, aAvailableISize, |
586 | 0 | aMargin, aBorder, |
587 | 0 | aPadding, aFlags).ISize(aWM); |
588 | 0 | } |
589 | | #ifdef DEBUG |
590 | | else { |
591 | | LogicalSize ancestorAutoSize = |
592 | | nsContainerFrame::ComputeAutoSize(aRenderingContext, aWM, |
593 | | aCBSize, aAvailableISize, |
594 | | aMargin, aBorder, |
595 | | aPadding, aFlags); |
596 | | // Disabled when there's inflation; see comment in GetXULPrefSize. |
597 | | MOZ_ASSERT(inflation != 1.0f || |
598 | | ancestorAutoSize.ISize(aWM) == autoSize.ISize(aWM), |
599 | | "Incorrect size computed by ComputeAutoSize?"); |
600 | | } |
601 | | #endif |
602 | | } |
603 | 0 | return autoSize; |
604 | 0 | } |
605 | | |
606 | | void |
607 | | nsTextControlFrame::Reflow(nsPresContext* aPresContext, |
608 | | ReflowOutput& aDesiredSize, |
609 | | const ReflowInput& aReflowInput, |
610 | | nsReflowStatus& aStatus) |
611 | 0 | { |
612 | 0 | MarkInReflow(); |
613 | 0 | DO_GLOBAL_REFLOW_COUNT("nsTextControlFrame"); |
614 | 0 | DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); |
615 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
616 | 0 |
|
617 | 0 | // make sure that the form registers itself on the initial/first reflow |
618 | 0 | if (mState & NS_FRAME_FIRST_REFLOW) { |
619 | 0 | nsCheckboxRadioFrame::RegUnRegAccessKey(this, true); |
620 | 0 | } |
621 | 0 |
|
622 | 0 | // set values of reflow's out parameters |
623 | 0 | WritingMode wm = aReflowInput.GetWritingMode(); |
624 | 0 | LogicalSize |
625 | 0 | finalSize(wm, |
626 | 0 | aReflowInput.ComputedISize() + |
627 | 0 | aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm), |
628 | 0 | aReflowInput.ComputedBSize() + |
629 | 0 | aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm)); |
630 | 0 | aDesiredSize.SetSize(wm, finalSize); |
631 | 0 |
|
632 | 0 | // Calculate the baseline and store it in mFirstBaseline. |
633 | 0 | nscoord lineHeight = aReflowInput.ComputedBSize(); |
634 | 0 | float inflation = nsLayoutUtils::FontSizeInflationFor(this); |
635 | 0 | if (!IsSingleLineTextControl()) { |
636 | 0 | lineHeight = ReflowInput::CalcLineHeight(GetContent(), |
637 | 0 | Style(), |
638 | 0 | PresContext(), |
639 | 0 | NS_AUTOHEIGHT, |
640 | 0 | inflation); |
641 | 0 | } |
642 | 0 | RefPtr<nsFontMetrics> fontMet = |
643 | 0 | nsLayoutUtils::GetFontMetricsForFrame(this, inflation); |
644 | 0 | mFirstBaseline = |
645 | 0 | nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight, |
646 | 0 | wm.IsLineInverted()) + |
647 | 0 | aReflowInput.ComputedLogicalBorderPadding().BStart(wm); |
648 | 0 | aDesiredSize.SetBlockStartAscent(mFirstBaseline); |
649 | 0 |
|
650 | 0 | // overflow handling |
651 | 0 | aDesiredSize.SetOverflowAreasToDesiredBounds(); |
652 | 0 | // perform reflow on all kids |
653 | 0 | nsIFrame* kid = mFrames.FirstChild(); |
654 | 0 | while (kid) { |
655 | 0 | ReflowTextControlChild(kid, aPresContext, aReflowInput, aStatus, aDesiredSize); |
656 | 0 | kid = kid->GetNextSibling(); |
657 | 0 | } |
658 | 0 |
|
659 | 0 | // take into account css properties that affect overflow handling |
660 | 0 | FinishAndStoreOverflow(&aDesiredSize); |
661 | 0 |
|
662 | 0 | aStatus.Reset(); // This type of frame can't be split. |
663 | 0 | NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); |
664 | 0 | } |
665 | | |
666 | | void |
667 | | nsTextControlFrame::ReflowTextControlChild(nsIFrame* aKid, |
668 | | nsPresContext* aPresContext, |
669 | | const ReflowInput& aReflowInput, |
670 | | nsReflowStatus& aStatus, |
671 | | ReflowOutput& aParentDesiredSize) |
672 | 0 | { |
673 | 0 | // compute available size and frame offsets for child |
674 | 0 | WritingMode wm = aKid->GetWritingMode(); |
675 | 0 | LogicalSize availSize = aReflowInput.ComputedSizeWithPadding(wm); |
676 | 0 | availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; |
677 | 0 |
|
678 | 0 | ReflowInput kidReflowInput(aPresContext, aReflowInput, |
679 | 0 | aKid, availSize, nullptr, |
680 | 0 | ReflowInput::CALLER_WILL_INIT); |
681 | 0 | // Override padding with our computed padding in case we got it from theming or percentage |
682 | 0 | kidReflowInput.Init(aPresContext, nullptr, nullptr, &aReflowInput.ComputedPhysicalPadding()); |
683 | 0 |
|
684 | 0 | // Set computed width and computed height for the child |
685 | 0 | kidReflowInput.SetComputedWidth(aReflowInput.ComputedWidth()); |
686 | 0 | kidReflowInput.SetComputedHeight(aReflowInput.ComputedHeight()); |
687 | 0 |
|
688 | 0 | // Offset the frame by the size of the parent's border |
689 | 0 | nscoord xOffset = aReflowInput.ComputedPhysicalBorderPadding().left - |
690 | 0 | aReflowInput.ComputedPhysicalPadding().left; |
691 | 0 | nscoord yOffset = aReflowInput.ComputedPhysicalBorderPadding().top - |
692 | 0 | aReflowInput.ComputedPhysicalPadding().top; |
693 | 0 |
|
694 | 0 | // reflow the child |
695 | 0 | ReflowOutput desiredSize(aReflowInput); |
696 | 0 | ReflowChild(aKid, aPresContext, desiredSize, kidReflowInput, |
697 | 0 | xOffset, yOffset, 0, aStatus); |
698 | 0 |
|
699 | 0 | // place the child |
700 | 0 | FinishReflowChild(aKid, aPresContext, desiredSize, |
701 | 0 | &kidReflowInput, xOffset, yOffset, 0); |
702 | 0 |
|
703 | 0 | // consider the overflow |
704 | 0 | aParentDesiredSize.mOverflowAreas.UnionWith(desiredSize.mOverflowAreas); |
705 | 0 | } |
706 | | |
707 | | nsSize |
708 | | nsTextControlFrame::GetXULMinSize(nsBoxLayoutState& aState) |
709 | 0 | { |
710 | 0 | // XXXbz why? Why not the nsBoxFrame sizes? |
711 | 0 | return nsBox::GetXULMinSize(aState); |
712 | 0 | } |
713 | | |
714 | | bool |
715 | | nsTextControlFrame::IsXULCollapsed() |
716 | 0 | { |
717 | 0 | // We're never collapsed in the box sense. |
718 | 0 | return false; |
719 | 0 | } |
720 | | |
721 | | NS_IMETHODIMP |
722 | | nsTextControlFrame::ScrollOnFocusEvent::Run() |
723 | 0 | { |
724 | 0 | if (mFrame) { |
725 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(mFrame->GetContent()); |
726 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
727 | 0 | nsISelectionController* selCon = txtCtrl->GetSelectionController(); |
728 | 0 | if (selCon) { |
729 | 0 | mFrame->mScrollEvent.Forget(); |
730 | 0 | selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, |
731 | 0 | nsISelectionController::SELECTION_FOCUS_REGION, |
732 | 0 | nsISelectionController::SCROLL_SYNCHRONOUS); |
733 | 0 | } |
734 | 0 | } |
735 | 0 | return NS_OK; |
736 | 0 | } |
737 | | |
738 | | //IMPLEMENTING NS_IFORMCONTROLFRAME |
739 | | void nsTextControlFrame::SetFocus(bool aOn, bool aRepaint) |
740 | 0 | { |
741 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
742 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
743 | 0 |
|
744 | 0 | // Revoke the previous scroll event if one exists |
745 | 0 | mScrollEvent.Revoke(); |
746 | 0 |
|
747 | 0 | // If 'dom.placeholeder.show_on_focus' preference is 'false', focusing or |
748 | 0 | // blurring the frame can have an impact on the placeholder visibility. |
749 | 0 | if (mPlaceholderDiv) { |
750 | 0 | txtCtrl->UpdateOverlayTextVisibility(true); |
751 | 0 | } |
752 | 0 |
|
753 | 0 | if (!aOn) { |
754 | 0 | return; |
755 | 0 | } |
756 | 0 | |
757 | 0 | nsISelectionController* selCon = txtCtrl->GetSelectionController(); |
758 | 0 | if (!selCon) { |
759 | 0 | return; |
760 | 0 | } |
761 | 0 | |
762 | 0 | RefPtr<Selection> ourSel = |
763 | 0 | selCon->GetSelection(nsISelectionController::SELECTION_NORMAL); |
764 | 0 | if (!ourSel) { |
765 | 0 | return; |
766 | 0 | } |
767 | 0 | |
768 | 0 | nsIPresShell* presShell = PresContext()->GetPresShell(); |
769 | 0 | RefPtr<nsCaret> caret = presShell->GetCaret(); |
770 | 0 | if (!caret) { |
771 | 0 | return; |
772 | 0 | } |
773 | 0 | |
774 | 0 | // Scroll the current selection into view |
775 | 0 | Selection* caretSelection = caret->GetSelection(); |
776 | 0 | const bool isFocusedRightNow = ourSel == caretSelection; |
777 | 0 | if (!isFocusedRightNow) { |
778 | 0 | // Don't scroll the current selection if we've been focused using the mouse. |
779 | 0 | uint32_t lastFocusMethod = 0; |
780 | 0 | nsIDocument* doc = GetContent()->GetComposedDoc(); |
781 | 0 | if (doc) { |
782 | 0 | nsIFocusManager* fm = nsFocusManager::GetFocusManager(); |
783 | 0 | if (fm) { |
784 | 0 | fm->GetLastFocusMethod(doc->GetWindow(), &lastFocusMethod); |
785 | 0 | } |
786 | 0 | } |
787 | 0 | if (!(lastFocusMethod & nsIFocusManager::FLAG_BYMOUSE)) { |
788 | 0 | RefPtr<ScrollOnFocusEvent> event = new ScrollOnFocusEvent(this); |
789 | 0 | nsresult rv = mContent->OwnerDoc()->Dispatch(TaskCategory::Other, |
790 | 0 | do_AddRef(event)); |
791 | 0 | if (NS_SUCCEEDED(rv)) { |
792 | 0 | mScrollEvent = std::move(event); |
793 | 0 | } |
794 | 0 | } |
795 | 0 | } |
796 | 0 |
|
797 | 0 | // tell the caret to use our selection |
798 | 0 | caret->SetSelection(ourSel); |
799 | 0 |
|
800 | 0 | // mutual-exclusion: the selection is either controlled by the |
801 | 0 | // document or by the text input/area. Clear any selection in the |
802 | 0 | // document since the focus is now on our independent selection. |
803 | 0 |
|
804 | 0 | nsCOMPtr<nsISelectionController> selcon = do_QueryInterface(presShell); |
805 | 0 | RefPtr<Selection> docSel = |
806 | 0 | selcon->GetSelection(nsISelectionController::SELECTION_NORMAL); |
807 | 0 | if (!docSel) { |
808 | 0 | return; |
809 | 0 | } |
810 | 0 | |
811 | 0 | if (!docSel->IsCollapsed()) { |
812 | 0 | docSel->RemoveAllRanges(IgnoreErrors()); |
813 | 0 | } |
814 | 0 | } |
815 | | |
816 | | nsresult nsTextControlFrame::SetFormProperty(nsAtom* aName, const nsAString& aValue) |
817 | 0 | { |
818 | 0 | if (!mIsProcessing)//some kind of lock. |
819 | 0 | { |
820 | 0 | mIsProcessing = true; |
821 | 0 | if (nsGkAtoms::select == aName) |
822 | 0 | { |
823 | 0 | // Select all the text. |
824 | 0 | // |
825 | 0 | // XXX: This is lame, we can't call editor's SelectAll method |
826 | 0 | // because that triggers AutoCopies in unix builds. |
827 | 0 | // Instead, we have to call our own homegrown version |
828 | 0 | // of select all which merely builds a range that selects |
829 | 0 | // all of the content and adds that to the selection. |
830 | 0 |
|
831 | 0 | AutoWeakFrame weakThis = this; |
832 | 0 | SelectAllOrCollapseToEndOfText(true); // NOTE: can destroy the world |
833 | 0 | if (!weakThis.IsAlive()) { |
834 | 0 | return NS_OK; |
835 | 0 | } |
836 | 0 | } |
837 | 0 | mIsProcessing = false; |
838 | 0 | } |
839 | 0 | return NS_OK; |
840 | 0 | } |
841 | | |
842 | | NS_IMETHODIMP_(already_AddRefed<TextEditor>) |
843 | | nsTextControlFrame::GetTextEditor() |
844 | 0 | { |
845 | 0 | if (NS_WARN_IF(NS_FAILED(EnsureEditorInitialized()))) { |
846 | 0 | return nullptr; |
847 | 0 | } |
848 | 0 | |
849 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
850 | 0 | MOZ_ASSERT(txtCtrl, "Content not a text control element"); |
851 | 0 | RefPtr<TextEditor> textEditor = txtCtrl->GetTextEditor(); |
852 | 0 | return textEditor.forget(); |
853 | 0 | } |
854 | | |
855 | | nsresult |
856 | | nsTextControlFrame::SetSelectionInternal(nsINode* aStartNode, |
857 | | uint32_t aStartOffset, |
858 | | nsINode* aEndNode, |
859 | | uint32_t aEndOffset, |
860 | | nsITextControlFrame::SelectionDirection aDirection) |
861 | 0 | { |
862 | 0 | // Create a new range to represent the new selection. |
863 | 0 | // Note that we use a new range to avoid having to do |
864 | 0 | // isIncreasing checks to avoid possible errors. |
865 | 0 |
|
866 | 0 | RefPtr<nsRange> range = new nsRange(mContent); |
867 | 0 | // Be careful to use internal nsRange methods which do not check to make sure |
868 | 0 | // we have access to the node. |
869 | 0 | // XXXbz nsRange::SetStartAndEnd takes int32_t (and ranges generally work on |
870 | 0 | // int32_t), but we're passing uint32_t. The good news is that at this point |
871 | 0 | // our endpoints should really be within our length, so not really that big. |
872 | 0 | // And if they _are_ that big, SetStartAndEnd() will simply error out, which |
873 | 0 | // is not too bad for a case we don't expect to happen. |
874 | 0 | nsresult rv = range->SetStartAndEnd(aStartNode, aStartOffset, |
875 | 0 | aEndNode, aEndOffset); |
876 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
877 | 0 |
|
878 | 0 | // Get the selection, clear it and add the new range to it! |
879 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
880 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
881 | 0 | nsISelectionController* selCon = txtCtrl->GetSelectionController(); |
882 | 0 | NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); |
883 | 0 |
|
884 | 0 | RefPtr<Selection> selection = |
885 | 0 | selCon->GetSelection(nsISelectionController::SELECTION_NORMAL); |
886 | 0 | NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
887 | 0 |
|
888 | 0 | nsDirection direction; |
889 | 0 | if (aDirection == eNone) { |
890 | 0 | // Preserve the direction |
891 | 0 | direction = selection->GetDirection(); |
892 | 0 | } else { |
893 | 0 | direction = (aDirection == eBackward) ? eDirPrevious : eDirNext; |
894 | 0 | } |
895 | 0 |
|
896 | 0 | ErrorResult err; |
897 | 0 | selection->RemoveAllRanges(err); |
898 | 0 | if (NS_WARN_IF(err.Failed())) { |
899 | 0 | return err.StealNSResult(); |
900 | 0 | } |
901 | 0 | |
902 | 0 | selection->AddRange(*range, err); // NOTE: can destroy the world |
903 | 0 | if (NS_WARN_IF(err.Failed())) { |
904 | 0 | return err.StealNSResult(); |
905 | 0 | } |
906 | 0 | |
907 | 0 | selection->SetDirection(direction); |
908 | 0 | return rv; |
909 | 0 | } |
910 | | |
911 | | nsresult |
912 | | nsTextControlFrame::ScrollSelectionIntoView() |
913 | 0 | { |
914 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
915 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
916 | 0 | nsISelectionController* selCon = txtCtrl->GetSelectionController(); |
917 | 0 | if (selCon) { |
918 | 0 | // Scroll the selection into view (see bug 231389). |
919 | 0 | return selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, |
920 | 0 | nsISelectionController::SELECTION_FOCUS_REGION, |
921 | 0 | nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY); |
922 | 0 | } |
923 | 0 | |
924 | 0 | return NS_ERROR_FAILURE; |
925 | 0 | } |
926 | | |
927 | | nsresult |
928 | | nsTextControlFrame::SelectAllOrCollapseToEndOfText(bool aSelect) |
929 | 0 | { |
930 | 0 | nsresult rv = EnsureEditorInitialized(); |
931 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
932 | 0 | return rv; |
933 | 0 | } |
934 | 0 | |
935 | 0 | nsCOMPtr<nsINode> rootNode; |
936 | 0 | rootNode= mRootNode; |
937 | 0 |
|
938 | 0 | NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); |
939 | 0 |
|
940 | 0 | int32_t numChildren = mRootNode->GetChildCount(); |
941 | 0 |
|
942 | 0 | if (numChildren > 0) { |
943 | 0 | // We never want to place the selection after the last |
944 | 0 | // br under the root node! |
945 | 0 | nsIContent *child = mRootNode->GetLastChild(); |
946 | 0 | if (child) { |
947 | 0 | if (child->IsHTMLElement(nsGkAtoms::br)) { |
948 | 0 | child = child->GetPreviousSibling(); |
949 | 0 | --numChildren; |
950 | 0 | } else if (child->IsText() && !child->Length()) { |
951 | 0 | // Editor won't remove text node when empty value. |
952 | 0 | --numChildren; |
953 | 0 | } |
954 | 0 | } |
955 | 0 | if (!aSelect && numChildren) { |
956 | 0 | child = child->GetPreviousSibling(); |
957 | 0 | if (child && child->IsText()) { |
958 | 0 | rootNode = child; |
959 | 0 | const nsTextFragment* fragment = child->GetText(); |
960 | 0 | numChildren = fragment ? fragment->GetLength() : 0; |
961 | 0 | } |
962 | 0 | } |
963 | 0 | } |
964 | 0 |
|
965 | 0 | rv = SetSelectionInternal(rootNode, aSelect ? 0 : numChildren, |
966 | 0 | rootNode, numChildren); |
967 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
968 | 0 |
|
969 | 0 | return ScrollSelectionIntoView(); |
970 | 0 | } |
971 | | |
972 | | nsresult |
973 | | nsTextControlFrame::SetSelectionEndPoints(uint32_t aSelStart, uint32_t aSelEnd, |
974 | | nsITextControlFrame::SelectionDirection aDirection) |
975 | 0 | { |
976 | 0 | NS_ASSERTION(aSelStart <= aSelEnd, "Invalid selection offsets!"); |
977 | 0 |
|
978 | 0 | if (aSelStart > aSelEnd) |
979 | 0 | return NS_ERROR_FAILURE; |
980 | 0 | |
981 | 0 | nsCOMPtr<nsINode> startNode, endNode; |
982 | 0 | uint32_t startOffset, endOffset; |
983 | 0 |
|
984 | 0 | // Calculate the selection start point. |
985 | 0 |
|
986 | 0 | nsresult rv = OffsetToDOMPoint(aSelStart, getter_AddRefs(startNode), &startOffset); |
987 | 0 |
|
988 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
989 | 0 |
|
990 | 0 | if (aSelStart == aSelEnd) { |
991 | 0 | // Collapsed selection, so start and end are the same! |
992 | 0 | endNode = startNode; |
993 | 0 | endOffset = startOffset; |
994 | 0 | } |
995 | 0 | else { |
996 | 0 | // Selection isn't collapsed so we have to calculate |
997 | 0 | // the end point too. |
998 | 0 |
|
999 | 0 | rv = OffsetToDOMPoint(aSelEnd, getter_AddRefs(endNode), &endOffset); |
1000 | 0 |
|
1001 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1002 | 0 | } |
1003 | 0 |
|
1004 | 0 | return SetSelectionInternal(startNode, startOffset, endNode, endOffset, aDirection); |
1005 | 0 | } |
1006 | | |
1007 | | NS_IMETHODIMP |
1008 | | nsTextControlFrame::SetSelectionRange(uint32_t aSelStart, uint32_t aSelEnd, |
1009 | | nsITextControlFrame::SelectionDirection aDirection) |
1010 | 0 | { |
1011 | 0 | nsresult rv = EnsureEditorInitialized(); |
1012 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1013 | 0 |
|
1014 | 0 | if (aSelStart > aSelEnd) { |
1015 | 0 | // Simulate what we'd see SetSelectionStart() was called, followed |
1016 | 0 | // by a SetSelectionEnd(). |
1017 | 0 |
|
1018 | 0 | aSelStart = aSelEnd; |
1019 | 0 | } |
1020 | 0 |
|
1021 | 0 | return SetSelectionEndPoints(aSelStart, aSelEnd, aDirection); |
1022 | 0 | } |
1023 | | |
1024 | | |
1025 | | nsresult |
1026 | | nsTextControlFrame::OffsetToDOMPoint(uint32_t aOffset, |
1027 | | nsINode** aResult, |
1028 | | uint32_t* aPosition) |
1029 | 0 | { |
1030 | 0 | NS_ENSURE_ARG_POINTER(aResult && aPosition); |
1031 | 0 |
|
1032 | 0 | *aResult = nullptr; |
1033 | 0 | *aPosition = 0; |
1034 | 0 |
|
1035 | 0 | nsresult rv = EnsureEditorInitialized(); |
1036 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1037 | 0 | return rv; |
1038 | 0 | } |
1039 | 0 | |
1040 | 0 | RefPtr<Element> rootNode = mRootNode; |
1041 | 0 | NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); |
1042 | 0 |
|
1043 | 0 | nsCOMPtr<nsINodeList> nodeList = rootNode->ChildNodes(); |
1044 | 0 | uint32_t length = nodeList->Length(); |
1045 | 0 |
|
1046 | 0 | NS_ASSERTION(length <= 2, "We should have one text node and one mozBR at most"); |
1047 | 0 |
|
1048 | 0 | nsCOMPtr<nsINode> firstNode = nodeList->Item(0); |
1049 | 0 | Text* textNode = firstNode ? firstNode->GetAsText() : nullptr; |
1050 | 0 |
|
1051 | 0 | if (length == 0) { |
1052 | 0 | rootNode.forget(aResult); |
1053 | 0 | *aPosition = 0; |
1054 | 0 | } else if (textNode) { |
1055 | 0 | uint32_t textLength = textNode->Length(); |
1056 | 0 | if (length == 2 && aOffset == textLength) { |
1057 | 0 | // If we're at the end of the text node and we have a trailing BR node, |
1058 | 0 | // set the selection on the BR node. |
1059 | 0 | rootNode.forget(aResult); |
1060 | 0 | *aPosition = 1; |
1061 | 0 | } else { |
1062 | 0 | // Otherwise, set the selection on the textnode itself. |
1063 | 0 | firstNode.forget(aResult); |
1064 | 0 | *aPosition = std::min(aOffset, textLength); |
1065 | 0 | } |
1066 | 0 | } else { |
1067 | 0 | rootNode.forget(aResult); |
1068 | 0 | *aPosition = 0; |
1069 | 0 | } |
1070 | 0 |
|
1071 | 0 | return NS_OK; |
1072 | 0 | } |
1073 | | |
1074 | | /////END INTERFACE IMPLEMENTATIONS |
1075 | | |
1076 | | ////NSIFRAME |
1077 | | nsresult |
1078 | | nsTextControlFrame::AttributeChanged(int32_t aNameSpaceID, |
1079 | | nsAtom* aAttribute, |
1080 | | int32_t aModType) |
1081 | 0 | { |
1082 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
1083 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
1084 | 0 | nsISelectionController* selCon = txtCtrl->GetSelectionController(); |
1085 | 0 | const bool needEditor = nsGkAtoms::maxlength == aAttribute || |
1086 | 0 | nsGkAtoms::readonly == aAttribute || |
1087 | 0 | nsGkAtoms::disabled == aAttribute || |
1088 | 0 | nsGkAtoms::spellcheck == aAttribute; |
1089 | 0 | RefPtr<TextEditor> textEditor = needEditor ? GetTextEditor() : nullptr; |
1090 | 0 | if ((needEditor && !textEditor) || !selCon) { |
1091 | 0 | return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); |
1092 | 0 | } |
1093 | 0 | |
1094 | 0 | if (nsGkAtoms::maxlength == aAttribute) { |
1095 | 0 | int32_t maxLength; |
1096 | 0 | bool maxDefined = GetMaxLength(&maxLength); |
1097 | 0 | if (textEditor) { |
1098 | 0 | if (maxDefined) { // set the maxLength attribute |
1099 | 0 | textEditor->SetMaxTextLength(maxLength); |
1100 | 0 | // if maxLength>docLength, we need to truncate the doc content |
1101 | 0 | } else { // unset the maxLength attribute |
1102 | 0 | textEditor->SetMaxTextLength(-1); |
1103 | 0 | } |
1104 | 0 | } |
1105 | 0 | return NS_OK; |
1106 | 0 | } |
1107 | 0 |
|
1108 | 0 | if (nsGkAtoms::readonly == aAttribute) { |
1109 | 0 | if (AttributeExists(nsGkAtoms::readonly)) { // set readonly |
1110 | 0 | if (nsContentUtils::IsFocusedContent(mContent)) { |
1111 | 0 | selCon->SetCaretEnabled(false); |
1112 | 0 | } |
1113 | 0 | textEditor->AddFlags(nsIPlaintextEditor::eEditorReadonlyMask); |
1114 | 0 | } else { // unset readonly |
1115 | 0 | if (!textEditor->IsDisabled() && |
1116 | 0 | nsContentUtils::IsFocusedContent(mContent)) { |
1117 | 0 | selCon->SetCaretEnabled(true); |
1118 | 0 | } |
1119 | 0 | textEditor->RemoveFlags(nsIPlaintextEditor::eEditorReadonlyMask); |
1120 | 0 | } |
1121 | 0 | return NS_OK; |
1122 | 0 | } |
1123 | 0 |
|
1124 | 0 | if (nsGkAtoms::disabled == aAttribute) { |
1125 | 0 | int16_t displaySelection = nsISelectionController::SELECTION_OFF; |
1126 | 0 | const bool focused = nsContentUtils::IsFocusedContent(mContent); |
1127 | 0 | const bool hasAttr = AttributeExists(nsGkAtoms::disabled); |
1128 | 0 | bool disable; |
1129 | 0 | if (hasAttr) { // set disabled |
1130 | 0 | disable = true; |
1131 | 0 | } else { // unset disabled |
1132 | 0 | disable = false; |
1133 | 0 | displaySelection = focused ? nsISelectionController::SELECTION_ON |
1134 | 0 | : nsISelectionController::SELECTION_HIDDEN; |
1135 | 0 | } |
1136 | 0 | selCon->SetDisplaySelection(displaySelection); |
1137 | 0 | if (focused) { |
1138 | 0 | selCon->SetCaretEnabled(!hasAttr); |
1139 | 0 | } |
1140 | 0 | if (disable) { |
1141 | 0 | textEditor->AddFlags(nsIPlaintextEditor::eEditorDisabledMask); |
1142 | 0 | } else { |
1143 | 0 | textEditor->RemoveFlags(nsIPlaintextEditor::eEditorDisabledMask); |
1144 | 0 | } |
1145 | 0 | return NS_OK; |
1146 | 0 | } |
1147 | 0 |
|
1148 | 0 | if (!mEditorHasBeenInitialized && nsGkAtoms::value == aAttribute) { |
1149 | 0 | UpdateValueDisplay(true); |
1150 | 0 | return NS_OK; |
1151 | 0 | } |
1152 | 0 | |
1153 | 0 | // Allow the base class to handle common attributes supported by all form |
1154 | 0 | // elements... |
1155 | 0 | return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); |
1156 | 0 | } |
1157 | | |
1158 | | |
1159 | | void |
1160 | | nsTextControlFrame::GetText(nsString& aText) |
1161 | 0 | { |
1162 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
1163 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
1164 | 0 | if (IsSingleLineTextControl()) { |
1165 | 0 | // There will be no line breaks so we can ignore the wrap property. |
1166 | 0 | txtCtrl->GetTextEditorValue(aText, true); |
1167 | 0 | } else { |
1168 | 0 | HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(mContent); |
1169 | 0 | if (textArea) { |
1170 | 0 | textArea->GetValue(aText); |
1171 | 0 | } |
1172 | 0 | } |
1173 | 0 | } |
1174 | | |
1175 | | |
1176 | | ///END NSIFRAME OVERLOADS |
1177 | | /////BEGIN PROTECTED METHODS |
1178 | | |
1179 | | bool |
1180 | | nsTextControlFrame::GetMaxLength(int32_t* aSize) |
1181 | 0 | { |
1182 | 0 | *aSize = -1; |
1183 | 0 |
|
1184 | 0 | nsGenericHTMLElement *content = nsGenericHTMLElement::FromNode(mContent); |
1185 | 0 | if (content) { |
1186 | 0 | const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::maxlength); |
1187 | 0 | if (attr && attr->Type() == nsAttrValue::eInteger) { |
1188 | 0 | *aSize = attr->GetIntegerValue(); |
1189 | 0 |
|
1190 | 0 | return true; |
1191 | 0 | } |
1192 | 0 | } |
1193 | 0 | return false; |
1194 | 0 | } |
1195 | | |
1196 | | // END IMPLEMENTING NS_IFORMCONTROLFRAME |
1197 | | |
1198 | | void |
1199 | | nsTextControlFrame::SetInitialChildList(ChildListID aListID, |
1200 | | nsFrameList& aChildList) |
1201 | 0 | { |
1202 | 0 | nsContainerFrame::SetInitialChildList(aListID, aChildList); |
1203 | 0 | if (aListID != kPrincipalList) { |
1204 | 0 | return; |
1205 | 0 | } |
1206 | 0 | |
1207 | 0 | // Mark the scroll frame as being a reflow root. This will allow |
1208 | 0 | // incremental reflows to be initiated at the scroll frame, rather |
1209 | 0 | // than descending from the root frame of the frame hierarchy. |
1210 | 0 | if (nsIFrame* first = PrincipalChildList().FirstChild()) { |
1211 | 0 | first->AddStateBits(NS_FRAME_REFLOW_ROOT); |
1212 | 0 |
|
1213 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
1214 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
1215 | 0 | txtCtrl->InitializeKeyboardEventListeners(); |
1216 | 0 |
|
1217 | 0 | nsPoint* contentScrollPos = GetProperty(ContentScrollPos()); |
1218 | 0 | if (contentScrollPos) { |
1219 | 0 | // If we have a scroll pos stored to be passed to our anonymous |
1220 | 0 | // div, do it here! |
1221 | 0 | nsIStatefulFrame* statefulFrame = do_QueryFrame(first); |
1222 | 0 | NS_ASSERTION(statefulFrame, "unexpected type of frame for the anonymous div"); |
1223 | 0 | UniquePtr<PresState> fakePresState = NewPresState(); |
1224 | 0 | fakePresState->scrollState() = *contentScrollPos; |
1225 | 0 | statefulFrame->RestoreState(fakePresState.get()); |
1226 | 0 | RemoveProperty(ContentScrollPos()); |
1227 | 0 | delete contentScrollPos; |
1228 | 0 | } |
1229 | 0 | } |
1230 | 0 | } |
1231 | | |
1232 | | void |
1233 | | nsTextControlFrame::SetValueChanged(bool aValueChanged) |
1234 | 0 | { |
1235 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = HTMLInputElement::FromNode(GetContent()); |
1236 | 0 | if (!txtCtrl) { |
1237 | 0 | txtCtrl = HTMLTextAreaElement::FromNode(GetContent()); |
1238 | 0 | } |
1239 | 0 | MOZ_ASSERT(txtCtrl, "Content not a text control element"); |
1240 | 0 |
|
1241 | 0 | if (mPlaceholderDiv) { |
1242 | 0 | AutoWeakFrame weakFrame(this); |
1243 | 0 | txtCtrl->UpdateOverlayTextVisibility(true); |
1244 | 0 | if (!weakFrame.IsAlive()) { |
1245 | 0 | return; |
1246 | 0 | } |
1247 | 0 | } |
1248 | 0 | |
1249 | 0 | txtCtrl->SetValueChanged(aValueChanged); |
1250 | 0 | } |
1251 | | |
1252 | | |
1253 | | nsresult |
1254 | | nsTextControlFrame::UpdateValueDisplay(bool aNotify, |
1255 | | bool aBeforeEditorInit, |
1256 | | const nsAString *aValue) |
1257 | 0 | { |
1258 | 0 | if (!IsSingleLineTextControl()) // textareas don't use this |
1259 | 0 | return NS_OK; |
1260 | 0 | |
1261 | 0 | MOZ_ASSERT(mRootNode, "Must have a div content\n"); |
1262 | 0 | MOZ_ASSERT(!mEditorHasBeenInitialized, |
1263 | 0 | "Do not call this after editor has been initialized"); |
1264 | 0 |
|
1265 | 0 | nsIContent* childContent = mRootNode->GetFirstChild(); |
1266 | 0 | Text* textContent; |
1267 | 0 | if (!childContent) { |
1268 | 0 | // Set up a textnode with our value |
1269 | 0 | RefPtr<nsTextNode> textNode = |
1270 | 0 | new nsTextNode(mContent->NodeInfo()->NodeInfoManager()); |
1271 | 0 | textNode->MarkAsMaybeModifiedFrequently(); |
1272 | 0 |
|
1273 | 0 | mRootNode->AppendChildTo(textNode, aNotify); |
1274 | 0 | textContent = textNode; |
1275 | 0 | } else { |
1276 | 0 | textContent = childContent->GetAsText(); |
1277 | 0 | } |
1278 | 0 |
|
1279 | 0 | NS_ENSURE_TRUE(textContent, NS_ERROR_UNEXPECTED); |
1280 | 0 |
|
1281 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
1282 | 0 | MOZ_ASSERT(txtCtrl); |
1283 | 0 |
|
1284 | 0 | // Get the current value of the textfield from the content. |
1285 | 0 | nsAutoString value; |
1286 | 0 | if (aValue) { |
1287 | 0 | value = *aValue; |
1288 | 0 | } else { |
1289 | 0 | txtCtrl->GetTextEditorValue(value, true); |
1290 | 0 | } |
1291 | 0 |
|
1292 | 0 | // Update the display of the placeholder value and preview text if needed. |
1293 | 0 | // We don't need to do this if we're about to initialize the editor, since |
1294 | 0 | // EnsureEditorInitialized takes care of this. |
1295 | 0 | if ((mPlaceholderDiv || mPreviewDiv) && !aBeforeEditorInit) { |
1296 | 0 | AutoWeakFrame weakFrame(this); |
1297 | 0 | txtCtrl->UpdateOverlayTextVisibility(aNotify); |
1298 | 0 | NS_ENSURE_STATE(weakFrame.IsAlive()); |
1299 | 0 | } |
1300 | 0 |
|
1301 | 0 | if (aBeforeEditorInit && value.IsEmpty()) { |
1302 | 0 | nsIContent* node = mRootNode->GetFirstChild(); |
1303 | 0 | if (node) { |
1304 | 0 | mRootNode->RemoveChildNode(node, true); |
1305 | 0 | } |
1306 | 0 | return NS_OK; |
1307 | 0 | } |
1308 | 0 |
|
1309 | 0 | if (!value.IsEmpty() && IsPasswordTextControl()) { |
1310 | 0 | TextEditRules::FillBufWithPWChars(&value, value.Length()); |
1311 | 0 | } |
1312 | 0 | return textContent->SetText(value, aNotify); |
1313 | 0 | } |
1314 | | |
1315 | | NS_IMETHODIMP |
1316 | | nsTextControlFrame::GetOwnedSelectionController(nsISelectionController** aSelCon) |
1317 | 0 | { |
1318 | 0 | NS_ENSURE_ARG_POINTER(aSelCon); |
1319 | 0 |
|
1320 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
1321 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
1322 | 0 |
|
1323 | 0 | *aSelCon = txtCtrl->GetSelectionController(); |
1324 | 0 | NS_IF_ADDREF(*aSelCon); |
1325 | 0 |
|
1326 | 0 | return NS_OK; |
1327 | 0 | } |
1328 | | |
1329 | | nsFrameSelection* |
1330 | | nsTextControlFrame::GetOwnedFrameSelection() |
1331 | 0 | { |
1332 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
1333 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element"); |
1334 | 0 |
|
1335 | 0 | return txtCtrl->GetConstFrameSelection(); |
1336 | 0 | } |
1337 | | |
1338 | | UniquePtr<PresState> |
1339 | | nsTextControlFrame::SaveState() |
1340 | 0 | { |
1341 | 0 | if (mRootNode) { |
1342 | 0 | // Query the nsIStatefulFrame from the HTMLScrollFrame |
1343 | 0 | nsIStatefulFrame* scrollStateFrame = |
1344 | 0 | do_QueryFrame(mRootNode->GetPrimaryFrame()); |
1345 | 0 | if (scrollStateFrame) { |
1346 | 0 | return scrollStateFrame->SaveState(); |
1347 | 0 | } |
1348 | 0 | } |
1349 | 0 | |
1350 | 0 | return nullptr; |
1351 | 0 | } |
1352 | | |
1353 | | NS_IMETHODIMP |
1354 | | nsTextControlFrame::RestoreState(PresState* aState) |
1355 | 0 | { |
1356 | 0 | NS_ENSURE_ARG_POINTER(aState); |
1357 | 0 |
|
1358 | 0 | if (mRootNode) { |
1359 | 0 | // Query the nsIStatefulFrame from the HTMLScrollFrame |
1360 | 0 | nsIStatefulFrame* scrollStateFrame = |
1361 | 0 | do_QueryFrame(mRootNode->GetPrimaryFrame()); |
1362 | 0 | if (scrollStateFrame) { |
1363 | 0 | return scrollStateFrame->RestoreState(aState); |
1364 | 0 | } |
1365 | 0 | } |
1366 | 0 | |
1367 | 0 | // Most likely, we don't have our anonymous content constructed yet, which |
1368 | 0 | // would cause us to end up here. In this case, we'll just store the scroll |
1369 | 0 | // pos ourselves, and forward it to the scroll frame later when it's created. |
1370 | 0 | SetProperty(ContentScrollPos(), new nsPoint(aState->scrollState())); |
1371 | 0 | return NS_OK; |
1372 | 0 | } |
1373 | | |
1374 | | nsresult |
1375 | | nsTextControlFrame::PeekOffset(nsPeekOffsetStruct *aPos) |
1376 | 0 | { |
1377 | 0 | return NS_ERROR_FAILURE; |
1378 | 0 | } |
1379 | | |
1380 | | void |
1381 | | nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
1382 | | const nsDisplayListSet& aLists) |
1383 | 0 | { |
1384 | 0 | /* |
1385 | 0 | * The implementation of this method is equivalent as: |
1386 | 0 | * nsContainerFrame::BuildDisplayList() |
1387 | 0 | * with the difference that we filter-out the placeholder frame when it |
1388 | 0 | * should not be visible. |
1389 | 0 | */ |
1390 | 0 | DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame"); |
1391 | 0 |
|
1392 | 0 | nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); |
1393 | 0 | NS_ASSERTION(txtCtrl, "Content not a text control element!"); |
1394 | 0 |
|
1395 | 0 | DisplayBorderBackgroundOutline(aBuilder, aLists); |
1396 | 0 |
|
1397 | 0 | nsIFrame* kid = mFrames.FirstChild(); |
1398 | 0 | // Redirect all lists to the Content list so that nothing can escape, ie |
1399 | 0 | // opacity creating stacking contexts that then get sorted with stacking |
1400 | 0 | // contexts external to us. |
1401 | 0 | nsDisplayList* content = aLists.Content(); |
1402 | 0 | nsDisplayListSet set(content, content, content, content, content, content); |
1403 | 0 |
|
1404 | 0 | while (kid) { |
1405 | 0 | // If the frame is the placeholder or preview frame, we should only show |
1406 | 0 | // it if it has to be visible. |
1407 | 0 | if (!((kid->GetContent() == mPlaceholderDiv && |
1408 | 0 | !txtCtrl->GetPlaceholderVisibility()) || |
1409 | 0 | (kid->GetContent() == mPreviewDiv && |
1410 | 0 | !txtCtrl->GetPreviewVisibility()))) { |
1411 | 0 | BuildDisplayListForChild(aBuilder, kid, set, 0); |
1412 | 0 | } |
1413 | 0 | kid = kid->GetNextSibling(); |
1414 | 0 | } |
1415 | 0 | } |
1416 | | |
1417 | | NS_IMETHODIMP |
1418 | | nsTextControlFrame::EditorInitializer::Run() |
1419 | 0 | { |
1420 | 0 | if (!mFrame) { |
1421 | 0 | return NS_OK; |
1422 | 0 | } |
1423 | 0 | |
1424 | 0 | // Need to block script to avoid bug 669767. |
1425 | 0 | nsAutoScriptBlocker scriptBlocker; |
1426 | 0 |
|
1427 | 0 | nsCOMPtr<nsIPresShell> shell = |
1428 | 0 | mFrame->PresContext()->GetPresShell(); |
1429 | 0 | bool observes = shell->ObservesNativeAnonMutationsForPrint(); |
1430 | 0 | shell->ObserveNativeAnonMutationsForPrint(true); |
1431 | 0 | // This can cause the frame to be destroyed (and call Revoke()). |
1432 | 0 | mFrame->EnsureEditorInitialized(); |
1433 | 0 | shell->ObserveNativeAnonMutationsForPrint(observes); |
1434 | 0 |
|
1435 | 0 | // The frame can *still* be destroyed even though we have a scriptblocker, |
1436 | 0 | // bug 682684. |
1437 | 0 | if (!mFrame) { |
1438 | 0 | return NS_ERROR_FAILURE; |
1439 | 0 | } |
1440 | 0 | |
1441 | 0 | mFrame->FinishedInitializer(); |
1442 | 0 | return NS_OK; |
1443 | 0 | } |
1444 | | |
1445 | | NS_IMPL_ISUPPORTS(nsTextControlFrame::nsAnonDivObserver, nsIMutationObserver) |
1446 | | |
1447 | | void |
1448 | | nsTextControlFrame::nsAnonDivObserver::CharacterDataChanged( |
1449 | | nsIContent* aContent, |
1450 | | const CharacterDataChangeInfo&) |
1451 | 0 | { |
1452 | 0 | mFrame.ClearCachedValue(); |
1453 | 0 | } |
1454 | | |
1455 | | void |
1456 | | nsTextControlFrame::nsAnonDivObserver::ContentAppended( |
1457 | | nsIContent* aFirstNewContent) |
1458 | 0 | { |
1459 | 0 | mFrame.ClearCachedValue(); |
1460 | 0 | } |
1461 | | |
1462 | | void |
1463 | | nsTextControlFrame::nsAnonDivObserver::ContentInserted(nsIContent* aChild) |
1464 | 0 | { |
1465 | 0 | mFrame.ClearCachedValue(); |
1466 | 0 | } |
1467 | | |
1468 | | void |
1469 | | nsTextControlFrame::nsAnonDivObserver::ContentRemoved( |
1470 | | nsIContent* aChild, |
1471 | | nsIContent* aPreviousSibling) |
1472 | 0 | { |
1473 | 0 | mFrame.ClearCachedValue(); |
1474 | 0 | } |