/src/mozilla-central/dom/html/nsTextEditorState.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 "nsTextEditorState.h" |
8 | | #include "mozilla/TextInputListener.h" |
9 | | |
10 | | #include "nsCOMPtr.h" |
11 | | #include "nsIPresShell.h" |
12 | | #include "nsView.h" |
13 | | #include "nsCaret.h" |
14 | | #include "nsLayoutCID.h" |
15 | | #include "nsITextControlFrame.h" |
16 | | #include "nsContentCreatorFunctions.h" |
17 | | #include "nsTextControlFrame.h" |
18 | | #include "nsIControllers.h" |
19 | | #include "nsITransactionManager.h" |
20 | | #include "nsIControllerContext.h" |
21 | | #include "nsAttrValue.h" |
22 | | #include "nsAttrValueInlines.h" |
23 | | #include "nsGenericHTMLElement.h" |
24 | | #include "nsIDOMEventListener.h" |
25 | | #include "nsIEditorObserver.h" |
26 | | #include "nsIWidget.h" |
27 | | #include "nsIDocumentEncoder.h" |
28 | | #include "nsPIDOMWindow.h" |
29 | | #include "nsServiceManagerUtils.h" |
30 | | #include "mozilla/dom/Selection.h" |
31 | | #include "mozilla/TextEditRules.h" |
32 | | #include "mozilla/EventListenerManager.h" |
33 | | #include "nsContentUtils.h" |
34 | | #include "mozilla/Preferences.h" |
35 | | #include "nsTextNode.h" |
36 | | #include "nsIController.h" |
37 | | #include "mozilla/AutoRestore.h" |
38 | | #include "mozilla/TextEvents.h" |
39 | | #include "mozilla/dom/Event.h" |
40 | | #include "mozilla/dom/ScriptSettings.h" |
41 | | #include "mozilla/dom/HTMLInputElement.h" |
42 | | #include "mozilla/dom/HTMLTextAreaElement.h" |
43 | | #include "mozilla/dom/Text.h" |
44 | | #include "nsNumberControlFrame.h" |
45 | | #include "nsFrameSelection.h" |
46 | | #include "mozilla/ErrorResult.h" |
47 | | #include "mozilla/Telemetry.h" |
48 | | |
49 | | using namespace mozilla; |
50 | | using namespace mozilla::dom; |
51 | | |
52 | | inline nsresult |
53 | | SetEditorFlagsIfNecessary(EditorBase& aEditorBase, uint32_t aFlags) |
54 | 0 | { |
55 | 0 | if (aEditorBase.Flags() == aFlags) { |
56 | 0 | return NS_OK; |
57 | 0 | } |
58 | 0 | return aEditorBase.SetFlags(aFlags); |
59 | 0 | } |
60 | | |
61 | | class MOZ_STACK_CLASS ValueSetter |
62 | | { |
63 | | public: |
64 | | explicit ValueSetter(TextEditor* aTextEditor) |
65 | | : mTextEditor(aTextEditor) |
66 | | // To protect against a reentrant call to SetValue, we check whether |
67 | | // another SetValue is already happening for this editor. If it is, |
68 | | // we must wait until we unwind to re-enable oninput events. |
69 | | , mOuterTransaction(aTextEditor->IsSuppressingDispatchingInputEvent()) |
70 | 0 | { |
71 | 0 | MOZ_ASSERT(aTextEditor); |
72 | 0 | } |
73 | | ~ValueSetter() |
74 | 0 | { |
75 | 0 | mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction); |
76 | 0 | } |
77 | | void Init() |
78 | 0 | { |
79 | 0 | mTextEditor->SuppressDispatchingInputEvent(true); |
80 | 0 | } |
81 | | |
82 | | private: |
83 | | RefPtr<TextEditor> mTextEditor; |
84 | | bool mOuterTransaction; |
85 | | }; |
86 | | |
87 | | class RestoreSelectionState : public Runnable { |
88 | | public: |
89 | | RestoreSelectionState(nsTextEditorState* aState, nsTextControlFrame* aFrame) |
90 | | : mozilla::Runnable("RestoreSelectionState") |
91 | | , mFrame(aFrame) |
92 | | , mTextEditorState(aState) |
93 | 0 | { |
94 | 0 | } |
95 | | |
96 | 0 | NS_IMETHOD Run() override { |
97 | 0 | if (!mTextEditorState) { |
98 | 0 | return NS_OK; |
99 | 0 | } |
100 | 0 | |
101 | 0 | AutoHideSelectionChanges hideSelectionChanges |
102 | 0 | (mFrame->GetConstFrameSelection()); |
103 | 0 |
|
104 | 0 | if (mFrame) { |
105 | 0 | // SetSelectionRange leads to Selection::AddRange which flushes Layout - |
106 | 0 | // need to block script to avoid nested PrepareEditor calls (bug 642800). |
107 | 0 | nsAutoScriptBlocker scriptBlocker; |
108 | 0 | nsTextEditorState::SelectionProperties& properties = |
109 | 0 | mTextEditorState->GetSelectionProperties(); |
110 | 0 | if (properties.IsDirty()) { |
111 | 0 | mFrame->SetSelectionRange(properties.GetStart(), |
112 | 0 | properties.GetEnd(), |
113 | 0 | properties.GetDirection()); |
114 | 0 | } |
115 | 0 | if (!mTextEditorState->mSelectionRestoreEagerInit) { |
116 | 0 | mTextEditorState->HideSelectionIfBlurred(); |
117 | 0 | } |
118 | 0 | mTextEditorState->mSelectionRestoreEagerInit = false; |
119 | 0 | } |
120 | 0 |
|
121 | 0 | if (mTextEditorState) { |
122 | 0 | mTextEditorState->FinishedRestoringSelection(); |
123 | 0 | } |
124 | 0 | return NS_OK; |
125 | 0 | } |
126 | | |
127 | | // Let the text editor tell us we're no longer relevant - avoids use of AutoWeakFrame |
128 | 0 | void Revoke() { |
129 | 0 | mFrame = nullptr; |
130 | 0 | mTextEditorState = nullptr; |
131 | 0 | } |
132 | | |
133 | | private: |
134 | | nsTextControlFrame* mFrame; |
135 | | nsTextEditorState* mTextEditorState; |
136 | | }; |
137 | | |
138 | | class MOZ_RAII AutoRestoreEditorState final |
139 | | { |
140 | | public: |
141 | | explicit AutoRestoreEditorState(TextEditor* aTextEditor |
142 | | MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
143 | | : mTextEditor(aTextEditor) |
144 | | , mSavedFlags(mTextEditor->Flags()) |
145 | | , mSavedMaxLength(mTextEditor->MaxTextLength()) |
146 | 0 | { |
147 | 0 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
148 | 0 | MOZ_ASSERT(mTextEditor); |
149 | 0 |
|
150 | 0 | // EditorBase::SetFlags() is a virtual method. Even though it does nothing |
151 | 0 | // if new flags and current flags are same, the calling cost causes |
152 | 0 | // appearing the method in profile. So, this class should check if it's |
153 | 0 | // necessary to call. |
154 | 0 | uint32_t flags = mSavedFlags; |
155 | 0 | flags &= ~(nsIPlaintextEditor::eEditorDisabledMask); |
156 | 0 | flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask); |
157 | 0 | flags |= nsIPlaintextEditor::eEditorDontEchoPassword; |
158 | 0 | if (mSavedFlags != flags) { |
159 | 0 | mTextEditor->SetFlags(flags); |
160 | 0 | } |
161 | 0 | mTextEditor->SetMaxTextLength(-1); |
162 | 0 | } |
163 | | |
164 | | ~AutoRestoreEditorState() |
165 | 0 | { |
166 | 0 | mTextEditor->SetMaxTextLength(mSavedMaxLength); |
167 | 0 | SetEditorFlagsIfNecessary(*mTextEditor, mSavedFlags); |
168 | 0 | } |
169 | | |
170 | | private: |
171 | | TextEditor* mTextEditor; |
172 | | uint32_t mSavedFlags; |
173 | | int32_t mSavedMaxLength; |
174 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
175 | | }; |
176 | | |
177 | | class MOZ_RAII AutoDisableUndo final |
178 | | { |
179 | | public: |
180 | | explicit AutoDisableUndo(TextEditor* aTextEditor |
181 | | MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
182 | | : mTextEditor(aTextEditor) |
183 | | , mNumberOfMaximumTransactions(0) |
184 | 0 | { |
185 | 0 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
186 | 0 | MOZ_ASSERT(mTextEditor); |
187 | 0 |
|
188 | 0 | mNumberOfMaximumTransactions = |
189 | 0 | mTextEditor ? mTextEditor->NumberOfMaximumTransactions() : 0; |
190 | 0 | DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo(); |
191 | 0 | NS_WARNING_ASSERTION(disabledUndoRedo, |
192 | 0 | "Failed to disable undo/redo transactions"); |
193 | 0 | } |
194 | | |
195 | | ~AutoDisableUndo() |
196 | 0 | { |
197 | 0 | // Don't change enable/disable of undo/redo if it's enabled after |
198 | 0 | // it's disabled by the constructor because we shouldn't change |
199 | 0 | // the maximum undo/redo count to the old value. |
200 | 0 | if (mTextEditor->IsUndoRedoEnabled()) { |
201 | 0 | return; |
202 | 0 | } |
203 | 0 | // If undo/redo was enabled, mNumberOfMaximumTransactions is -1 or lager |
204 | 0 | // than 0. Only when it's 0, it was disabled. |
205 | 0 | if (mNumberOfMaximumTransactions) { |
206 | 0 | DebugOnly<bool> enabledUndoRedo = |
207 | 0 | mTextEditor->EnableUndoRedo(mNumberOfMaximumTransactions); |
208 | 0 | NS_WARNING_ASSERTION(enabledUndoRedo, |
209 | 0 | "Failed to enable undo/redo transactions"); |
210 | 0 | } else { |
211 | 0 | DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo(); |
212 | 0 | NS_WARNING_ASSERTION(disabledUndoRedo, |
213 | 0 | "Failed to disable undo/redo transactions"); |
214 | 0 | } |
215 | 0 | } |
216 | | |
217 | | private: |
218 | | TextEditor* mTextEditor; |
219 | | int32_t mNumberOfMaximumTransactions; |
220 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
221 | | }; |
222 | | |
223 | | /*static*/ |
224 | | bool |
225 | | nsITextControlElement::GetWrapPropertyEnum(nsIContent* aContent, |
226 | | nsITextControlElement::nsHTMLTextWrap& aWrapProp) |
227 | 0 | { |
228 | 0 | // soft is the default; "physical" defaults to soft as well because all other |
229 | 0 | // browsers treat it that way and there is no real reason to maintain physical |
230 | 0 | // and virtual as separate entities if no one else does. Only hard and off |
231 | 0 | // do anything different. |
232 | 0 | aWrapProp = eHTMLTextWrap_Soft; // the default |
233 | 0 |
|
234 | 0 | nsAutoString wrap; |
235 | 0 | if (aContent->IsHTMLElement()) { |
236 | 0 | static Element::AttrValuesArray strings[] = |
237 | 0 | {&nsGkAtoms::HARD, &nsGkAtoms::OFF, nullptr}; |
238 | 0 |
|
239 | 0 | switch (aContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, |
240 | 0 | nsGkAtoms::wrap, strings, |
241 | 0 | eIgnoreCase)) { |
242 | 0 | case 0: aWrapProp = eHTMLTextWrap_Hard; break; |
243 | 0 | case 1: aWrapProp = eHTMLTextWrap_Off; break; |
244 | 0 | } |
245 | 0 | |
246 | 0 | return true; |
247 | 0 | } |
248 | 0 | |
249 | 0 | return false; |
250 | 0 | } |
251 | | |
252 | | /*static*/ |
253 | | already_AddRefed<nsITextControlElement> |
254 | | nsITextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost) |
255 | 0 | { |
256 | 0 | if (!aHost) { |
257 | 0 | return nullptr; |
258 | 0 | } |
259 | 0 | |
260 | 0 | nsCOMPtr<nsITextControlElement> parent = |
261 | 0 | do_QueryInterface(aHost->GetParent()); |
262 | 0 |
|
263 | 0 | return parent.forget(); |
264 | 0 | } |
265 | | |
266 | | static bool |
267 | | SuppressEventHandlers(nsPresContext* aPresContext) |
268 | 0 | { |
269 | 0 | bool suppressHandlers = false; |
270 | 0 |
|
271 | 0 | if (aPresContext) |
272 | 0 | { |
273 | 0 | // Right now we only suppress event handlers and controller manipulation |
274 | 0 | // when in a print preview or print context! |
275 | 0 |
|
276 | 0 | // In the current implementation, we only paginate when |
277 | 0 | // printing or in print preview. |
278 | 0 |
|
279 | 0 | suppressHandlers = aPresContext->IsPaginated(); |
280 | 0 | } |
281 | 0 |
|
282 | 0 | return suppressHandlers; |
283 | 0 | } |
284 | | |
285 | | class nsAnonDivObserver final : public nsStubMutationObserver |
286 | | { |
287 | | public: |
288 | | explicit nsAnonDivObserver(nsTextEditorState* aTextEditorState) |
289 | 0 | : mTextEditorState(aTextEditorState) {} |
290 | | NS_DECL_ISUPPORTS |
291 | | NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED |
292 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED |
293 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED |
294 | | NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED |
295 | | |
296 | | private: |
297 | 0 | ~nsAnonDivObserver() {} |
298 | | nsTextEditorState* mTextEditorState; |
299 | | }; |
300 | | |
301 | | class nsTextInputSelectionImpl final : public nsSupportsWeakReference |
302 | | , public nsISelectionController |
303 | | { |
304 | 0 | ~nsTextInputSelectionImpl(){} |
305 | | |
306 | | public: |
307 | | NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
308 | | NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTextInputSelectionImpl, nsISelectionController) |
309 | | |
310 | | nsTextInputSelectionImpl(nsFrameSelection *aSel, nsIPresShell *aShell, nsIContent *aLimiter); |
311 | | |
312 | | void SetScrollableFrame(nsIScrollableFrame *aScrollableFrame); |
313 | | nsFrameSelection* GetConstFrameSelection() |
314 | 0 | { return mFrameSelection; } |
315 | | // Will return null if !mFrameSelection. |
316 | | Selection* GetSelection(SelectionType aSelectionType); |
317 | | |
318 | | //NSISELECTIONCONTROLLER INTERFACES |
319 | | NS_IMETHOD SetDisplaySelection(int16_t toggle) override; |
320 | | NS_IMETHOD GetDisplaySelection(int16_t* _retval) override; |
321 | | NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override; |
322 | | NS_IMETHOD GetSelectionFlags(int16_t *aOutEnable) override; |
323 | | NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType, |
324 | | Selection** aSelection) override; |
325 | | Selection* GetSelection(RawSelectionType aRawSelectionType) override; |
326 | | NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType, |
327 | | int16_t aRegion, int16_t aFlags) override; |
328 | | NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override; |
329 | | nsresult RepaintSelection(nsPresContext* aPresContext, |
330 | | SelectionType aSelectionType); |
331 | | NS_IMETHOD SetCaretEnabled(bool enabled) override; |
332 | | NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override; |
333 | | NS_IMETHOD GetCaretEnabled(bool* _retval) override; |
334 | | NS_IMETHOD GetCaretVisible(bool* _retval) override; |
335 | | NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override; |
336 | | NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) override; |
337 | | NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override; |
338 | | NS_IMETHOD CharacterExtendForDelete() override; |
339 | | NS_IMETHOD CharacterExtendForBackspace() override; |
340 | | NS_IMETHOD WordMove(bool aForward, bool aExtend) override; |
341 | | NS_IMETHOD WordExtendForDelete(bool aForward) override; |
342 | | NS_IMETHOD LineMove(bool aForward, bool aExtend) override; |
343 | | NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override; |
344 | | NS_IMETHOD PageMove(bool aForward, bool aExtend) override; |
345 | | NS_IMETHOD CompleteScroll(bool aForward) override; |
346 | | NS_IMETHOD CompleteMove(bool aForward, bool aExtend) override; |
347 | | NS_IMETHOD ScrollPage(bool aForward) override; |
348 | | NS_IMETHOD ScrollLine(bool aForward) override; |
349 | | NS_IMETHOD ScrollCharacter(bool aRight) override; |
350 | | NS_IMETHOD SelectAll(void) override; |
351 | | NS_IMETHOD CheckVisibility(nsINode *node, int16_t startOffset, int16_t EndOffset, bool* _retval) override; |
352 | | virtual nsresult CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset, int16_t aEndOffset, bool* aRetval) override; |
353 | | |
354 | | private: |
355 | | RefPtr<nsFrameSelection> mFrameSelection; |
356 | | nsCOMPtr<nsIContent> mLimiter; |
357 | | nsIScrollableFrame *mScrollFrame; |
358 | | nsWeakPtr mPresShellWeak; |
359 | | }; |
360 | | |
361 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextInputSelectionImpl) |
362 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextInputSelectionImpl) |
363 | 0 | NS_INTERFACE_TABLE_HEAD(nsTextInputSelectionImpl) |
364 | 0 | NS_INTERFACE_TABLE(nsTextInputSelectionImpl, |
365 | 0 | nsISelectionController, |
366 | 0 | nsISelectionDisplay, |
367 | 0 | nsISupportsWeakReference) |
368 | 0 | NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsTextInputSelectionImpl) |
369 | 0 | NS_INTERFACE_MAP_END |
370 | | |
371 | | NS_IMPL_CYCLE_COLLECTION(nsTextInputSelectionImpl, mFrameSelection, mLimiter) |
372 | | |
373 | | |
374 | | // BEGIN nsTextInputSelectionImpl |
375 | | |
376 | | nsTextInputSelectionImpl::nsTextInputSelectionImpl(nsFrameSelection *aSel, |
377 | | nsIPresShell *aShell, |
378 | | nsIContent *aLimiter) |
379 | | : mScrollFrame(nullptr) |
380 | 0 | { |
381 | 0 | if (aSel && aShell) |
382 | 0 | { |
383 | 0 | mFrameSelection = aSel;//we are the owner now! |
384 | 0 | mLimiter = aLimiter; |
385 | 0 | bool accessibleCaretEnabled = |
386 | 0 | PresShell::AccessibleCaretEnabled(aLimiter->OwnerDoc()->GetDocShell()); |
387 | 0 | mFrameSelection->Init(aShell, mLimiter, accessibleCaretEnabled); |
388 | 0 | mPresShellWeak = do_GetWeakReference(aShell); |
389 | 0 | } |
390 | 0 | } |
391 | | |
392 | | void |
393 | | nsTextInputSelectionImpl::SetScrollableFrame(nsIScrollableFrame *aScrollableFrame) |
394 | 0 | { |
395 | 0 | mScrollFrame = aScrollableFrame; |
396 | 0 | if (!mScrollFrame && mFrameSelection) { |
397 | 0 | mFrameSelection->DisconnectFromPresShell(); |
398 | 0 | mFrameSelection = nullptr; |
399 | 0 | } |
400 | 0 | } |
401 | | |
402 | | Selection* |
403 | | nsTextInputSelectionImpl::GetSelection(SelectionType aSelectionType) |
404 | 0 | { |
405 | 0 | if (!mFrameSelection) { |
406 | 0 | return nullptr; |
407 | 0 | } |
408 | 0 | |
409 | 0 | return mFrameSelection->GetSelection(aSelectionType); |
410 | 0 | } |
411 | | |
412 | | NS_IMETHODIMP |
413 | | nsTextInputSelectionImpl::SetDisplaySelection(int16_t aToggle) |
414 | 0 | { |
415 | 0 | if (!mFrameSelection) |
416 | 0 | return NS_ERROR_NULL_POINTER; |
417 | 0 | |
418 | 0 | mFrameSelection->SetDisplaySelection(aToggle); |
419 | 0 | return NS_OK; |
420 | 0 | } |
421 | | |
422 | | NS_IMETHODIMP |
423 | | nsTextInputSelectionImpl::GetDisplaySelection(int16_t *aToggle) |
424 | 0 | { |
425 | 0 | if (!mFrameSelection) |
426 | 0 | return NS_ERROR_NULL_POINTER; |
427 | 0 | |
428 | 0 | *aToggle = mFrameSelection->GetDisplaySelection(); |
429 | 0 | return NS_OK; |
430 | 0 | } |
431 | | |
432 | | NS_IMETHODIMP |
433 | | nsTextInputSelectionImpl::SetSelectionFlags(int16_t aToggle) |
434 | 0 | { |
435 | 0 | return NS_OK;//stub this out. not used in input |
436 | 0 | } |
437 | | |
438 | | NS_IMETHODIMP |
439 | | nsTextInputSelectionImpl::GetSelectionFlags(int16_t *aOutEnable) |
440 | 0 | { |
441 | 0 | *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT; |
442 | 0 | return NS_OK; |
443 | 0 | } |
444 | | |
445 | | NS_IMETHODIMP |
446 | | nsTextInputSelectionImpl::GetSelectionFromScript(RawSelectionType aRawSelectionType, |
447 | | Selection** aSelection) |
448 | 0 | { |
449 | 0 | if (!mFrameSelection) |
450 | 0 | return NS_ERROR_NULL_POINTER; |
451 | 0 | |
452 | 0 | *aSelection = |
453 | 0 | mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType)); |
454 | 0 |
|
455 | 0 | // GetSelection() fails only when aRawSelectionType is invalid value. |
456 | 0 | if (!(*aSelection)) { |
457 | 0 | return NS_ERROR_INVALID_ARG; |
458 | 0 | } |
459 | 0 | |
460 | 0 | NS_ADDREF(*aSelection); |
461 | 0 | return NS_OK; |
462 | 0 | } |
463 | | |
464 | | Selection* |
465 | | nsTextInputSelectionImpl::GetSelection(RawSelectionType aRawSelectionType) |
466 | 0 | { |
467 | 0 | return GetSelection(ToSelectionType(aRawSelectionType)); |
468 | 0 | } |
469 | | |
470 | | NS_IMETHODIMP |
471 | | nsTextInputSelectionImpl::ScrollSelectionIntoView( |
472 | | RawSelectionType aRawSelectionType, |
473 | | int16_t aRegion, |
474 | | int16_t aFlags) |
475 | 0 | { |
476 | 0 | if (!mFrameSelection) |
477 | 0 | return NS_ERROR_FAILURE; |
478 | 0 | |
479 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
480 | 0 | return frameSelection->ScrollSelectionIntoView( |
481 | 0 | ToSelectionType(aRawSelectionType), |
482 | 0 | aRegion, aFlags); |
483 | 0 | } |
484 | | |
485 | | NS_IMETHODIMP |
486 | | nsTextInputSelectionImpl::RepaintSelection(RawSelectionType aRawSelectionType) |
487 | 0 | { |
488 | 0 | if (!mFrameSelection) |
489 | 0 | return NS_ERROR_FAILURE; |
490 | 0 | |
491 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
492 | 0 | return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType)); |
493 | 0 | } |
494 | | |
495 | | nsresult |
496 | | nsTextInputSelectionImpl::RepaintSelection(nsPresContext* aPresContext, |
497 | | SelectionType aSelectionType) |
498 | 0 | { |
499 | 0 | if (!mFrameSelection) |
500 | 0 | return NS_ERROR_FAILURE; |
501 | 0 | |
502 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
503 | 0 | return frameSelection->RepaintSelection(aSelectionType); |
504 | 0 | } |
505 | | |
506 | | NS_IMETHODIMP |
507 | | nsTextInputSelectionImpl::SetCaretEnabled(bool enabled) |
508 | 0 | { |
509 | 0 | if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; |
510 | 0 | |
511 | 0 | nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak); |
512 | 0 | if (!shell) return NS_ERROR_FAILURE; |
513 | 0 | |
514 | 0 | // tell the pres shell to enable the caret, rather than settings its visibility directly. |
515 | 0 | // this way the presShell's idea of caret visibility is maintained. |
516 | 0 | nsCOMPtr<nsISelectionController> selCon = do_QueryInterface(shell); |
517 | 0 | if (!selCon) return NS_ERROR_NO_INTERFACE; |
518 | 0 | selCon->SetCaretEnabled(enabled); |
519 | 0 |
|
520 | 0 | return NS_OK; |
521 | 0 | } |
522 | | |
523 | | NS_IMETHODIMP |
524 | | nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly) |
525 | 0 | { |
526 | 0 | if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; |
527 | 0 | nsresult result; |
528 | 0 | nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result); |
529 | 0 | if (shell) |
530 | 0 | { |
531 | 0 | RefPtr<nsCaret> caret = shell->GetCaret(); |
532 | 0 | if (caret) { |
533 | 0 | Selection* selection = |
534 | 0 | mFrameSelection->GetSelection(SelectionType::eNormal); |
535 | 0 | if (selection) { |
536 | 0 | caret->SetCaretReadOnly(aReadOnly); |
537 | 0 | } |
538 | 0 | return NS_OK; |
539 | 0 | } |
540 | 0 | } |
541 | 0 | return NS_ERROR_FAILURE; |
542 | 0 | } |
543 | | |
544 | | NS_IMETHODIMP |
545 | | nsTextInputSelectionImpl::GetCaretEnabled(bool *_retval) |
546 | 0 | { |
547 | 0 | return GetCaretVisible(_retval); |
548 | 0 | } |
549 | | |
550 | | NS_IMETHODIMP |
551 | | nsTextInputSelectionImpl::GetCaretVisible(bool *_retval) |
552 | 0 | { |
553 | 0 | if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; |
554 | 0 | nsresult result; |
555 | 0 | nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result); |
556 | 0 | if (shell) |
557 | 0 | { |
558 | 0 | RefPtr<nsCaret> caret = shell->GetCaret(); |
559 | 0 | if (caret) { |
560 | 0 | *_retval = caret->IsVisible(); |
561 | 0 | return NS_OK; |
562 | 0 | } |
563 | 0 | } |
564 | 0 | return NS_ERROR_FAILURE; |
565 | 0 | } |
566 | | |
567 | | NS_IMETHODIMP |
568 | | nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility) |
569 | 0 | { |
570 | 0 | if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; |
571 | 0 | nsresult result; |
572 | 0 | nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result); |
573 | 0 | if (shell) |
574 | 0 | { |
575 | 0 | RefPtr<nsCaret> caret = shell->GetCaret(); |
576 | 0 | if (caret) { |
577 | 0 | Selection* selection = |
578 | 0 | mFrameSelection->GetSelection(SelectionType::eNormal); |
579 | 0 | if (selection) { |
580 | 0 | caret->SetVisibilityDuringSelection(aVisibility); |
581 | 0 | } |
582 | 0 | return NS_OK; |
583 | 0 | } |
584 | 0 | } |
585 | 0 | return NS_ERROR_FAILURE; |
586 | 0 | } |
587 | | |
588 | | NS_IMETHODIMP |
589 | | nsTextInputSelectionImpl::PhysicalMove(int16_t aDirection, int16_t aAmount, |
590 | | bool aExtend) |
591 | 0 | { |
592 | 0 | if (mFrameSelection) { |
593 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
594 | 0 | return frameSelection->PhysicalMove(aDirection, aAmount, aExtend); |
595 | 0 | } |
596 | 0 | return NS_ERROR_NULL_POINTER; |
597 | 0 | } |
598 | | |
599 | | NS_IMETHODIMP |
600 | | nsTextInputSelectionImpl::CharacterMove(bool aForward, bool aExtend) |
601 | 0 | { |
602 | 0 | if (mFrameSelection) { |
603 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
604 | 0 | return frameSelection->CharacterMove(aForward, aExtend); |
605 | 0 | } |
606 | 0 | return NS_ERROR_NULL_POINTER; |
607 | 0 | } |
608 | | |
609 | | NS_IMETHODIMP |
610 | | nsTextInputSelectionImpl::CharacterExtendForDelete() |
611 | 0 | { |
612 | 0 | if (mFrameSelection) { |
613 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
614 | 0 | return frameSelection->CharacterExtendForDelete(); |
615 | 0 | } |
616 | 0 | return NS_ERROR_NULL_POINTER; |
617 | 0 | } |
618 | | |
619 | | NS_IMETHODIMP |
620 | | nsTextInputSelectionImpl::CharacterExtendForBackspace() |
621 | 0 | { |
622 | 0 | if (mFrameSelection) { |
623 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
624 | 0 | return frameSelection->CharacterExtendForBackspace(); |
625 | 0 | } |
626 | 0 | return NS_ERROR_NULL_POINTER; |
627 | 0 | } |
628 | | |
629 | | NS_IMETHODIMP |
630 | | nsTextInputSelectionImpl::WordMove(bool aForward, bool aExtend) |
631 | 0 | { |
632 | 0 | if (mFrameSelection) { |
633 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
634 | 0 | return frameSelection->WordMove(aForward, aExtend); |
635 | 0 | } |
636 | 0 | return NS_ERROR_NULL_POINTER; |
637 | 0 | } |
638 | | |
639 | | NS_IMETHODIMP |
640 | | nsTextInputSelectionImpl::WordExtendForDelete(bool aForward) |
641 | 0 | { |
642 | 0 | if (mFrameSelection) { |
643 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
644 | 0 | return frameSelection->WordExtendForDelete(aForward); |
645 | 0 | } |
646 | 0 | return NS_ERROR_NULL_POINTER; |
647 | 0 | } |
648 | | |
649 | | NS_IMETHODIMP |
650 | | nsTextInputSelectionImpl::LineMove(bool aForward, bool aExtend) |
651 | 0 | { |
652 | 0 | if (mFrameSelection) |
653 | 0 | { |
654 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
655 | 0 | nsresult result = frameSelection->LineMove(aForward, aExtend); |
656 | 0 | if (NS_FAILED(result)) |
657 | 0 | result = CompleteMove(aForward,aExtend); |
658 | 0 | return result; |
659 | 0 | } |
660 | 0 | return NS_ERROR_NULL_POINTER; |
661 | 0 | } |
662 | | |
663 | | |
664 | | NS_IMETHODIMP |
665 | | nsTextInputSelectionImpl::IntraLineMove(bool aForward, bool aExtend) |
666 | 0 | { |
667 | 0 | if (mFrameSelection) { |
668 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
669 | 0 | return frameSelection->IntraLineMove(aForward, aExtend); |
670 | 0 | } |
671 | 0 | return NS_ERROR_NULL_POINTER; |
672 | 0 | } |
673 | | |
674 | | |
675 | | NS_IMETHODIMP |
676 | | nsTextInputSelectionImpl::PageMove(bool aForward, bool aExtend) |
677 | 0 | { |
678 | 0 | // expected behavior for PageMove is to scroll AND move the caret |
679 | 0 | // and to remain relative position of the caret in view. see Bug 4302. |
680 | 0 | if (mScrollFrame) |
681 | 0 | { |
682 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
683 | 0 | frameSelection->CommonPageMove(aForward, aExtend, mScrollFrame); |
684 | 0 | } |
685 | 0 | // After ScrollSelectionIntoView(), the pending notifications might be |
686 | 0 | // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. |
687 | 0 | return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, |
688 | 0 | nsISelectionController::SELECTION_FOCUS_REGION, |
689 | 0 | nsISelectionController::SCROLL_SYNCHRONOUS | |
690 | 0 | nsISelectionController::SCROLL_FOR_CARET_MOVE); |
691 | 0 | } |
692 | | |
693 | | NS_IMETHODIMP |
694 | | nsTextInputSelectionImpl::CompleteScroll(bool aForward) |
695 | 0 | { |
696 | 0 | if (!mScrollFrame) |
697 | 0 | return NS_ERROR_NOT_INITIALIZED; |
698 | 0 | |
699 | 0 | mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), |
700 | 0 | nsIScrollableFrame::WHOLE, |
701 | 0 | nsIScrollableFrame::INSTANT); |
702 | 0 | return NS_OK; |
703 | 0 | } |
704 | | |
705 | | NS_IMETHODIMP |
706 | | nsTextInputSelectionImpl::CompleteMove(bool aForward, bool aExtend) |
707 | 0 | { |
708 | 0 | NS_ENSURE_STATE(mFrameSelection); |
709 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
710 | 0 |
|
711 | 0 | // grab the parent / root DIV for this text widget |
712 | 0 | nsIContent* parentDIV = frameSelection->GetLimiter(); |
713 | 0 | if (!parentDIV) |
714 | 0 | return NS_ERROR_UNEXPECTED; |
715 | 0 | |
716 | 0 | // make the caret be either at the very beginning (0) or the very end |
717 | 0 | int32_t offset = 0; |
718 | 0 | CaretAssociationHint hint = CARET_ASSOCIATE_BEFORE; |
719 | 0 | if (aForward) |
720 | 0 | { |
721 | 0 | offset = parentDIV->GetChildCount(); |
722 | 0 |
|
723 | 0 | // Prevent the caret from being placed after the last |
724 | 0 | // BR node in the content tree! |
725 | 0 |
|
726 | 0 | if (offset > 0) |
727 | 0 | { |
728 | 0 | nsIContent *child = parentDIV->GetLastChild(); |
729 | 0 |
|
730 | 0 | if (child->IsHTMLElement(nsGkAtoms::br)) |
731 | 0 | { |
732 | 0 | --offset; |
733 | 0 | hint = CARET_ASSOCIATE_AFTER; // for Bug 106855 |
734 | 0 | } |
735 | 0 | } |
736 | 0 | } |
737 | 0 |
|
738 | 0 | frameSelection->HandleClick(parentDIV, offset, offset, aExtend, |
739 | 0 | false, hint); |
740 | 0 |
|
741 | 0 | // if we got this far, attempt to scroll no matter what the above result is |
742 | 0 | return CompleteScroll(aForward); |
743 | 0 | } |
744 | | |
745 | | NS_IMETHODIMP |
746 | | nsTextInputSelectionImpl::ScrollPage(bool aForward) |
747 | 0 | { |
748 | 0 | if (!mScrollFrame) |
749 | 0 | return NS_ERROR_NOT_INITIALIZED; |
750 | 0 | |
751 | 0 | mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), |
752 | 0 | nsIScrollableFrame::PAGES, |
753 | 0 | nsIScrollableFrame::SMOOTH); |
754 | 0 | return NS_OK; |
755 | 0 | } |
756 | | |
757 | | NS_IMETHODIMP |
758 | | nsTextInputSelectionImpl::ScrollLine(bool aForward) |
759 | 0 | { |
760 | 0 | if (!mScrollFrame) |
761 | 0 | return NS_ERROR_NOT_INITIALIZED; |
762 | 0 | |
763 | 0 | mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), |
764 | 0 | nsIScrollableFrame::LINES, |
765 | 0 | nsIScrollableFrame::SMOOTH); |
766 | 0 | return NS_OK; |
767 | 0 | } |
768 | | |
769 | | NS_IMETHODIMP |
770 | | nsTextInputSelectionImpl::ScrollCharacter(bool aRight) |
771 | 0 | { |
772 | 0 | if (!mScrollFrame) |
773 | 0 | return NS_ERROR_NOT_INITIALIZED; |
774 | 0 | |
775 | 0 | mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0), |
776 | 0 | nsIScrollableFrame::LINES, |
777 | 0 | nsIScrollableFrame::SMOOTH); |
778 | 0 | return NS_OK; |
779 | 0 | } |
780 | | |
781 | | NS_IMETHODIMP |
782 | | nsTextInputSelectionImpl::SelectAll() |
783 | 0 | { |
784 | 0 | if (mFrameSelection) { |
785 | 0 | RefPtr<nsFrameSelection> frameSelection = mFrameSelection; |
786 | 0 | return frameSelection->SelectAll(); |
787 | 0 | } |
788 | 0 | return NS_ERROR_NULL_POINTER; |
789 | 0 | } |
790 | | |
791 | | NS_IMETHODIMP |
792 | | nsTextInputSelectionImpl::CheckVisibility(nsINode *node, int16_t startOffset, int16_t EndOffset, bool *_retval) |
793 | 0 | { |
794 | 0 | if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; |
795 | 0 | nsresult result; |
796 | 0 | nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak, &result); |
797 | 0 | if (shell) |
798 | 0 | { |
799 | 0 | return shell->CheckVisibility(node,startOffset,EndOffset, _retval); |
800 | 0 | } |
801 | 0 | return NS_ERROR_FAILURE; |
802 | 0 |
|
803 | 0 | } |
804 | | |
805 | | nsresult |
806 | | nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent* aNode, |
807 | | int16_t aStartOffset, |
808 | | int16_t aEndOffset, |
809 | | bool* aRetval) |
810 | 0 | { |
811 | 0 | if (!mPresShellWeak) { |
812 | 0 | return NS_ERROR_NOT_INITIALIZED; |
813 | 0 | } |
814 | 0 | |
815 | 0 | nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak); |
816 | 0 | NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); |
817 | 0 |
|
818 | 0 | return shell->CheckVisibilityContent(aNode, aStartOffset, aEndOffset, aRetval); |
819 | 0 | } |
820 | | |
821 | | /* |
822 | | * mozilla::TextInputListener implementation |
823 | | */ |
824 | | |
825 | | TextInputListener::TextInputListener(nsITextControlElement* aTxtCtrlElement) |
826 | | : mFrame(nullptr) |
827 | | , mTxtCtrlElement(aTxtCtrlElement) |
828 | | , mSelectionWasCollapsed(true) |
829 | | , mHadUndoItems(false) |
830 | | , mHadRedoItems(false) |
831 | | , mSettingValue(false) |
832 | | , mSetValueChanged(true) |
833 | | , mListeningToSelectionChange(false) |
834 | 0 | { |
835 | 0 | } |
836 | | |
837 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener) |
838 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener) |
839 | | |
840 | 0 | NS_INTERFACE_MAP_BEGIN(TextInputListener) |
841 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
842 | 0 | NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) |
843 | 0 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener) |
844 | 0 | NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener) |
845 | 0 | NS_INTERFACE_MAP_END |
846 | | |
847 | | NS_IMPL_CYCLE_COLLECTION_0(TextInputListener) |
848 | | |
849 | | void |
850 | | TextInputListener::OnSelectionChange(Selection& aSelection, |
851 | | int16_t aReason) |
852 | 0 | { |
853 | 0 | if (!mListeningToSelectionChange) { |
854 | 0 | return; |
855 | 0 | } |
856 | 0 | |
857 | 0 | AutoWeakFrame weakFrame = mFrame; |
858 | 0 |
|
859 | 0 | // Fire the select event |
860 | 0 | // The specs don't exactly say when we should fire the select event. |
861 | 0 | // IE: Whenever you add/remove a character to/from the selection. Also |
862 | 0 | // each time for select all. Also if you get to the end of the text |
863 | 0 | // field you will get new event for each keypress or a continuous |
864 | 0 | // stream of events if you use the mouse. IE will fire select event |
865 | 0 | // when the selection collapses to nothing if you are holding down |
866 | 0 | // the shift or mouse button. |
867 | 0 | // Mozilla: If we have non-empty selection we will fire a new event for each |
868 | 0 | // keypress (or mouseup) if the selection changed. Mozilla will also |
869 | 0 | // create the event each time select all is called, even if everything |
870 | 0 | // was previously selected, becase technically select all will first collapse |
871 | 0 | // and then extend. Mozilla will never create an event if the selection |
872 | 0 | // collapses to nothing. |
873 | 0 | bool collapsed = aSelection.IsCollapsed(); |
874 | 0 | if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON | |
875 | 0 | nsISelectionListener::KEYPRESS_REASON | |
876 | 0 | nsISelectionListener::SELECTALL_REASON))) { |
877 | 0 | nsIContent* content = mFrame->GetContent(); |
878 | 0 | if (content) { |
879 | 0 | nsCOMPtr<nsIDocument> doc = content->GetComposedDoc(); |
880 | 0 | if (doc) { |
881 | 0 | nsCOMPtr<nsIPresShell> presShell = doc->GetShell(); |
882 | 0 | if (presShell) { |
883 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
884 | 0 | WidgetEvent event(true, eFormSelect); |
885 | 0 |
|
886 | 0 | presShell->HandleEventWithTarget(&event, mFrame, content, &status); |
887 | 0 | } |
888 | 0 | } |
889 | 0 | } |
890 | 0 | } |
891 | 0 |
|
892 | 0 | // if the collapsed state did not change, don't fire notifications |
893 | 0 | if (collapsed == mSelectionWasCollapsed) { |
894 | 0 | return; |
895 | 0 | } |
896 | 0 | |
897 | 0 | mSelectionWasCollapsed = collapsed; |
898 | 0 |
|
899 | 0 | if (!weakFrame.IsAlive() || !mFrame || |
900 | 0 | !nsContentUtils::IsFocusedContent(mFrame->GetContent())) { |
901 | 0 | return; |
902 | 0 | } |
903 | 0 | |
904 | 0 | UpdateTextInputCommands(NS_LITERAL_STRING("select"), |
905 | 0 | &aSelection, aReason); |
906 | 0 | } |
907 | | |
908 | | static void |
909 | | DoCommandCallback(Command aCommand, void* aData) |
910 | 0 | { |
911 | 0 | nsTextControlFrame *frame = static_cast<nsTextControlFrame*>(aData); |
912 | 0 | nsIContent *content = frame->GetContent(); |
913 | 0 |
|
914 | 0 | nsCOMPtr<nsIControllers> controllers; |
915 | 0 | HTMLInputElement* input = HTMLInputElement::FromNode(content); |
916 | 0 | if (input) { |
917 | 0 | input->GetControllers(getter_AddRefs(controllers)); |
918 | 0 | } else { |
919 | 0 | HTMLTextAreaElement* textArea = |
920 | 0 | HTMLTextAreaElement::FromNode(content); |
921 | 0 |
|
922 | 0 | if (textArea) { |
923 | 0 | textArea->GetControllers(getter_AddRefs(controllers)); |
924 | 0 | } |
925 | 0 | } |
926 | 0 |
|
927 | 0 | if (!controllers) { |
928 | 0 | NS_WARNING("Could not get controllers"); |
929 | 0 | return; |
930 | 0 | } |
931 | 0 |
|
932 | 0 | const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand); |
933 | 0 |
|
934 | 0 | nsCOMPtr<nsIController> controller; |
935 | 0 | controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller)); |
936 | 0 | if (!controller) { |
937 | 0 | return; |
938 | 0 | } |
939 | 0 | |
940 | 0 | bool commandEnabled; |
941 | 0 | nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled); |
942 | 0 | NS_ENSURE_SUCCESS_VOID(rv); |
943 | 0 | if (commandEnabled) { |
944 | 0 | controller->DoCommand(commandStr); |
945 | 0 | } |
946 | 0 | } |
947 | | |
948 | | NS_IMETHODIMP |
949 | | TextInputListener::HandleEvent(Event* aEvent) |
950 | 0 | { |
951 | 0 | if (aEvent->DefaultPrevented()) { |
952 | 0 | return NS_OK; |
953 | 0 | } |
954 | 0 | |
955 | 0 | if (!aEvent->IsTrusted()) { |
956 | 0 | return NS_OK; |
957 | 0 | } |
958 | 0 | |
959 | 0 | WidgetKeyboardEvent* keyEvent = |
960 | 0 | aEvent->WidgetEventPtr()->AsKeyboardEvent(); |
961 | 0 | if (!keyEvent) { |
962 | 0 | return NS_ERROR_UNEXPECTED; |
963 | 0 | } |
964 | 0 | |
965 | 0 | if (keyEvent->mMessage != eKeyPress) { |
966 | 0 | return NS_OK; |
967 | 0 | } |
968 | 0 | |
969 | 0 | nsIWidget::NativeKeyBindingsType nativeKeyBindingsType = |
970 | 0 | mTxtCtrlElement->IsTextArea() ? |
971 | 0 | nsIWidget::NativeKeyBindingsForMultiLineEditor : |
972 | 0 | nsIWidget::NativeKeyBindingsForSingleLineEditor; |
973 | 0 |
|
974 | 0 | nsIWidget* widget = keyEvent->mWidget; |
975 | 0 | // If the event is created by chrome script, the widget is nullptr. |
976 | 0 | if (!widget) { |
977 | 0 | widget = mFrame->GetNearestWidget(); |
978 | 0 | NS_ENSURE_TRUE(widget, NS_OK); |
979 | 0 | } |
980 | 0 |
|
981 | 0 | // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget. |
982 | 0 | // If the event is created by chrome script, it is nullptr but we need to |
983 | 0 | // execute native key bindings. Therefore, we need to set widget to |
984 | 0 | // WidgetEvent::mWidget temporarily. |
985 | 0 | AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(keyEvent->mWidget); |
986 | 0 | keyEvent->mWidget = widget; |
987 | 0 | if (keyEvent->ExecuteEditCommands(nativeKeyBindingsType, |
988 | 0 | DoCommandCallback, mFrame)) { |
989 | 0 | aEvent->PreventDefault(); |
990 | 0 | } |
991 | 0 | return NS_OK; |
992 | 0 | } |
993 | | |
994 | | void |
995 | | TextInputListener::OnEditActionHandled() |
996 | 0 | { |
997 | 0 | if (!mFrame) { |
998 | 0 | // We've been disconnected from the nsTextEditorState object, nothing to do |
999 | 0 | // here. |
1000 | 0 | return; |
1001 | 0 | } |
1002 | 0 | |
1003 | 0 | AutoWeakFrame weakFrame = mFrame; |
1004 | 0 |
|
1005 | 0 | nsITextControlFrame* frameBase = do_QueryFrame(mFrame); |
1006 | 0 | nsTextControlFrame* frame = static_cast<nsTextControlFrame*> (frameBase); |
1007 | 0 | NS_ASSERTION(frame, "Where is our frame?"); |
1008 | 0 | // |
1009 | 0 | // Update the undo / redo menus |
1010 | 0 | // |
1011 | 0 | RefPtr<TextEditor> textEditor = frame->GetTextEditor(); |
1012 | 0 |
|
1013 | 0 | // Get the number of undo / redo items |
1014 | 0 | size_t numUndoItems = textEditor->NumberOfUndoItems(); |
1015 | 0 | size_t numRedoItems = textEditor->NumberOfRedoItems(); |
1016 | 0 | if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) || |
1017 | 0 | (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) { |
1018 | 0 | // Modify the menu if undo or redo items are different |
1019 | 0 | UpdateTextInputCommands(NS_LITERAL_STRING("undo")); |
1020 | 0 |
|
1021 | 0 | mHadUndoItems = numUndoItems != 0; |
1022 | 0 | mHadRedoItems = numRedoItems != 0; |
1023 | 0 | } |
1024 | 0 |
|
1025 | 0 | if (!weakFrame.IsAlive()) { |
1026 | 0 | return; |
1027 | 0 | } |
1028 | 0 | |
1029 | 0 | HandleValueChanged(frame); |
1030 | 0 | } |
1031 | | |
1032 | | void |
1033 | | TextInputListener::HandleValueChanged(nsTextControlFrame* aFrame) |
1034 | 0 | { |
1035 | 0 | // Make sure we know we were changed (do NOT set this to false if there are |
1036 | 0 | // no undo items; JS could change the value and we'd still need to save it) |
1037 | 0 | if (mSetValueChanged) { |
1038 | 0 | if (!aFrame) { |
1039 | 0 | nsITextControlFrame* frameBase = do_QueryFrame(mFrame); |
1040 | 0 | aFrame = static_cast<nsTextControlFrame*> (frameBase); |
1041 | 0 | NS_ASSERTION(aFrame, "Where is our frame?"); |
1042 | 0 | } |
1043 | 0 | aFrame->SetValueChanged(true); |
1044 | 0 | } |
1045 | 0 |
|
1046 | 0 | if (!mSettingValue) { |
1047 | 0 | mTxtCtrlElement->OnValueChanged(/* aNotify = */ true, |
1048 | 0 | /* aWasInteractiveUserChange = */ true); |
1049 | 0 | } |
1050 | 0 | } |
1051 | | |
1052 | | nsresult |
1053 | | TextInputListener::UpdateTextInputCommands(const nsAString& aCommandsToUpdate, |
1054 | | Selection* aSelection, |
1055 | | int16_t aReason) |
1056 | 0 | { |
1057 | 0 | nsIContent* content = mFrame->GetContent(); |
1058 | 0 | NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); |
1059 | 0 |
|
1060 | 0 | nsCOMPtr<nsIDocument> doc = content->GetComposedDoc(); |
1061 | 0 | NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); |
1062 | 0 |
|
1063 | 0 | nsPIDOMWindowOuter* domWindow = doc->GetWindow(); |
1064 | 0 | NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); |
1065 | 0 |
|
1066 | 0 | domWindow->UpdateCommands(aCommandsToUpdate, aSelection, aReason); |
1067 | 0 | return NS_OK; |
1068 | 0 | } |
1069 | | |
1070 | | // END mozilla::TextInputListener |
1071 | | |
1072 | | // nsTextEditorState |
1073 | | |
1074 | | nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement) |
1075 | | : mTextCtrlElement(aOwningElement) |
1076 | | , mBoundFrame(nullptr) |
1077 | | , mEverInited(false) |
1078 | | , mEditorInitialized(false) |
1079 | | , mInitializing(false) |
1080 | | , mValueTransferInProgress(false) |
1081 | | , mSelectionCached(true) |
1082 | | , mSelectionRestoreEagerInit(false) |
1083 | | , mPlaceholderVisibility(false) |
1084 | | , mPreviewVisibility(false) |
1085 | | , mIsCommittingComposition(false) |
1086 | | // When adding more member variable initializations here, add the same |
1087 | | // also to ::Construct. |
1088 | 0 | { |
1089 | 0 | MOZ_COUNT_CTOR(nsTextEditorState); |
1090 | 0 | } |
1091 | | |
1092 | | nsTextEditorState* |
1093 | | nsTextEditorState::Construct(nsITextControlElement* aOwningElement, |
1094 | | nsTextEditorState** aReusedState) |
1095 | 0 | { |
1096 | 0 | if (*aReusedState) { |
1097 | 0 | nsTextEditorState* state = *aReusedState; |
1098 | 0 | *aReusedState = nullptr; |
1099 | 0 | state->mTextCtrlElement = aOwningElement; |
1100 | 0 | state->mBoundFrame = nullptr; |
1101 | 0 | state->mSelectionProperties = SelectionProperties(); |
1102 | 0 | state->mEverInited = false; |
1103 | 0 | state->mEditorInitialized = false; |
1104 | 0 | state->mInitializing = false; |
1105 | 0 | state->mValueTransferInProgress = false; |
1106 | 0 | state->mSelectionCached = true; |
1107 | 0 | state->mSelectionRestoreEagerInit = false; |
1108 | 0 | state->mPlaceholderVisibility = false; |
1109 | 0 | state->mPreviewVisibility = false; |
1110 | 0 | state->mIsCommittingComposition = false; |
1111 | 0 | // When adding more member variable initializations here, add the same |
1112 | 0 | // also to the constructor. |
1113 | 0 | return state; |
1114 | 0 | } |
1115 | 0 | |
1116 | 0 | return new nsTextEditorState(aOwningElement); |
1117 | 0 | } |
1118 | | |
1119 | | nsTextEditorState::~nsTextEditorState() |
1120 | 0 | { |
1121 | 0 | MOZ_COUNT_DTOR(nsTextEditorState); |
1122 | 0 | Clear(); |
1123 | 0 | } |
1124 | | |
1125 | | Element* |
1126 | | nsTextEditorState::GetRootNode() |
1127 | 0 | { |
1128 | 0 | return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr; |
1129 | 0 | } |
1130 | | |
1131 | | Element* |
1132 | | nsTextEditorState::GetPreviewNode() |
1133 | 0 | { |
1134 | 0 | return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr; |
1135 | 0 | } |
1136 | | |
1137 | | void |
1138 | | nsTextEditorState::Clear() |
1139 | 0 | { |
1140 | 0 | if (mTextEditor) { |
1141 | 0 | mTextEditor->SetTextInputListener(nullptr); |
1142 | 0 | } |
1143 | 0 |
|
1144 | 0 | if (mBoundFrame) { |
1145 | 0 | // Oops, we still have a frame! |
1146 | 0 | // This should happen when the type of a text input control is being changed |
1147 | 0 | // to something which is not a text control. In this case, we should pretend |
1148 | 0 | // that a frame is being destroyed, and clean up after ourselves properly. |
1149 | 0 | UnbindFromFrame(mBoundFrame); |
1150 | 0 | mTextEditor = nullptr; |
1151 | 0 | } else { |
1152 | 0 | // If we have a bound frame around, UnbindFromFrame will call DestroyEditor |
1153 | 0 | // for us. |
1154 | 0 | DestroyEditor(); |
1155 | 0 | } |
1156 | 0 | mTextListener = nullptr; |
1157 | 0 | } |
1158 | | |
1159 | | void nsTextEditorState::Unlink() |
1160 | 0 | { |
1161 | 0 | nsTextEditorState* tmp = this; |
1162 | 0 | tmp->Clear(); |
1163 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon) |
1164 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor) |
1165 | 0 | } |
1166 | | |
1167 | | void nsTextEditorState::Traverse(nsCycleCollectionTraversalCallback& cb) |
1168 | 0 | { |
1169 | 0 | nsTextEditorState* tmp = this; |
1170 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon) |
1171 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor) |
1172 | 0 | } |
1173 | | |
1174 | | nsFrameSelection* |
1175 | 0 | nsTextEditorState::GetConstFrameSelection() { |
1176 | 0 | if (mSelCon) |
1177 | 0 | return mSelCon->GetConstFrameSelection(); |
1178 | 0 | return nullptr; |
1179 | 0 | } |
1180 | | |
1181 | | TextEditor* |
1182 | | nsTextEditorState::GetTextEditor() |
1183 | 0 | { |
1184 | 0 | if (!mTextEditor) { |
1185 | 0 | nsresult rv = PrepareEditor(); |
1186 | 0 | NS_ENSURE_SUCCESS(rv, nullptr); |
1187 | 0 | } |
1188 | 0 | return mTextEditor; |
1189 | 0 | } |
1190 | | |
1191 | | nsISelectionController* |
1192 | | nsTextEditorState::GetSelectionController() const |
1193 | 0 | { |
1194 | 0 | return mSelCon; |
1195 | 0 | } |
1196 | | |
1197 | | // Helper class, used below in BindToFrame(). |
1198 | | class PrepareEditorEvent : public Runnable { |
1199 | | public: |
1200 | | PrepareEditorEvent(nsTextEditorState& aState, |
1201 | | nsIContent* aOwnerContent, |
1202 | | const nsAString& aCurrentValue) |
1203 | | : mozilla::Runnable("PrepareEditorEvent") |
1204 | | , mState(&aState) |
1205 | | , mOwnerContent(aOwnerContent) |
1206 | | , mCurrentValue(aCurrentValue) |
1207 | 0 | { |
1208 | 0 | aState.mValueTransferInProgress = true; |
1209 | 0 | } |
1210 | | |
1211 | 0 | NS_IMETHOD Run() override { |
1212 | 0 | NS_ENSURE_TRUE(mState, NS_ERROR_NULL_POINTER); |
1213 | 0 |
|
1214 | 0 | // Transfer the saved value to the editor if we have one |
1215 | 0 | const nsAString *value = nullptr; |
1216 | 0 | if (!mCurrentValue.IsEmpty()) { |
1217 | 0 | value = &mCurrentValue; |
1218 | 0 | } |
1219 | 0 |
|
1220 | 0 | nsAutoScriptBlocker scriptBlocker; |
1221 | 0 |
|
1222 | 0 | mState->PrepareEditor(value); |
1223 | 0 |
|
1224 | 0 | mState->mValueTransferInProgress = false; |
1225 | 0 |
|
1226 | 0 | return NS_OK; |
1227 | 0 | } |
1228 | | |
1229 | | private: |
1230 | | WeakPtr<nsTextEditorState> mState; |
1231 | | nsCOMPtr<nsIContent> mOwnerContent; // strong reference |
1232 | | nsAutoString mCurrentValue; |
1233 | | }; |
1234 | | |
1235 | | nsresult |
1236 | | nsTextEditorState::BindToFrame(nsTextControlFrame* aFrame) |
1237 | 0 | { |
1238 | 0 | NS_ASSERTION(aFrame, "The frame to bind to should be valid"); |
1239 | 0 | NS_ENSURE_ARG_POINTER(aFrame); |
1240 | 0 |
|
1241 | 0 | NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first"); |
1242 | 0 | NS_ENSURE_TRUE(!mBoundFrame, NS_ERROR_FAILURE); |
1243 | 0 |
|
1244 | 0 | // If we'll need to transfer our current value to the editor, save it before |
1245 | 0 | // binding to the frame. |
1246 | 0 | nsAutoString currentValue; |
1247 | 0 | if (mTextEditor) { |
1248 | 0 | GetValue(currentValue, true); |
1249 | 0 | } |
1250 | 0 |
|
1251 | 0 | mBoundFrame = aFrame; |
1252 | 0 |
|
1253 | 0 | Element* rootNode = aFrame->GetRootNode(); |
1254 | 0 | MOZ_ASSERT(rootNode); |
1255 | 0 |
|
1256 | 0 | nsIPresShell* shell = aFrame->PresContext()->GetPresShell(); |
1257 | 0 | MOZ_ASSERT(shell); |
1258 | 0 |
|
1259 | 0 | // Create selection |
1260 | 0 | RefPtr<nsFrameSelection> frameSel = new nsFrameSelection(); |
1261 | 0 |
|
1262 | 0 | // Create a SelectionController |
1263 | 0 | mSelCon = new nsTextInputSelectionImpl(frameSel, shell, rootNode); |
1264 | 0 | MOZ_ASSERT(!mTextListener, "Should not overwrite the object"); |
1265 | 0 | mTextListener = new TextInputListener(mTextCtrlElement); |
1266 | 0 |
|
1267 | 0 | mTextListener->SetFrame(mBoundFrame); |
1268 | 0 | mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); |
1269 | 0 |
|
1270 | 0 | // Get the caret and make it a selection listener. |
1271 | 0 | // FYI: It's safe to use raw pointer for calling |
1272 | 0 | // Selection::AddSelectionListner() because it only appends the listener |
1273 | 0 | // to its internal array. |
1274 | 0 | Selection* selection = mSelCon->GetSelection(SelectionType::eNormal); |
1275 | 0 | if (selection) { |
1276 | 0 | RefPtr<nsCaret> caret = shell->GetCaret(); |
1277 | 0 | if (caret) { |
1278 | 0 | selection->AddSelectionListener(caret); |
1279 | 0 | } |
1280 | 0 | mTextListener->StartToListenToSelectionChange(); |
1281 | 0 | } |
1282 | 0 |
|
1283 | 0 | // If an editor exists from before, prepare it for usage |
1284 | 0 | if (mTextEditor) { |
1285 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); |
1286 | 0 | NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); |
1287 | 0 |
|
1288 | 0 | // Set the correct direction on the newly created root node |
1289 | 0 | if (mTextEditor->IsRightToLeft()) { |
1290 | 0 | rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), false); |
1291 | 0 | } else if (mTextEditor->IsLeftToRight()) { |
1292 | 0 | rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), false); |
1293 | 0 | } else { |
1294 | 0 | // otherwise, inherit the content node's direction |
1295 | 0 | } |
1296 | 0 |
|
1297 | 0 | nsContentUtils::AddScriptRunner( |
1298 | 0 | new PrepareEditorEvent(*this, content, currentValue)); |
1299 | 0 | } |
1300 | 0 |
|
1301 | 0 | return NS_OK; |
1302 | 0 | } |
1303 | | |
1304 | | struct PreDestroyer |
1305 | | { |
1306 | 0 | void Init(TextEditor* aTextEditor) { mTextEditor = aTextEditor; } |
1307 | | ~PreDestroyer() |
1308 | 0 | { |
1309 | 0 | if (mTextEditor) { |
1310 | 0 | mTextEditor->PreDestroy(true); |
1311 | 0 | } |
1312 | 0 | } |
1313 | | void Swap(RefPtr<TextEditor>& aTextEditor) |
1314 | 0 | { |
1315 | 0 | return mTextEditor.swap(aTextEditor); |
1316 | 0 | } |
1317 | | private: |
1318 | | RefPtr<TextEditor> mTextEditor; |
1319 | | }; |
1320 | | |
1321 | | nsresult |
1322 | | nsTextEditorState::PrepareEditor(const nsAString *aValue) |
1323 | 0 | { |
1324 | 0 | if (!mBoundFrame) { |
1325 | 0 | // Cannot create an editor without a bound frame. |
1326 | 0 | // Don't return a failure code, because js callers can't handle that. |
1327 | 0 | return NS_OK; |
1328 | 0 | } |
1329 | 0 | |
1330 | 0 | if (mEditorInitialized) { |
1331 | 0 | // Do not initialize the editor multiple times. |
1332 | 0 | return NS_OK; |
1333 | 0 | } |
1334 | 0 | |
1335 | 0 | AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection()); |
1336 | 0 |
|
1337 | 0 | // Don't attempt to initialize recursively! |
1338 | 0 | InitializationGuard guard(*this); |
1339 | 0 | if (guard.IsInitializingRecursively()) { |
1340 | 0 | return NS_ERROR_NOT_INITIALIZED; |
1341 | 0 | } |
1342 | 0 | |
1343 | 0 | // Note that we don't check mTextEditor here, because we might already have |
1344 | 0 | // one around, in which case we don't create a new one, and we'll just tie |
1345 | 0 | // the required machinery to it. |
1346 | 0 | |
1347 | 0 | nsPresContext *presContext = mBoundFrame->PresContext(); |
1348 | 0 | nsIPresShell *shell = presContext->GetPresShell(); |
1349 | 0 |
|
1350 | 0 | // Setup the editor flags |
1351 | 0 | uint32_t editorFlags = nsIPlaintextEditor::eEditorPlaintextMask; |
1352 | 0 | if (IsSingleLineTextControl()) |
1353 | 0 | editorFlags |= nsIPlaintextEditor::eEditorSingleLineMask; |
1354 | 0 | if (IsPasswordTextControl()) |
1355 | 0 | editorFlags |= nsIPlaintextEditor::eEditorPasswordMask; |
1356 | 0 |
|
1357 | 0 | // All nsTextControlFrames are widgets |
1358 | 0 | editorFlags |= nsIPlaintextEditor::eEditorWidgetMask; |
1359 | 0 |
|
1360 | 0 | // Spell check is diabled at creation time. It is enabled once |
1361 | 0 | // the editor comes into focus. |
1362 | 0 | editorFlags |= nsIPlaintextEditor::eEditorSkipSpellCheck; |
1363 | 0 |
|
1364 | 0 | bool shouldInitializeEditor = false; |
1365 | 0 | RefPtr<TextEditor> newTextEditor; // the editor that we might create |
1366 | 0 | nsresult rv = NS_OK; |
1367 | 0 | PreDestroyer preDestroyer; |
1368 | 0 | if (!mTextEditor) { |
1369 | 0 | shouldInitializeEditor = true; |
1370 | 0 |
|
1371 | 0 | // Create an editor |
1372 | 0 | newTextEditor = new TextEditor(); |
1373 | 0 | preDestroyer.Init(newTextEditor); |
1374 | 0 |
|
1375 | 0 | // Make sure we clear out the non-breaking space before we initialize the editor |
1376 | 0 | rv = mBoundFrame->UpdateValueDisplay(true, true); |
1377 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1378 | 0 | } else { |
1379 | 0 | if (aValue || !mEditorInitialized) { |
1380 | 0 | // Set the correct value in the root node |
1381 | 0 | rv = mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue); |
1382 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1383 | 0 | } |
1384 | 0 |
|
1385 | 0 | newTextEditor = mTextEditor; // just pretend that we have a new editor! |
1386 | 0 |
|
1387 | 0 | // Don't lose application flags in the process. |
1388 | 0 | if (newTextEditor->IsMailEditor()) { |
1389 | 0 | editorFlags |= nsIPlaintextEditor::eEditorMailMask; |
1390 | 0 | } |
1391 | 0 | } |
1392 | 0 |
|
1393 | 0 | // Get the current value of the textfield from the content. |
1394 | 0 | // Note that if we've created a new editor, mTextEditor is null at this stage, |
1395 | 0 | // so we will get the real value from the content. |
1396 | 0 | nsAutoString defaultValue; |
1397 | 0 | if (aValue) { |
1398 | 0 | defaultValue = *aValue; |
1399 | 0 | } else { |
1400 | 0 | GetValue(defaultValue, true); |
1401 | 0 | } |
1402 | 0 |
|
1403 | 0 | if (!mEditorInitialized) { |
1404 | 0 | // Now initialize the editor. |
1405 | 0 | // |
1406 | 0 | // NOTE: Conversion of '\n' to <BR> happens inside the |
1407 | 0 | // editor's Init() call. |
1408 | 0 |
|
1409 | 0 | // Get the DOM document |
1410 | 0 | nsCOMPtr<nsIDocument> doc = shell->GetDocument(); |
1411 | 0 | if (NS_WARN_IF(!doc)) { |
1412 | 0 | return NS_ERROR_FAILURE; |
1413 | 0 | } |
1414 | 0 | |
1415 | 0 | // What follows is a bit of a hack. The editor uses the public DOM APIs |
1416 | 0 | // for its content manipulations, and it causes it to fail some security |
1417 | 0 | // checks deep inside when initializing. So we explictly make it clear that |
1418 | 0 | // we're native code. |
1419 | 0 | // Note that any script that's directly trying to access our value |
1420 | 0 | // has to be going through some scriptable object to do that and that |
1421 | 0 | // already does the relevant security checks. |
1422 | 0 | AutoNoJSAPI nojsapi; |
1423 | 0 |
|
1424 | 0 | rv = newTextEditor->Init(*doc, GetRootNode(), mSelCon, editorFlags, |
1425 | 0 | defaultValue); |
1426 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1427 | 0 | } |
1428 | 0 |
|
1429 | 0 | // Initialize the controller for the editor |
1430 | 0 |
|
1431 | 0 | if (!SuppressEventHandlers(presContext)) { |
1432 | 0 | nsCOMPtr<nsIControllers> controllers; |
1433 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); |
1434 | 0 | HTMLInputElement* inputElement = |
1435 | 0 | HTMLInputElement::FromNodeOrNull(content); |
1436 | 0 | if (inputElement) { |
1437 | 0 | rv = inputElement->GetControllers(getter_AddRefs(controllers)); |
1438 | 0 | } else { |
1439 | 0 | HTMLTextAreaElement* textAreaElement = |
1440 | 0 | HTMLTextAreaElement::FromNodeOrNull(content); |
1441 | 0 |
|
1442 | 0 | if (!textAreaElement) |
1443 | 0 | return NS_ERROR_FAILURE; |
1444 | 0 | |
1445 | 0 | rv = textAreaElement->GetControllers(getter_AddRefs(controllers)); |
1446 | 0 | } |
1447 | 0 |
|
1448 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1449 | 0 |
|
1450 | 0 | if (controllers) { |
1451 | 0 | uint32_t numControllers; |
1452 | 0 | bool found = false; |
1453 | 0 | rv = controllers->GetControllerCount(&numControllers); |
1454 | 0 | for (uint32_t i = 0; i < numControllers; i ++) { |
1455 | 0 | nsCOMPtr<nsIController> controller; |
1456 | 0 | rv = controllers->GetControllerAt(i, getter_AddRefs(controller)); |
1457 | 0 | if (NS_SUCCEEDED(rv) && controller) { |
1458 | 0 | nsCOMPtr<nsIControllerContext> editController = |
1459 | 0 | do_QueryInterface(controller); |
1460 | 0 | if (editController) { |
1461 | 0 | editController->SetCommandContext( |
1462 | 0 | static_cast<nsIEditor*>(newTextEditor)); |
1463 | 0 | found = true; |
1464 | 0 | } |
1465 | 0 | } |
1466 | 0 | } |
1467 | 0 | if (!found) |
1468 | 0 | rv = NS_ERROR_FAILURE; |
1469 | 0 | } |
1470 | 0 | } |
1471 | 0 |
|
1472 | 0 | // Initialize the plaintext editor |
1473 | 0 | if (shouldInitializeEditor) { |
1474 | 0 | // Set up wrapping |
1475 | 0 | newTextEditor->SetWrapColumn(GetWrapCols()); |
1476 | 0 | } |
1477 | 0 |
|
1478 | 0 | // Set max text field length |
1479 | 0 | newTextEditor->SetMaxTextLength(GetMaxLength()); |
1480 | 0 |
|
1481 | 0 | if (nsCOMPtr<Element> element = do_QueryInterface(mTextCtrlElement)) { |
1482 | 0 | editorFlags = newTextEditor->Flags(); |
1483 | 0 |
|
1484 | 0 | // Check if the readonly attribute is set. |
1485 | 0 | if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) |
1486 | 0 | editorFlags |= nsIPlaintextEditor::eEditorReadonlyMask; |
1487 | 0 |
|
1488 | 0 | // Check if the disabled attribute is set. |
1489 | 0 | // TODO: call IsDisabled() here! |
1490 | 0 | if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) |
1491 | 0 | editorFlags |= nsIPlaintextEditor::eEditorDisabledMask; |
1492 | 0 |
|
1493 | 0 | // Disable the selection if necessary. |
1494 | 0 | if (newTextEditor->IsDisabled()) { |
1495 | 0 | mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_OFF); |
1496 | 0 | } |
1497 | 0 |
|
1498 | 0 | SetEditorFlagsIfNecessary(*newTextEditor, editorFlags); |
1499 | 0 | } |
1500 | 0 |
|
1501 | 0 | if (shouldInitializeEditor) { |
1502 | 0 | // Hold on to the newly created editor |
1503 | 0 | preDestroyer.Swap(mTextEditor); |
1504 | 0 | } |
1505 | 0 |
|
1506 | 0 | // If we have a default value, insert it under the div we created |
1507 | 0 | // above, but be sure to use the editor so that '*' characters get |
1508 | 0 | // displayed for password fields, etc. SetValue() will call the |
1509 | 0 | // editor for us. |
1510 | 0 |
|
1511 | 0 | if (!defaultValue.IsEmpty()) { |
1512 | 0 | rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags); |
1513 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1514 | 0 | return rv; |
1515 | 0 | } |
1516 | 0 | |
1517 | 0 | // Now call SetValue() which will make the necessary editor calls to set |
1518 | 0 | // the default value. Make sure to turn off undo before setting the default |
1519 | 0 | // value, and turn it back on afterwards. This will make sure we can't undo |
1520 | 0 | // past the default value. |
1521 | 0 | // So, we use eSetValue_Internal flag only that it will turn off undo. |
1522 | 0 | |
1523 | 0 | bool success = SetValue(defaultValue, eSetValue_Internal); |
1524 | 0 | NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); |
1525 | 0 |
|
1526 | 0 | // Now restore the original editor flags. |
1527 | 0 | rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags); |
1528 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1529 | 0 | return rv; |
1530 | 0 | } |
1531 | 0 | } |
1532 | 0 | |
1533 | 0 | if (IsPasswordTextControl()) { |
1534 | 0 | // Disable undo for <input type="password">. Note that we want to do this |
1535 | 0 | // at the very end of InitEditor(), so the calls to EnableUndoRedo() when |
1536 | 0 | // setting the default value don't screw us up. Since changing the |
1537 | 0 | // control type does a reframe, we don't have to worry about dynamic type |
1538 | 0 | // changes here. |
1539 | 0 | DebugOnly<bool> disabledUndoRedo = newTextEditor->DisableUndoRedo(); |
1540 | 0 | NS_WARNING_ASSERTION(disabledUndoRedo, |
1541 | 0 | "Failed to disable undo/redo transaction"); |
1542 | 0 | } else { |
1543 | 0 | DebugOnly<bool> enabledUndoRedo = |
1544 | 0 | newTextEditor->EnableUndoRedo(nsITextControlElement::DEFAULT_UNDO_CAP); |
1545 | 0 | NS_WARNING_ASSERTION(enabledUndoRedo, |
1546 | 0 | "Failed to enable undo/redo transaction"); |
1547 | 0 | } |
1548 | 0 |
|
1549 | 0 | if (!mEditorInitialized) { |
1550 | 0 | newTextEditor->PostCreate(); |
1551 | 0 | mEverInited = true; |
1552 | 0 | mEditorInitialized = true; |
1553 | 0 | } |
1554 | 0 |
|
1555 | 0 | if (mTextListener) { |
1556 | 0 | newTextEditor->SetTextInputListener(mTextListener); |
1557 | 0 | } |
1558 | 0 |
|
1559 | 0 | // Restore our selection after being bound to a new frame |
1560 | 0 | HTMLInputElement* number = GetParentNumberControl(mBoundFrame); |
1561 | 0 | if (number ? number->IsSelectionCached() : mSelectionCached) { |
1562 | 0 | if (mRestoringSelection) // paranoia |
1563 | 0 | mRestoringSelection->Revoke(); |
1564 | 0 | mRestoringSelection = new RestoreSelectionState(this, mBoundFrame); |
1565 | 0 | if (mRestoringSelection) { |
1566 | 0 | nsContentUtils::AddScriptRunner(mRestoringSelection); |
1567 | 0 | } |
1568 | 0 | } |
1569 | 0 |
|
1570 | 0 | // The selection cache is no longer going to be valid. |
1571 | 0 | // |
1572 | 0 | // XXXbz Shouldn't we do this at the point when we're actually about to |
1573 | 0 | // restore the properties or something? As things stand, if UnbindFromFrame |
1574 | 0 | // happens before our RestoreSelectionState runs, it looks like we'll lose our |
1575 | 0 | // selection info, because we will think we don't have it cached and try to |
1576 | 0 | // read it from the selection controller, which will not have it yet. |
1577 | 0 | if (number) { |
1578 | 0 | number->ClearSelectionCached(); |
1579 | 0 | } else { |
1580 | 0 | mSelectionCached = false; |
1581 | 0 | } |
1582 | 0 |
|
1583 | 0 | return rv; |
1584 | 0 | } |
1585 | | |
1586 | | void |
1587 | | nsTextEditorState::FinishedRestoringSelection() |
1588 | 0 | { |
1589 | 0 | mRestoringSelection = nullptr; |
1590 | 0 | } |
1591 | | |
1592 | | bool |
1593 | | nsTextEditorState::IsSelectionCached() const |
1594 | 0 | { |
1595 | 0 | if (mBoundFrame) { |
1596 | 0 | HTMLInputElement* number = GetParentNumberControl(mBoundFrame); |
1597 | 0 | if (number) { |
1598 | 0 | return number->IsSelectionCached(); |
1599 | 0 | } |
1600 | 0 | } |
1601 | 0 | return mSelectionCached; |
1602 | 0 | } |
1603 | | |
1604 | | nsTextEditorState::SelectionProperties& |
1605 | | nsTextEditorState::GetSelectionProperties() |
1606 | 0 | { |
1607 | 0 | if (mBoundFrame) { |
1608 | 0 | HTMLInputElement* number = GetParentNumberControl(mBoundFrame); |
1609 | 0 | if (number) { |
1610 | 0 | return number->GetSelectionProperties(); |
1611 | 0 | } |
1612 | 0 | } |
1613 | 0 | return mSelectionProperties; |
1614 | 0 | } |
1615 | | |
1616 | | void |
1617 | | nsTextEditorState::SyncUpSelectionPropertiesBeforeDestruction() |
1618 | 0 | { |
1619 | 0 | if (mBoundFrame) { |
1620 | 0 | UnbindFromFrame(mBoundFrame); |
1621 | 0 | } |
1622 | 0 | } |
1623 | | |
1624 | | void |
1625 | | nsTextEditorState::SetSelectionProperties(nsTextEditorState::SelectionProperties& aProps) |
1626 | 0 | { |
1627 | 0 | if (mBoundFrame) { |
1628 | 0 | mBoundFrame->SetSelectionRange(aProps.GetStart(), |
1629 | 0 | aProps.GetEnd(), |
1630 | 0 | aProps.GetDirection()); |
1631 | 0 | } else { |
1632 | 0 | mSelectionProperties = aProps; |
1633 | 0 | } |
1634 | 0 | } |
1635 | | |
1636 | | void |
1637 | | nsTextEditorState::GetSelectionRange(uint32_t* aSelectionStart, |
1638 | | uint32_t* aSelectionEnd, |
1639 | | ErrorResult& aRv) |
1640 | 0 | { |
1641 | 0 | MOZ_ASSERT(aSelectionStart); |
1642 | 0 | MOZ_ASSERT(aSelectionEnd); |
1643 | 0 | MOZ_ASSERT(IsSelectionCached() || GetSelectionController(), |
1644 | 0 | "How can we not have a cached selection if we have no selection " |
1645 | 0 | "controller?"); |
1646 | 0 |
|
1647 | 0 | // Note that we may have both IsSelectionCached() _and_ |
1648 | 0 | // GetSelectionController() if we haven't initialized our editor yet. |
1649 | 0 | if (IsSelectionCached()) { |
1650 | 0 | const SelectionProperties& props = GetSelectionProperties(); |
1651 | 0 | *aSelectionStart = props.GetStart(); |
1652 | 0 | *aSelectionEnd = props.GetEnd(); |
1653 | 0 | return; |
1654 | 0 | } |
1655 | 0 | |
1656 | 0 | Selection* sel = mSelCon->GetSelection(SelectionType::eNormal); |
1657 | 0 | if (NS_WARN_IF(!sel)) { |
1658 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
1659 | 0 | return; |
1660 | 0 | } |
1661 | 0 | |
1662 | 0 | mozilla::dom::Element* root = GetRootNode(); |
1663 | 0 | if (NS_WARN_IF(!root)) { |
1664 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
1665 | 0 | return; |
1666 | 0 | } |
1667 | 0 | nsContentUtils::GetSelectionInTextControl(sel, root, |
1668 | 0 | *aSelectionStart, *aSelectionEnd); |
1669 | 0 | } |
1670 | | |
1671 | | nsITextControlFrame::SelectionDirection |
1672 | | nsTextEditorState::GetSelectionDirection(ErrorResult& aRv) |
1673 | 0 | { |
1674 | 0 | MOZ_ASSERT(IsSelectionCached() || GetSelectionController(), |
1675 | 0 | "How can we not have a cached selection if we have no selection " |
1676 | 0 | "controller?"); |
1677 | 0 |
|
1678 | 0 | // Note that we may have both IsSelectionCached() _and_ |
1679 | 0 | // GetSelectionController() if we haven't initialized our editor yet. |
1680 | 0 | if (IsSelectionCached()) { |
1681 | 0 | return GetSelectionProperties().GetDirection(); |
1682 | 0 | } |
1683 | 0 | |
1684 | 0 | Selection* sel = mSelCon->GetSelection(SelectionType::eNormal); |
1685 | 0 | if (NS_WARN_IF(!sel)) { |
1686 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
1687 | 0 | return nsITextControlFrame::eForward; // Doesn't really matter |
1688 | 0 | } |
1689 | 0 | |
1690 | 0 | nsDirection direction = sel->GetDirection(); |
1691 | 0 | if (direction == eDirNext) { |
1692 | 0 | return nsITextControlFrame::eForward; |
1693 | 0 | } |
1694 | 0 | |
1695 | 0 | MOZ_ASSERT(direction == eDirPrevious); |
1696 | 0 | return nsITextControlFrame::eBackward; |
1697 | 0 | } |
1698 | | |
1699 | | void |
1700 | | nsTextEditorState::SetSelectionRange(uint32_t aStart, uint32_t aEnd, |
1701 | | nsITextControlFrame::SelectionDirection aDirection, |
1702 | | ErrorResult& aRv) |
1703 | 0 | { |
1704 | 0 | MOZ_ASSERT(IsSelectionCached() || mBoundFrame, |
1705 | 0 | "How can we have a non-cached selection but no frame?"); |
1706 | 0 |
|
1707 | 0 | if (aStart > aEnd) { |
1708 | 0 | aStart = aEnd; |
1709 | 0 | } |
1710 | 0 |
|
1711 | 0 | bool changed = false; |
1712 | 0 | nsresult rv = NS_OK; // For the ScrollSelectionIntoView() return value. |
1713 | 0 | if (IsSelectionCached()) { |
1714 | 0 | nsAutoString value; |
1715 | 0 | // XXXbz is "false" the right thing to pass here? Hard to tell, given the |
1716 | 0 | // various mismatches between our impl and the spec. |
1717 | 0 | GetValue(value, false); |
1718 | 0 | uint32_t length = value.Length(); |
1719 | 0 | if (aStart > length) { |
1720 | 0 | aStart = length; |
1721 | 0 | } |
1722 | 0 | if (aEnd > length) { |
1723 | 0 | aEnd = length; |
1724 | 0 | } |
1725 | 0 | SelectionProperties& props = GetSelectionProperties(); |
1726 | 0 | changed = props.GetStart() != aStart || |
1727 | 0 | props.GetEnd() != aEnd || |
1728 | 0 | props.GetDirection() != aDirection; |
1729 | 0 | props.SetStart(aStart); |
1730 | 0 | props.SetEnd(aEnd); |
1731 | 0 | props.SetDirection(aDirection); |
1732 | 0 | } else { |
1733 | 0 | WeakPtr<nsTextEditorState> self(this); |
1734 | 0 | aRv = mBoundFrame->SetSelectionRange(aStart, aEnd, aDirection); |
1735 | 0 | if (aRv.Failed() || !self.get()) { |
1736 | 0 | return; |
1737 | 0 | } |
1738 | 0 | rv = mBoundFrame->ScrollSelectionIntoView(); |
1739 | 0 | // Press on to firing the event even if that failed, like our old code did. |
1740 | 0 | // But is that really what we want? Firing the event _and_ throwing from |
1741 | 0 | // here is weird. Maybe we should just ignore ScrollSelectionIntoView |
1742 | 0 | // failures? |
1743 | 0 |
|
1744 | 0 | // XXXbz This is preserving our current behavior of firing a "select" event |
1745 | 0 | // on all mutations when we have an editor, but we should really consider |
1746 | 0 | // fixing that... |
1747 | 0 | changed = true; |
1748 | 0 | } |
1749 | 0 |
|
1750 | 0 | if (changed) { |
1751 | 0 | // It sure would be nice if we had an existing Element* or so to work with. |
1752 | 0 | nsCOMPtr<nsINode> node = do_QueryInterface(mTextCtrlElement); |
1753 | 0 | RefPtr<AsyncEventDispatcher> asyncDispatcher = |
1754 | 0 | new AsyncEventDispatcher(node, |
1755 | 0 | NS_LITERAL_STRING("select"), |
1756 | 0 | CanBubble::eYes, |
1757 | 0 | ChromeOnlyDispatch::eNo); |
1758 | 0 | asyncDispatcher->PostDOMEvent(); |
1759 | 0 | } |
1760 | 0 |
|
1761 | 0 | if (NS_FAILED(rv)) { |
1762 | 0 | aRv.Throw(rv); |
1763 | 0 | } |
1764 | 0 | } |
1765 | | |
1766 | | void |
1767 | | nsTextEditorState::SetSelectionStart(const Nullable<uint32_t>& aStart, |
1768 | | ErrorResult& aRv) |
1769 | 0 | { |
1770 | 0 | uint32_t start = 0; |
1771 | 0 | if (!aStart.IsNull()) { |
1772 | 0 | start = aStart.Value(); |
1773 | 0 | } |
1774 | 0 |
|
1775 | 0 | uint32_t ignored, end; |
1776 | 0 | GetSelectionRange(&ignored, &end, aRv); |
1777 | 0 | if (aRv.Failed()) { |
1778 | 0 | return; |
1779 | 0 | } |
1780 | 0 | |
1781 | 0 | nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv); |
1782 | 0 | if (aRv.Failed()) { |
1783 | 0 | return; |
1784 | 0 | } |
1785 | 0 | |
1786 | 0 | if (end < start) { |
1787 | 0 | end = start; |
1788 | 0 | } |
1789 | 0 |
|
1790 | 0 | SetSelectionRange(start, end, dir, aRv); |
1791 | 0 | } |
1792 | | |
1793 | | void |
1794 | | nsTextEditorState::SetSelectionEnd(const Nullable<uint32_t>& aEnd, |
1795 | | ErrorResult& aRv) |
1796 | 0 | { |
1797 | 0 | uint32_t end = 0; |
1798 | 0 | if (!aEnd.IsNull()) { |
1799 | 0 | end = aEnd.Value(); |
1800 | 0 | } |
1801 | 0 |
|
1802 | 0 | uint32_t start, ignored; |
1803 | 0 | GetSelectionRange(&start, &ignored, aRv); |
1804 | 0 | if (aRv.Failed()) { |
1805 | 0 | return; |
1806 | 0 | } |
1807 | 0 | |
1808 | 0 | nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv); |
1809 | 0 | if (aRv.Failed()) { |
1810 | 0 | return; |
1811 | 0 | } |
1812 | 0 | |
1813 | 0 | SetSelectionRange(start, end, dir, aRv); |
1814 | 0 | } |
1815 | | |
1816 | | static void |
1817 | | DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection) |
1818 | 0 | { |
1819 | 0 | if (dir == nsITextControlFrame::eNone) { |
1820 | 0 | NS_WARNING("We don't actually support this... how did we get it?"); |
1821 | 0 | aDirection.AssignLiteral("none"); |
1822 | 0 | } else if (dir == nsITextControlFrame::eForward) { |
1823 | 0 | aDirection.AssignLiteral("forward"); |
1824 | 0 | } else if (dir == nsITextControlFrame::eBackward) { |
1825 | 0 | aDirection.AssignLiteral("backward"); |
1826 | 0 | } else { |
1827 | 0 | MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value"); |
1828 | 0 | } |
1829 | 0 | } |
1830 | | |
1831 | | void |
1832 | | nsTextEditorState::GetSelectionDirectionString(nsAString& aDirection, |
1833 | | ErrorResult& aRv) |
1834 | 0 | { |
1835 | 0 | nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv); |
1836 | 0 | if (aRv.Failed()) { |
1837 | 0 | return; |
1838 | 0 | } |
1839 | 0 | DirectionToName(dir, aDirection); |
1840 | 0 | } |
1841 | | |
1842 | | static nsITextControlFrame::SelectionDirection |
1843 | | DirectionStringToSelectionDirection(const nsAString& aDirection) |
1844 | 0 | { |
1845 | 0 | if (aDirection.EqualsLiteral("backward")) { |
1846 | 0 | return nsITextControlFrame::eBackward; |
1847 | 0 | } |
1848 | 0 | |
1849 | 0 | // We don't support directionless selections. |
1850 | 0 | return nsITextControlFrame::eForward; |
1851 | 0 | } |
1852 | | |
1853 | | void |
1854 | | nsTextEditorState::SetSelectionDirection(const nsAString& aDirection, |
1855 | | ErrorResult& aRv) |
1856 | 0 | { |
1857 | 0 | nsITextControlFrame::SelectionDirection dir = |
1858 | 0 | DirectionStringToSelectionDirection(aDirection); |
1859 | 0 |
|
1860 | 0 | if (IsSelectionCached()) { |
1861 | 0 | GetSelectionProperties().SetDirection(dir); |
1862 | 0 | return; |
1863 | 0 | } |
1864 | 0 | |
1865 | 0 | uint32_t start, end; |
1866 | 0 | GetSelectionRange(&start, &end, aRv); |
1867 | 0 | if (aRv.Failed()) { |
1868 | 0 | return; |
1869 | 0 | } |
1870 | 0 | |
1871 | 0 | SetSelectionRange(start, end, dir, aRv); |
1872 | 0 | } |
1873 | | |
1874 | | static nsITextControlFrame::SelectionDirection |
1875 | | DirectionStringToSelectionDirection(const Optional<nsAString>& aDirection) |
1876 | 0 | { |
1877 | 0 | if (!aDirection.WasPassed()) { |
1878 | 0 | // We don't support directionless selections. |
1879 | 0 | return nsITextControlFrame::eForward; |
1880 | 0 | } |
1881 | 0 | |
1882 | 0 | return DirectionStringToSelectionDirection(aDirection.Value()); |
1883 | 0 | } |
1884 | | |
1885 | | void |
1886 | | nsTextEditorState::SetSelectionRange(uint32_t aSelectionStart, |
1887 | | uint32_t aSelectionEnd, |
1888 | | const Optional<nsAString>& aDirection, |
1889 | | ErrorResult& aRv) |
1890 | 0 | { |
1891 | 0 | nsITextControlFrame::SelectionDirection dir = |
1892 | 0 | DirectionStringToSelectionDirection(aDirection); |
1893 | 0 |
|
1894 | 0 | SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv); |
1895 | 0 | } |
1896 | | |
1897 | | void |
1898 | | nsTextEditorState::SetRangeText(const nsAString& aReplacement, |
1899 | | ErrorResult& aRv) |
1900 | 0 | { |
1901 | 0 | uint32_t start, end; |
1902 | 0 | GetSelectionRange(&start, &end, aRv); |
1903 | 0 | if (aRv.Failed()) { |
1904 | 0 | return; |
1905 | 0 | } |
1906 | 0 | |
1907 | 0 | SetRangeText(aReplacement, start, end, SelectionMode::Preserve, |
1908 | 0 | aRv, Some(start), Some(end)); |
1909 | 0 | } |
1910 | | |
1911 | | void |
1912 | | nsTextEditorState::SetRangeText(const nsAString& aReplacement, uint32_t aStart, |
1913 | | uint32_t aEnd, SelectionMode aSelectMode, |
1914 | | ErrorResult& aRv, |
1915 | | const Maybe<uint32_t>& aSelectionStart, |
1916 | | const Maybe<uint32_t>& aSelectionEnd) |
1917 | 0 | { |
1918 | 0 | if (aStart > aEnd) { |
1919 | 0 | aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
1920 | 0 | return; |
1921 | 0 | } |
1922 | 0 | |
1923 | 0 | nsAutoString value; |
1924 | 0 | mTextCtrlElement->GetValueFromSetRangeText(value); |
1925 | 0 | uint32_t inputValueLength = value.Length(); |
1926 | 0 |
|
1927 | 0 | if (aStart > inputValueLength) { |
1928 | 0 | aStart = inputValueLength; |
1929 | 0 | } |
1930 | 0 |
|
1931 | 0 | if (aEnd > inputValueLength) { |
1932 | 0 | aEnd = inputValueLength; |
1933 | 0 | } |
1934 | 0 |
|
1935 | 0 | uint32_t selectionStart, selectionEnd; |
1936 | 0 | if (!aSelectionStart) { |
1937 | 0 | MOZ_ASSERT(!aSelectionEnd); |
1938 | 0 | GetSelectionRange(&selectionStart, &selectionEnd, aRv); |
1939 | 0 | if (aRv.Failed()) { |
1940 | 0 | return; |
1941 | 0 | } |
1942 | 0 | } else { |
1943 | 0 | MOZ_ASSERT(aSelectionEnd); |
1944 | 0 | selectionStart = *aSelectionStart; |
1945 | 0 | selectionEnd = *aSelectionEnd; |
1946 | 0 | } |
1947 | 0 |
|
1948 | 0 | MOZ_ASSERT(aStart <= aEnd); |
1949 | 0 | value.Replace(aStart, aEnd - aStart, aReplacement); |
1950 | 0 | nsresult rv = mTextCtrlElement->SetValueFromSetRangeText(value); |
1951 | 0 | if (NS_FAILED(rv)) { |
1952 | 0 | aRv.Throw(rv); |
1953 | 0 | return; |
1954 | 0 | } |
1955 | 0 | |
1956 | 0 | uint32_t newEnd = aStart + aReplacement.Length(); |
1957 | 0 | int32_t delta = aReplacement.Length() - (aEnd - aStart); |
1958 | 0 |
|
1959 | 0 | switch (aSelectMode) { |
1960 | 0 | case mozilla::dom::SelectionMode::Select: |
1961 | 0 | { |
1962 | 0 | selectionStart = aStart; |
1963 | 0 | selectionEnd = newEnd; |
1964 | 0 | } |
1965 | 0 | break; |
1966 | 0 | case mozilla::dom::SelectionMode::Start: |
1967 | 0 | { |
1968 | 0 | selectionStart = selectionEnd = aStart; |
1969 | 0 | } |
1970 | 0 | break; |
1971 | 0 | case mozilla::dom::SelectionMode::End: |
1972 | 0 | { |
1973 | 0 | selectionStart = selectionEnd = newEnd; |
1974 | 0 | } |
1975 | 0 | break; |
1976 | 0 | case mozilla::dom::SelectionMode::Preserve: |
1977 | 0 | { |
1978 | 0 | if (selectionStart > aEnd) { |
1979 | 0 | selectionStart += delta; |
1980 | 0 | } else if (selectionStart > aStart) { |
1981 | 0 | selectionStart = aStart; |
1982 | 0 | } |
1983 | 0 |
|
1984 | 0 | if (selectionEnd > aEnd) { |
1985 | 0 | selectionEnd += delta; |
1986 | 0 | } else if (selectionEnd > aStart) { |
1987 | 0 | selectionEnd = newEnd; |
1988 | 0 | } |
1989 | 0 | } |
1990 | 0 | break; |
1991 | 0 | default: |
1992 | 0 | MOZ_CRASH("Unknown mode!"); |
1993 | 0 | } |
1994 | 0 |
|
1995 | 0 | SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv); |
1996 | 0 | } |
1997 | | |
1998 | | HTMLInputElement* |
1999 | | nsTextEditorState::GetParentNumberControl(nsFrame* aFrame) const |
2000 | 0 | { |
2001 | 0 | MOZ_ASSERT(aFrame); |
2002 | 0 | nsIContent* content = aFrame->GetContent(); |
2003 | 0 | MOZ_ASSERT(content); |
2004 | 0 | nsIContent* parent = content->GetParent(); |
2005 | 0 | if (!parent) { |
2006 | 0 | return nullptr; |
2007 | 0 | } |
2008 | 0 | nsIContent* parentOfParent = parent->GetParent(); |
2009 | 0 | if (!parentOfParent) { |
2010 | 0 | return nullptr; |
2011 | 0 | } |
2012 | 0 | HTMLInputElement* input = HTMLInputElement::FromNode(parentOfParent); |
2013 | 0 | if (input) { |
2014 | 0 | // This function might be called during frame reconstruction as a result |
2015 | 0 | // of changing the input control's type from number to something else. In |
2016 | 0 | // that situation, the type of the control has changed, but its frame has |
2017 | 0 | // not been reconstructed yet. So we need to check the type of the input |
2018 | 0 | // control in addition to the type of the frame. |
2019 | 0 | return (input->ControlType() == NS_FORM_INPUT_NUMBER) ? input : nullptr; |
2020 | 0 | } |
2021 | 0 |
|
2022 | 0 | return nullptr; |
2023 | 0 | } |
2024 | | |
2025 | | void |
2026 | | nsTextEditorState::DestroyEditor() |
2027 | 0 | { |
2028 | 0 | // notify the editor that we are going away |
2029 | 0 | if (mEditorInitialized) { |
2030 | 0 | mTextEditor->PreDestroy(true); |
2031 | 0 | mEditorInitialized = false; |
2032 | 0 | } |
2033 | 0 | } |
2034 | | |
2035 | | void |
2036 | | nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame) |
2037 | 0 | { |
2038 | 0 | NS_ENSURE_TRUE_VOID(mBoundFrame); |
2039 | 0 |
|
2040 | 0 | // If it was, however, it should be unbounded from the same frame. |
2041 | 0 | MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame"); |
2042 | 0 | NS_ENSURE_TRUE_VOID(!aFrame || aFrame == mBoundFrame); |
2043 | 0 |
|
2044 | 0 | // If the editor is modified but nsIEditorObserver::EditAction() hasn't been |
2045 | 0 | // called yet, we need to notify it here because editor may be destroyed |
2046 | 0 | // before EditAction() is called if selection listener causes flushing layout. |
2047 | 0 | if (mTextListener && mTextEditor && mEditorInitialized && |
2048 | 0 | mTextEditor->IsInEditSubAction()) { |
2049 | 0 | mTextListener->OnEditActionHandled(); |
2050 | 0 | } |
2051 | 0 |
|
2052 | 0 | // We need to start storing the value outside of the editor if we're not |
2053 | 0 | // going to use it anymore, so retrieve it for now. |
2054 | 0 | nsAutoString value; |
2055 | 0 | GetValue(value, true); |
2056 | 0 |
|
2057 | 0 | if (mRestoringSelection) { |
2058 | 0 | mRestoringSelection->Revoke(); |
2059 | 0 | mRestoringSelection = nullptr; |
2060 | 0 | } |
2061 | 0 |
|
2062 | 0 | // Save our selection state if needed. |
2063 | 0 | // Note that GetSelectionRange will attempt to work with our selection |
2064 | 0 | // controller, so we should make sure we do it before we start doing things |
2065 | 0 | // like destroying our editor (if we have one), tearing down the selection |
2066 | 0 | // controller, and so forth. |
2067 | 0 | if (!IsSelectionCached()) { |
2068 | 0 | // Go ahead and cache it now. |
2069 | 0 | uint32_t start = 0, end = 0; |
2070 | 0 | GetSelectionRange(&start, &end, IgnoreErrors()); |
2071 | 0 |
|
2072 | 0 | nsITextControlFrame::SelectionDirection direction = |
2073 | 0 | GetSelectionDirection(IgnoreErrors()); |
2074 | 0 |
|
2075 | 0 | SelectionProperties& props = GetSelectionProperties(); |
2076 | 0 | props.SetStart(start); |
2077 | 0 | props.SetEnd(end); |
2078 | 0 | props.SetDirection(direction); |
2079 | 0 | HTMLInputElement* number = GetParentNumberControl(aFrame); |
2080 | 0 | if (number) { |
2081 | 0 | // If we are inside a number control, cache the selection on the |
2082 | 0 | // parent control, because this text editor state will be destroyed |
2083 | 0 | // together with the native anonymous text control. |
2084 | 0 | number->SetSelectionCached(); |
2085 | 0 | } else { |
2086 | 0 | mSelectionCached = true; |
2087 | 0 | } |
2088 | 0 | } |
2089 | 0 |
|
2090 | 0 | // Destroy our editor |
2091 | 0 | DestroyEditor(); |
2092 | 0 |
|
2093 | 0 | // Clean up the controller |
2094 | 0 | if (!SuppressEventHandlers(mBoundFrame->PresContext())) |
2095 | 0 | { |
2096 | 0 | nsCOMPtr<nsIControllers> controllers; |
2097 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); |
2098 | 0 | HTMLInputElement* inputElement = |
2099 | 0 | HTMLInputElement::FromNodeOrNull(content); |
2100 | 0 | if (inputElement) |
2101 | 0 | inputElement->GetControllers(getter_AddRefs(controllers)); |
2102 | 0 | else |
2103 | 0 | { |
2104 | 0 | HTMLTextAreaElement* textAreaElement = |
2105 | 0 | HTMLTextAreaElement::FromNodeOrNull(content); |
2106 | 0 | if (textAreaElement) { |
2107 | 0 | textAreaElement->GetControllers(getter_AddRefs(controllers)); |
2108 | 0 | } |
2109 | 0 | } |
2110 | 0 |
|
2111 | 0 | if (controllers) |
2112 | 0 | { |
2113 | 0 | uint32_t numControllers; |
2114 | 0 | nsresult rv = controllers->GetControllerCount(&numControllers); |
2115 | 0 | NS_ASSERTION((NS_SUCCEEDED(rv)), "bad result in gfx text control destructor"); |
2116 | 0 | for (uint32_t i = 0; i < numControllers; i ++) |
2117 | 0 | { |
2118 | 0 | nsCOMPtr<nsIController> controller; |
2119 | 0 | rv = controllers->GetControllerAt(i, getter_AddRefs(controller)); |
2120 | 0 | if (NS_SUCCEEDED(rv) && controller) |
2121 | 0 | { |
2122 | 0 | nsCOMPtr<nsIControllerContext> editController = do_QueryInterface(controller); |
2123 | 0 | if (editController) |
2124 | 0 | { |
2125 | 0 | editController->SetCommandContext(nullptr); |
2126 | 0 | } |
2127 | 0 | } |
2128 | 0 | } |
2129 | 0 | } |
2130 | 0 | } |
2131 | 0 |
|
2132 | 0 | if (mSelCon) { |
2133 | 0 | if (mTextListener) { |
2134 | 0 | mTextListener->EndListeningToSelectionChange(); |
2135 | 0 | } |
2136 | 0 |
|
2137 | 0 | mSelCon->SetScrollableFrame(nullptr); |
2138 | 0 | mSelCon = nullptr; |
2139 | 0 | } |
2140 | 0 |
|
2141 | 0 | if (mTextListener) |
2142 | 0 | { |
2143 | 0 | mTextListener->SetFrame(nullptr); |
2144 | 0 |
|
2145 | 0 | nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement); |
2146 | 0 | EventListenerManager* manager = target->GetExistingListenerManager(); |
2147 | 0 | if (manager) { |
2148 | 0 | manager->RemoveEventListenerByType(mTextListener, |
2149 | 0 | NS_LITERAL_STRING("keydown"), |
2150 | 0 | TrustedEventsAtSystemGroupBubble()); |
2151 | 0 | manager->RemoveEventListenerByType(mTextListener, |
2152 | 0 | NS_LITERAL_STRING("keypress"), |
2153 | 0 | TrustedEventsAtSystemGroupBubble()); |
2154 | 0 | manager->RemoveEventListenerByType(mTextListener, |
2155 | 0 | NS_LITERAL_STRING("keyup"), |
2156 | 0 | TrustedEventsAtSystemGroupBubble()); |
2157 | 0 | } |
2158 | 0 |
|
2159 | 0 | mTextListener = nullptr; |
2160 | 0 | } |
2161 | 0 |
|
2162 | 0 | mBoundFrame = nullptr; |
2163 | 0 |
|
2164 | 0 | // Now that we don't have a frame any more, store the value in the text buffer. |
2165 | 0 | // The only case where we don't do this is if a value transfer is in progress. |
2166 | 0 | if (!mValueTransferInProgress) { |
2167 | 0 | bool success = SetValue(value, eSetValue_Internal); |
2168 | 0 | // TODO Find something better to do if this fails... |
2169 | 0 | NS_ENSURE_TRUE_VOID(success); |
2170 | 0 | } |
2171 | 0 | } |
2172 | | |
2173 | | int32_t |
2174 | | nsTextEditorState::GetMaxLength() |
2175 | 0 | { |
2176 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); |
2177 | 0 | nsGenericHTMLElement* element = |
2178 | 0 | nsGenericHTMLElement::FromNodeOrNull(content); |
2179 | 0 | if (NS_WARN_IF(!element)) { |
2180 | 0 | return -1; |
2181 | 0 | } |
2182 | 0 | |
2183 | 0 | const nsAttrValue* attr = element->GetParsedAttr(nsGkAtoms::maxlength); |
2184 | 0 | if (attr && attr->Type() == nsAttrValue::eInteger) { |
2185 | 0 | return attr->GetIntegerValue(); |
2186 | 0 | } |
2187 | 0 | |
2188 | 0 | return -1; |
2189 | 0 | } |
2190 | | |
2191 | | void |
2192 | | nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const |
2193 | 0 | { |
2194 | 0 | // While SetValue() is being called and requesting to commit composition to |
2195 | 0 | // IME, GetValue() may be called for appending text or something. Then, we |
2196 | 0 | // need to return the latest aValue of SetValue() since the value hasn't |
2197 | 0 | // been set to the editor yet. |
2198 | 0 | if (mIsCommittingComposition) { |
2199 | 0 | aValue = mValueBeingSet; |
2200 | 0 | return; |
2201 | 0 | } |
2202 | 0 | |
2203 | 0 | if (mTextEditor && mBoundFrame && |
2204 | 0 | (mEditorInitialized || !IsSingleLineTextControl())) { |
2205 | 0 | if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) { |
2206 | 0 | aValue = mBoundFrame->CachedValue(); |
2207 | 0 | return; |
2208 | 0 | } |
2209 | 0 | |
2210 | 0 | aValue.Truncate(); // initialize out param |
2211 | 0 |
|
2212 | 0 | uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak | |
2213 | 0 | nsIDocumentEncoder::OutputPreformatted | |
2214 | 0 | nsIDocumentEncoder::OutputPersistNBSP | |
2215 | 0 | nsIDocumentEncoder::OutputBodyOnly); |
2216 | 0 | if (!aIgnoreWrap) { |
2217 | 0 | nsITextControlElement::nsHTMLTextWrap wrapProp; |
2218 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); |
2219 | 0 | if (content && |
2220 | 0 | nsITextControlElement::GetWrapPropertyEnum(content, wrapProp) && |
2221 | 0 | wrapProp == nsITextControlElement::eHTMLTextWrap_Hard) { |
2222 | 0 | flags |= nsIDocumentEncoder::OutputWrap; |
2223 | 0 | } |
2224 | 0 | } |
2225 | 0 |
|
2226 | 0 | // What follows is a bit of a hack. The problem is that we could be in |
2227 | 0 | // this method because we're being destroyed for whatever reason while |
2228 | 0 | // script is executing. If that happens, editor will run with the |
2229 | 0 | // privileges of the executing script, which means it may not be able to |
2230 | 0 | // access its own DOM nodes! Let's try to deal with that by pushing a null |
2231 | 0 | // JSContext on the JSContext stack to make it clear that we're native |
2232 | 0 | // code. Note that any script that's directly trying to access our value |
2233 | 0 | // has to be going through some scriptable object to do that and that |
2234 | 0 | // already does the relevant security checks. |
2235 | 0 | // XXXbz if we could just get the textContent of our anonymous content (eg |
2236 | 0 | // if plaintext editor didn't create <br> nodes all over), we wouldn't need |
2237 | 0 | // this. |
2238 | 0 | { /* Scope for AutoNoJSAPI. */ |
2239 | 0 | AutoNoJSAPI nojsapi; |
2240 | 0 |
|
2241 | 0 | DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue); |
2242 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value"); |
2243 | 0 | } |
2244 | 0 | // Only when the result doesn't include line breaks caused by hard-wrap, |
2245 | 0 | // mCacheValue should cache the value. |
2246 | 0 | if (!(flags & nsIDocumentEncoder::OutputWrap)) { |
2247 | 0 | mBoundFrame->CacheValue(aValue); |
2248 | 0 | } else { |
2249 | 0 | mBoundFrame->ClearCachedValue(); |
2250 | 0 | } |
2251 | 0 | } else { |
2252 | 0 | if (!mTextCtrlElement->ValueChanged() || !mValue) { |
2253 | 0 | mTextCtrlElement->GetDefaultValueFromContent(aValue); |
2254 | 0 | } else { |
2255 | 0 | aValue = *mValue; |
2256 | 0 | } |
2257 | 0 | } |
2258 | 0 | } |
2259 | | |
2260 | | bool |
2261 | | nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue, |
2262 | | uint32_t aFlags) |
2263 | 0 | { |
2264 | 0 | nsAutoString newValue(aValue); |
2265 | 0 |
|
2266 | 0 | // While mIsCommittingComposition is true (that means that some event |
2267 | 0 | // handlers which are fired during committing composition are the caller of |
2268 | 0 | // this method), GetValue() uses mValueBeingSet for its result because the |
2269 | 0 | // first calls of this methods hasn't set the value yet. So, when it's true, |
2270 | 0 | // we need to modify mValueBeingSet. In this case, we will back to the first |
2271 | 0 | // call of this method, then, mValueBeingSet will be truncated when |
2272 | 0 | // mIsCommittingComposition is set false. See below. |
2273 | 0 | if (mIsCommittingComposition) { |
2274 | 0 | mValueBeingSet = aValue; |
2275 | 0 | // GetValue doesn't return current text frame's content during committing. |
2276 | 0 | // So we cannot trust this old value |
2277 | 0 | aOldValue = nullptr; |
2278 | 0 | } |
2279 | 0 |
|
2280 | 0 | // Note that if this may be called during reframe of the editor. In such |
2281 | 0 | // case, we shouldn't commit composition. Therefore, when this is called |
2282 | 0 | // for internal processing, we shouldn't commit the composition. |
2283 | 0 | if (aFlags & (eSetValue_BySetUserInput | eSetValue_ByContent)) { |
2284 | 0 | if (EditorHasComposition()) { |
2285 | 0 | // When this is called recursively, there shouldn't be composition. |
2286 | 0 | if (NS_WARN_IF(mIsCommittingComposition)) { |
2287 | 0 | // Don't request to commit composition again. But if it occurs, |
2288 | 0 | // we should skip to set the new value to the editor here. It should |
2289 | 0 | // be set later with the updated mValueBeingSet. |
2290 | 0 | return true; |
2291 | 0 | } |
2292 | 0 | if (NS_WARN_IF(!mBoundFrame)) { |
2293 | 0 | // We're not sure if this case is possible. |
2294 | 0 | } else { |
2295 | 0 | // If setting value won't change current value, we shouldn't commit |
2296 | 0 | // composition for compatibility with the other browsers. |
2297 | 0 | nsAutoString currentValue; |
2298 | 0 | if (aOldValue) { |
2299 | | #ifdef DEBUG |
2300 | | mBoundFrame->GetText(currentValue); |
2301 | | MOZ_ASSERT(currentValue.Equals(*aOldValue)); |
2302 | | #endif |
2303 | | currentValue.Assign(*aOldValue); |
2304 | 0 | } else { |
2305 | 0 | mBoundFrame->GetText(currentValue); |
2306 | 0 | } |
2307 | 0 | if (newValue == currentValue) { |
2308 | 0 | // Note that in this case, we shouldn't fire any events with setting |
2309 | 0 | // value because event handlers may try to set value recursively but |
2310 | 0 | // we cannot commit composition at that time due to unsafe to run |
2311 | 0 | // script (see below). |
2312 | 0 | return true; |
2313 | 0 | } |
2314 | 0 | // IME might commit composition, then change value, so we cannot |
2315 | 0 | // trust old value from parameter. |
2316 | 0 | aOldValue = nullptr; |
2317 | 0 | } |
2318 | 0 | // If there is composition, need to commit composition first because |
2319 | 0 | // other browsers do that. |
2320 | 0 | // NOTE: We don't need to block nested calls of this because input nor |
2321 | 0 | // other events won't be fired by setting values and script blocker |
2322 | 0 | // is used during setting the value to the editor. IE also allows |
2323 | 0 | // to set the editor value on the input event which is caused by |
2324 | 0 | // forcibly committing composition. |
2325 | 0 | if (nsContentUtils::IsSafeToRunScript()) { |
2326 | 0 | WeakPtr<nsTextEditorState> self(this); |
2327 | 0 | // WARNING: During this call, compositionupdate, compositionend, input |
2328 | 0 | // events will be fired. Therefore, everything can occur. E.g., the |
2329 | 0 | // document may be unloaded. |
2330 | 0 | mValueBeingSet = aValue; |
2331 | 0 | mIsCommittingComposition = true; |
2332 | 0 | RefPtr<TextEditor> textEditor = mTextEditor; |
2333 | 0 | nsresult rv = textEditor->CommitComposition(); |
2334 | 0 | if (!self.get()) { |
2335 | 0 | return true; |
2336 | 0 | } |
2337 | 0 | mIsCommittingComposition = false; |
2338 | 0 | // If this is called recursively during committing composition and |
2339 | 0 | // some of them may be skipped above. Therefore, we need to set |
2340 | 0 | // value to the editor with the aValue of the latest call. |
2341 | 0 | newValue = mValueBeingSet; |
2342 | 0 | // When mIsCommittingComposition is false, mValueBeingSet won't be |
2343 | 0 | // used. Therefore, let's clear it. |
2344 | 0 | mValueBeingSet.Truncate(); |
2345 | 0 | if (NS_FAILED(rv)) { |
2346 | 0 | NS_WARNING("nsTextEditorState failed to commit composition"); |
2347 | 0 | return true; |
2348 | 0 | } |
2349 | 0 | } else { |
2350 | 0 | NS_WARNING("SetValue() is called when there is composition but " |
2351 | 0 | "it's not safe to request to commit the composition"); |
2352 | 0 | } |
2353 | 0 | } |
2354 | 0 | } |
2355 | 0 |
|
2356 | 0 | // \r is an illegal character in the dom, but people use them, |
2357 | 0 | // so convert windows and mac platform linebreaks to \n: |
2358 | 0 | if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) { |
2359 | 0 | return false; |
2360 | 0 | } |
2361 | 0 | |
2362 | 0 | if (mTextEditor && mBoundFrame) { |
2363 | 0 | // The InsertText call below might flush pending notifications, which |
2364 | 0 | // could lead into a scheduled PrepareEditor to be called. That will |
2365 | 0 | // lead to crashes (or worse) because we'd be initializing the editor |
2366 | 0 | // before InsertText returns. This script blocker makes sure that |
2367 | 0 | // PrepareEditor cannot be called prematurely. |
2368 | 0 | nsAutoScriptBlocker scriptBlocker; |
2369 | 0 |
|
2370 | | #ifdef DEBUG |
2371 | | if (IsSingleLineTextControl()) { |
2372 | | NS_ASSERTION(mEditorInitialized || mInitializing, |
2373 | | "We should never try to use the editor if we're not initialized unless we're being initialized"); |
2374 | | } |
2375 | | #endif |
2376 | |
|
2377 | 0 | nsAutoString currentValue; |
2378 | 0 | if (aOldValue) { |
2379 | | #ifdef DEBUG |
2380 | | mBoundFrame->GetText(currentValue); |
2381 | | MOZ_ASSERT(currentValue.Equals(*aOldValue)); |
2382 | | #endif |
2383 | | currentValue.Assign(*aOldValue); |
2384 | 0 | } else { |
2385 | 0 | mBoundFrame->GetText(currentValue); |
2386 | 0 | } |
2387 | 0 |
|
2388 | 0 | AutoWeakFrame weakFrame(mBoundFrame); |
2389 | 0 |
|
2390 | 0 | // this is necessary to avoid infinite recursion |
2391 | 0 | if (!currentValue.Equals(newValue)) { |
2392 | 0 | RefPtr<TextEditor> textEditor = mTextEditor; |
2393 | 0 | ValueSetter valueSetter(textEditor); |
2394 | 0 |
|
2395 | 0 | nsCOMPtr<nsIDocument> document = textEditor->GetDocument(); |
2396 | 0 | if (NS_WARN_IF(!document)) { |
2397 | 0 | return true; |
2398 | 0 | } |
2399 | 0 | |
2400 | 0 | // Time to mess with our security context... See comments in GetValue() |
2401 | 0 | // for why this is needed. Note that we have to do this up here, because |
2402 | 0 | // otherwise SelectAll() will fail. |
2403 | 0 | { |
2404 | 0 | AutoNoJSAPI nojsapi; |
2405 | 0 |
|
2406 | 0 | // FYI: It's safe to use raw pointer for selection here because |
2407 | 0 | // SelectionBatcher will grab it with RefPtr. |
2408 | 0 | Selection* selection = |
2409 | 0 | mSelCon->GetSelection(SelectionType::eNormal); |
2410 | 0 | SelectionBatcher selectionBatcher(selection); |
2411 | 0 |
|
2412 | 0 | if (NS_WARN_IF(!weakFrame.IsAlive())) { |
2413 | 0 | return true; |
2414 | 0 | } |
2415 | 0 | |
2416 | 0 | valueSetter.Init(); |
2417 | 0 |
|
2418 | 0 | // get the flags, remove readonly, disabled and max-length, |
2419 | 0 | // set the value, restore flags |
2420 | 0 | { |
2421 | 0 | AutoRestoreEditorState restoreState(textEditor); |
2422 | 0 |
|
2423 | 0 | mTextListener->SettingValue(true); |
2424 | 0 | bool notifyValueChanged = !!(aFlags & eSetValue_Notify); |
2425 | 0 | mTextListener->SetValueChanged(notifyValueChanged); |
2426 | 0 |
|
2427 | 0 | if (aFlags & eSetValue_BySetUserInput) { |
2428 | 0 | // If the caller inserts text as part of user input, for example, |
2429 | 0 | // autocomplete, we need to replace the text as "insert string" |
2430 | 0 | // because undo should cancel only this operation (i.e., previous |
2431 | 0 | // transactions typed by user shouldn't be merged with this). |
2432 | 0 | DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue); |
2433 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
2434 | 0 | "Failed to set the new value"); |
2435 | 0 | } else if (aFlags & eSetValue_ForXUL) { |
2436 | 0 | // On XUL <textbox> element, we need to preserve existing undo |
2437 | 0 | // transactions. |
2438 | 0 | // XXX Do we really need to do such complicated optimization? |
2439 | 0 | // This was landed for web pages which set <textarea> value |
2440 | 0 | // per line (bug 518122). For example: |
2441 | 0 | // for (;;) { |
2442 | 0 | // textarea.value += oneLineText + "\n"; |
2443 | 0 | // } |
2444 | 0 | // However, this path won't be used in web content anymore. |
2445 | 0 | nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get(); |
2446 | 0 | uint32_t currentLength = currentValue.Length(); |
2447 | 0 | uint32_t newlength = newValue.Length(); |
2448 | 0 | if (!currentLength || |
2449 | 0 | !StringBeginsWith(newValue, currentValue)) { |
2450 | 0 | // Replace the whole text. |
2451 | 0 | currentLength = 0; |
2452 | 0 | kungFuDeathGrip->SelectAll(); |
2453 | 0 | } else { |
2454 | 0 | // Collapse selection to the end so that we can append data. |
2455 | 0 | mBoundFrame->SelectAllOrCollapseToEndOfText(false); |
2456 | 0 | } |
2457 | 0 | const nsAString& insertValue = |
2458 | 0 | StringTail(newValue, newlength - currentLength); |
2459 | 0 |
|
2460 | 0 | if (insertValue.IsEmpty()) { |
2461 | 0 | DebugOnly<nsresult> rv = |
2462 | 0 | textEditor->DeleteSelectionAsAction(nsIEditor::eNone, |
2463 | 0 | nsIEditor::eStrip); |
2464 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
2465 | 0 | "Failed to remove the text"); |
2466 | 0 | } else { |
2467 | 0 | DebugOnly<nsresult> rv = |
2468 | 0 | textEditor->InsertTextAsAction(insertValue); |
2469 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
2470 | 0 | "Failed to insert the new value"); |
2471 | 0 | } |
2472 | 0 | } else { |
2473 | 0 | // On <input> or <textarea>, we shouldn't preserve existing undo |
2474 | 0 | // transactions because other browsers do not preserve them too |
2475 | 0 | // and not preserving transactions makes setting value faster. |
2476 | 0 | AutoDisableUndo disableUndo(textEditor); |
2477 | 0 | if (selection) { |
2478 | 0 | // Since we don't use undo transaction, we don't need to store |
2479 | 0 | // selection state. SetText will set selection to tail. |
2480 | 0 | // Note that textEditor will collapse selection to the end. |
2481 | 0 | // Therefore, it's safe to use RemoveAllRangesTemporarily() here. |
2482 | 0 | selection->RemoveAllRangesTemporarily(); |
2483 | 0 | } |
2484 | 0 |
|
2485 | 0 | textEditor->SetText(newValue); |
2486 | 0 |
|
2487 | 0 | // Call the listener's HandleValueChanged() callback manually, since |
2488 | 0 | // we don't use the transaction manager in this path and it could be |
2489 | 0 | // that the editor would bypass calling the listener for that reason. |
2490 | 0 | mTextListener->HandleValueChanged(); |
2491 | 0 | } |
2492 | 0 |
|
2493 | 0 | mTextListener->SetValueChanged(true); |
2494 | 0 | mTextListener->SettingValue(false); |
2495 | 0 |
|
2496 | 0 | if (!notifyValueChanged) { |
2497 | 0 | // Listener doesn't update frame, but it is required for placeholder |
2498 | 0 | ValueWasChanged(true); |
2499 | 0 | } |
2500 | 0 | } |
2501 | 0 |
|
2502 | 0 | if (!weakFrame.IsAlive()) { |
2503 | 0 | // If the frame was destroyed because of a flush somewhere inside |
2504 | 0 | // InsertText, mBoundFrame here will be false. But it's also possible |
2505 | 0 | // for the frame to go away because of another reason (such as deleting |
2506 | 0 | // the existing selection -- see bug 574558), in which case we don't |
2507 | 0 | // need to reset the value here. |
2508 | 0 | if (!mBoundFrame) { |
2509 | 0 | return SetValue(newValue, aFlags & eSetValue_Notify); |
2510 | 0 | } |
2511 | 0 | return true; |
2512 | 0 | } |
2513 | 0 | |
2514 | 0 | // The new value never includes line breaks caused by hard-wrap. |
2515 | 0 | // So, mCachedValue can always cache the new value. |
2516 | 0 | if (!mBoundFrame->CacheValue(newValue, fallible)) { |
2517 | 0 | return false; |
2518 | 0 | } |
2519 | 0 | } |
2520 | 0 | } |
2521 | 0 | } else { |
2522 | 0 | if (!mValue) { |
2523 | 0 | mValue.emplace(); |
2524 | 0 | } |
2525 | 0 |
|
2526 | 0 | // We can't just early-return here if mValue->Equals(newValue), because |
2527 | 0 | // ValueWasChanged and OnValueChanged below still need to be called. |
2528 | 0 | if (!mValue->Equals(newValue) || |
2529 | 0 | !nsContentUtils::SkipCursorMoveForSameValueSet()) { |
2530 | 0 | if (!mValue->Assign(newValue, fallible)) { |
2531 | 0 | return false; |
2532 | 0 | } |
2533 | 0 | |
2534 | 0 | // Since we have no editor we presumably have cached selection state. |
2535 | 0 | if (IsSelectionCached()) { |
2536 | 0 | SelectionProperties& props = GetSelectionProperties(); |
2537 | 0 | if (aFlags & eSetValue_MoveCursorToEndIfValueChanged) { |
2538 | 0 | props.SetStart(newValue.Length()); |
2539 | 0 | props.SetEnd(newValue.Length()); |
2540 | 0 | props.SetDirection(nsITextControlFrame::eForward); |
2541 | 0 | } else { |
2542 | 0 | // Make sure our cached selection position is not outside the new value. |
2543 | 0 | props.SetStart(std::min(props.GetStart(), newValue.Length())); |
2544 | 0 | props.SetEnd(std::min(props.GetEnd(), newValue.Length())); |
2545 | 0 | } |
2546 | 0 | } |
2547 | 0 |
|
2548 | 0 | // Update the frame display if needed |
2549 | 0 | if (mBoundFrame) { |
2550 | 0 | mBoundFrame->UpdateValueDisplay(true); |
2551 | 0 | } |
2552 | 0 | } else { |
2553 | 0 | // Even if our value is not actually changing, apparently we need to mark |
2554 | 0 | // our SelectionProperties dirty to make accessibility tests happy. |
2555 | 0 | // Probably because they depend on the SetSelectionRange() call we make on |
2556 | 0 | // our frame in RestoreSelectionState, but I have no idea why they do. |
2557 | 0 | if (IsSelectionCached()) { |
2558 | 0 | SelectionProperties& props = GetSelectionProperties(); |
2559 | 0 | props.SetIsDirty(); |
2560 | 0 | } |
2561 | 0 | } |
2562 | 0 |
|
2563 | 0 | // If we've reached the point where the root node has been created, we |
2564 | 0 | // can assume that it's safe to notify. |
2565 | 0 | ValueWasChanged(!!mBoundFrame); |
2566 | 0 | } |
2567 | 0 |
|
2568 | 0 | mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame, |
2569 | 0 | /* aWasInteractiveUserChange = */ false); |
2570 | 0 |
|
2571 | 0 | return true; |
2572 | 0 | } |
2573 | | |
2574 | | bool |
2575 | | nsTextEditorState::HasNonEmptyValue() |
2576 | 0 | { |
2577 | 0 | if (mTextEditor && mBoundFrame && mEditorInitialized && |
2578 | 0 | !mIsCommittingComposition) { |
2579 | 0 | bool empty; |
2580 | 0 | nsresult rv = mTextEditor->IsEmpty(&empty); |
2581 | 0 | if (NS_SUCCEEDED(rv)) { |
2582 | 0 | return !empty; |
2583 | 0 | } |
2584 | 0 | } |
2585 | 0 | |
2586 | 0 | nsAutoString value; |
2587 | 0 | GetValue(value, true); |
2588 | 0 | return !value.IsEmpty(); |
2589 | 0 | } |
2590 | | |
2591 | | void |
2592 | | nsTextEditorState::InitializeKeyboardEventListeners() |
2593 | 0 | { |
2594 | 0 | //register key listeners |
2595 | 0 | nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement); |
2596 | 0 | EventListenerManager* manager = target->GetOrCreateListenerManager(); |
2597 | 0 | if (manager) { |
2598 | 0 | manager->AddEventListenerByType(mTextListener, |
2599 | 0 | NS_LITERAL_STRING("keydown"), |
2600 | 0 | TrustedEventsAtSystemGroupBubble()); |
2601 | 0 | manager->AddEventListenerByType(mTextListener, |
2602 | 0 | NS_LITERAL_STRING("keypress"), |
2603 | 0 | TrustedEventsAtSystemGroupBubble()); |
2604 | 0 | manager->AddEventListenerByType(mTextListener, |
2605 | 0 | NS_LITERAL_STRING("keyup"), |
2606 | 0 | TrustedEventsAtSystemGroupBubble()); |
2607 | 0 | } |
2608 | 0 |
|
2609 | 0 | mSelCon->SetScrollableFrame(do_QueryFrame(mBoundFrame->PrincipalChildList().FirstChild())); |
2610 | 0 | } |
2611 | | |
2612 | | void |
2613 | | nsTextEditorState::ValueWasChanged(bool aNotify) |
2614 | 0 | { |
2615 | 0 | UpdateOverlayTextVisibility(aNotify); |
2616 | 0 | } |
2617 | | |
2618 | | void |
2619 | | nsTextEditorState::SetPreviewText(const nsAString& aValue, bool aNotify) |
2620 | 0 | { |
2621 | 0 | // If we don't have a preview div, there's nothing to do. |
2622 | 0 | Element* previewDiv = GetPreviewNode(); |
2623 | 0 | if (!previewDiv) |
2624 | 0 | return; |
2625 | 0 | |
2626 | 0 | nsAutoString previewValue(aValue); |
2627 | 0 |
|
2628 | 0 | nsContentUtils::RemoveNewlines(previewValue); |
2629 | 0 | MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child"); |
2630 | 0 | previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify); |
2631 | 0 |
|
2632 | 0 | UpdateOverlayTextVisibility(aNotify); |
2633 | 0 | } |
2634 | | |
2635 | | void |
2636 | | nsTextEditorState::GetPreviewText(nsAString& aValue) |
2637 | 0 | { |
2638 | 0 | // If we don't have a preview div, there's nothing to do. |
2639 | 0 | Element* previewDiv = GetPreviewNode(); |
2640 | 0 | if (!previewDiv) |
2641 | 0 | return; |
2642 | 0 | |
2643 | 0 | MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child"); |
2644 | 0 | const nsTextFragment *text = previewDiv->GetFirstChild()->GetText(); |
2645 | 0 |
|
2646 | 0 | aValue.Truncate(); |
2647 | 0 | text->AppendTo(aValue); |
2648 | 0 | } |
2649 | | |
2650 | | void |
2651 | | nsTextEditorState::UpdateOverlayTextVisibility(bool aNotify) |
2652 | 0 | { |
2653 | 0 | nsAutoString value, previewValue; |
2654 | 0 | bool valueIsEmpty = !HasNonEmptyValue(); |
2655 | 0 | GetPreviewText(previewValue); |
2656 | 0 |
|
2657 | 0 | mPreviewVisibility = valueIsEmpty && !previewValue.IsEmpty(); |
2658 | 0 | mPlaceholderVisibility = valueIsEmpty && previewValue.IsEmpty(); |
2659 | 0 |
|
2660 | 0 | if (mPlaceholderVisibility && |
2661 | 0 | !nsContentUtils::ShowInputPlaceholderOnFocus()) { |
2662 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); |
2663 | 0 | mPlaceholderVisibility = !nsContentUtils::IsFocusedContent(content); |
2664 | 0 | } |
2665 | 0 |
|
2666 | 0 | if (mBoundFrame && aNotify) { |
2667 | 0 | mBoundFrame->InvalidateFrame(); |
2668 | 0 | } |
2669 | 0 | } |
2670 | | |
2671 | | void |
2672 | | nsTextEditorState::HideSelectionIfBlurred() |
2673 | 0 | { |
2674 | 0 | MOZ_ASSERT(mSelCon, "Should have a selection controller if we have a frame!"); |
2675 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); |
2676 | 0 | if (!nsContentUtils::IsFocusedContent(content)) { |
2677 | 0 | mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); |
2678 | 0 | } |
2679 | 0 | } |
2680 | | |
2681 | | bool |
2682 | | nsTextEditorState::EditorHasComposition() |
2683 | 0 | { |
2684 | 0 | return mTextEditor && mTextEditor->IsIMEComposing(); |
2685 | 0 | } |