mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 01:48:05 +00:00
Bug 335615. Don't do editor init inside frame construction. r=mats, sr=roc
This commit is contained in:
parent
16fff652b0
commit
ff8ff59c94
@ -1302,6 +1302,7 @@ public:
|
||||
* scripts. Passing null is allowed and results in nothing
|
||||
* happening. It is also allowed to pass an object that
|
||||
* has not yet been AddRefed.
|
||||
* @return false on out of memory, true otherwise.
|
||||
*/
|
||||
static PRBool AddScriptRunner(nsIRunnable* aRunnable);
|
||||
|
||||
|
@ -1005,41 +1005,26 @@ nsHTMLInputElement::SetValueInternal(const nsAString& aValue,
|
||||
|
||||
if (mType == NS_FORM_INPUT_TEXT || mType == NS_FORM_INPUT_PASSWORD) {
|
||||
|
||||
nsITextControlFrame* textControlFrame = aFrame;
|
||||
nsIFormControlFrame* formControlFrame = textControlFrame;
|
||||
if (!textControlFrame) {
|
||||
nsIFormControlFrame* formControlFrame = aFrame;
|
||||
if (!formControlFrame) {
|
||||
// No need to flush here, if there's no frame at this point we
|
||||
// don't need to force creation of one just to tell it about this
|
||||
// new value.
|
||||
formControlFrame = GetFormControlFrame(PR_FALSE);
|
||||
|
||||
if (formControlFrame) {
|
||||
textControlFrame = do_QueryFrame(formControlFrame);
|
||||
}
|
||||
}
|
||||
|
||||
// File frames always own the value (if the frame is there).
|
||||
// Text frames have a bit that says whether they own the value.
|
||||
PRBool frameOwnsValue = PR_FALSE;
|
||||
if (textControlFrame) {
|
||||
textControlFrame->OwnsValue(&frameOwnsValue);
|
||||
}
|
||||
// If the frame owns the value, set the value in the frame
|
||||
if (frameOwnsValue) {
|
||||
if (formControlFrame) {
|
||||
// Always set the value in the frame. If the frame does not own the
|
||||
// value yet (per OwnsValue()), it will turn around and call
|
||||
// TakeTextFrameValue() on us, but will update its display with the new
|
||||
// value if needed.
|
||||
formControlFrame->SetFormProperty(
|
||||
aUserInput ? nsGkAtoms::userInput : nsGkAtoms::value, aValue);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If the frame does not own the value, set mValue
|
||||
if (mValue) {
|
||||
nsMemory::Free(mValue);
|
||||
}
|
||||
|
||||
mValue = ToNewUTF8String(aValue);
|
||||
|
||||
SetValueChanged(PR_TRUE);
|
||||
return mValue ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
|
||||
return TakeTextFrameValue(aValue);
|
||||
}
|
||||
|
||||
if (mType == NS_FORM_INPUT_FILE) {
|
||||
|
@ -5406,8 +5406,6 @@ nsCSSFrameConstructor::CreateAnonymousFrames(nsFrameConstructorState& aState,
|
||||
}
|
||||
}
|
||||
|
||||
creator->PostCreateFrames();
|
||||
|
||||
// process the current pseudo frame state
|
||||
if (!aState.mPseudoFrames.IsEmpty()) {
|
||||
ProcessPseudoFrames(aState, aChildItems);
|
||||
|
@ -1020,9 +1020,7 @@ struct nsAutoLayoutPhase {
|
||||
"constructing frames in the middle of a paint");
|
||||
NS_ASSERTION(mPresContext->mLayoutPhaseCount[eLayoutPhase_Reflow] == 0,
|
||||
"constructing frames in the middle of reflow");
|
||||
// The nsXBLService::LoadBindings call in ConstructFrameInternal
|
||||
// makes us hit this one too often to be an NS_ASSERTION,
|
||||
// despite how scary it is.
|
||||
// Once bug 337957 is fixed this should become an NS_ASSERTION
|
||||
NS_WARN_IF_FALSE(mPresContext->mLayoutPhaseCount[eLayoutPhase_FrameC] == 0,
|
||||
"recurring into frame construction");
|
||||
break;
|
||||
|
@ -1034,9 +1034,6 @@ nsTextControlFrame::nsTextControlFrame(nsIPresShell* aShell, nsStyleContext* aCo
|
||||
, mFireChangeEventState(PR_FALSE)
|
||||
, mInSecureKeyboardInputMode(PR_FALSE)
|
||||
, mTextListener(nsnull)
|
||||
#ifdef DEBUG
|
||||
, mCreateFrameForCalled(PR_FALSE)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
@ -1133,7 +1130,6 @@ nsTextControlFrame::PreDestroy()
|
||||
mFrameSel = nsnull;
|
||||
}
|
||||
|
||||
//unregister self from content
|
||||
nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), PR_FALSE);
|
||||
if (mTextListener)
|
||||
{
|
||||
@ -1388,7 +1384,7 @@ nsTextControlFrame::CalcIntrinsicSize(nsIRenderingContext* aRenderingContext,
|
||||
}
|
||||
|
||||
void
|
||||
nsTextControlFrame::PostCreateFrames()
|
||||
nsTextControlFrame::DelayedEditorInit()
|
||||
{
|
||||
InitEditor();
|
||||
// Notify the text listener we have focus and setup the caret etc (bug 446663).
|
||||
@ -1398,53 +1394,30 @@ nsTextControlFrame::PostCreateFrames()
|
||||
}
|
||||
}
|
||||
|
||||
nsIFrame*
|
||||
nsTextControlFrame::CreateFrameFor(nsIContent* aContent)
|
||||
nsresult
|
||||
nsTextControlFrame::InitEditor()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NS_ASSERTION(!mCreateFrameForCalled, "CreateFrameFor called more than once!");
|
||||
mCreateFrameForCalled = PR_TRUE;
|
||||
#endif
|
||||
// This method initializes our editor, if needed.
|
||||
|
||||
nsPresContext *presContext = PresContext();
|
||||
nsIPresShell *shell = presContext->GetPresShell();
|
||||
if (!shell)
|
||||
return nsnull;
|
||||
|
||||
nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(shell->GetDocument());
|
||||
if (!domdoc)
|
||||
return nsnull;
|
||||
// This code used to be called from CreateAnonymousContent(), but
|
||||
// when the editor set the initial string, it would trigger a
|
||||
// PresShell listener which called FlushPendingNotifications()
|
||||
// during frame construction. This was causing other form controls
|
||||
// to display wrong values. So we call this from a script runner
|
||||
// now.
|
||||
|
||||
// Don't create any frames here, but just setup the editor.
|
||||
// This way DOM Ranges (which editor uses) work properly since the anonymous
|
||||
// content is bound to tree after CreateAnonymousContent but before this
|
||||
// method.
|
||||
nsresult rv = NS_OK;
|
||||
// Check if this method has been called already.
|
||||
// If so, just return early.
|
||||
|
||||
if (mUseEditor)
|
||||
return NS_OK;
|
||||
|
||||
// Create an editor
|
||||
|
||||
nsresult rv;
|
||||
mEditor = do_CreateInstance(kTextEditorCID, &rv);
|
||||
if (NS_FAILED(rv) || !mEditor)
|
||||
return nsnull;
|
||||
|
||||
// Create selection
|
||||
|
||||
mFrameSel = do_CreateInstance(kFrameSelectionCID, &rv);
|
||||
if (NS_FAILED(rv))
|
||||
return nsnull;
|
||||
mFrameSel->SetScrollableViewProvider(this);
|
||||
|
||||
// Create a SelectionController
|
||||
|
||||
mSelCon = static_cast<nsISelectionController*>
|
||||
(new nsTextInputSelectionImpl(mFrameSel, shell, aContent));
|
||||
if (!mSelCon)
|
||||
return nsnull;
|
||||
mTextListener = new nsTextInputListener();
|
||||
if (!mTextListener)
|
||||
return nsnull;
|
||||
NS_ADDREF(mTextListener);
|
||||
|
||||
mTextListener->SetFrame(this);
|
||||
mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Setup the editor flags
|
||||
|
||||
PRUint32 editorFlags = 0;
|
||||
@ -1455,7 +1428,7 @@ nsTextControlFrame::CreateFrameFor(nsIContent* aContent)
|
||||
if (IsPasswordTextControl())
|
||||
editorFlags |= nsIPlaintextEditor::eEditorPasswordMask;
|
||||
|
||||
// All gfxtextcontrolframe2's are widgets
|
||||
// All nsTextControlFrames are widgets
|
||||
editorFlags |= nsIPlaintextEditor::eEditorWidgetMask;
|
||||
|
||||
// Use async reflow and painting for text widgets to improve
|
||||
@ -1471,10 +1444,16 @@ nsTextControlFrame::CreateFrameFor(nsIContent* aContent)
|
||||
// NOTE: Conversion of '\n' to <BR> happens inside the
|
||||
// editor's Init() call.
|
||||
|
||||
rv = mEditor->Init(domdoc, shell, aContent, mSelCon, editorFlags);
|
||||
nsPresContext *presContext = PresContext();
|
||||
nsIPresShell *shell = presContext->GetPresShell();
|
||||
|
||||
if (NS_FAILED(rv))
|
||||
return nsnull;
|
||||
// Get the DOM document
|
||||
nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(shell->GetDocument());
|
||||
if (!domdoc)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
rv = mEditor->Init(domdoc, shell, mAnonymousDiv, mSelCon, editorFlags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Initialize the controller for the editor
|
||||
|
||||
@ -1489,13 +1468,13 @@ nsTextControlFrame::CreateFrameFor(nsIContent* aContent)
|
||||
do_QueryInterface(mContent);
|
||||
|
||||
if (!textAreaElement)
|
||||
return nsnull;
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
rv = textAreaElement->GetControllers(getter_AddRefs(controllers));
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv))
|
||||
return nsnull;
|
||||
return rv;
|
||||
|
||||
if (controllers) {
|
||||
PRUint32 numControllers;
|
||||
@ -1545,26 +1524,6 @@ nsTextControlFrame::CreateFrameFor(nsIContent* aContent)
|
||||
textEditor->SetMaxTextLength(maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the caret and make it a selection listener.
|
||||
|
||||
nsRefPtr<nsISelection> domSelection;
|
||||
if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(domSelection))) &&
|
||||
domSelection) {
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(domSelection));
|
||||
nsRefPtr<nsCaret> caret;
|
||||
nsCOMPtr<nsISelectionListener> listener;
|
||||
if (NS_SUCCEEDED(shell->GetCaret(getter_AddRefs(caret))) && caret) {
|
||||
listener = do_QueryInterface(caret);
|
||||
if (listener) {
|
||||
selPriv->AddSelectionListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
selPriv->AddSelectionListener(static_cast<nsISelectionListener*>
|
||||
(mTextListener));
|
||||
}
|
||||
|
||||
if (mContent) {
|
||||
rv = mEditor->GetFlags(&editorFlags);
|
||||
@ -1589,33 +1548,6 @@ nsTextControlFrame::CreateFrameFor(nsIContent* aContent)
|
||||
|
||||
mEditor->SetFlags(editorFlags);
|
||||
}
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsTextControlFrame::InitEditor()
|
||||
{
|
||||
// This method must be called during/after the text
|
||||
// control frame's initial reflow to avoid any unintened
|
||||
// forced reflows that might result when the editor
|
||||
// calls into DOM/layout code while trying to set the
|
||||
// initial string.
|
||||
//
|
||||
// This code used to be called from CreateAnonymousContent(),
|
||||
// but when the editor set the initial string, it would trigger
|
||||
// a PresShell listener which called FlushPendingNotifications()
|
||||
// during frame construction. This was causing other form controls
|
||||
// to display wrong values.
|
||||
|
||||
// Check if this method has been called already.
|
||||
// If so, just return early.
|
||||
|
||||
if (mUseEditor)
|
||||
return NS_OK;
|
||||
|
||||
// If the editor is not here, then we can't use it, now can we?
|
||||
if (!mEditor)
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
|
||||
// Get the current value of the textfield from the content.
|
||||
nsAutoString defaultValue;
|
||||
@ -1631,13 +1563,6 @@ nsTextControlFrame::InitEditor()
|
||||
// editor for us.
|
||||
|
||||
if (!defaultValue.IsEmpty()) {
|
||||
PRUint32 editorFlags = 0;
|
||||
|
||||
nsresult rv = mEditor->GetFlags(&editorFlags);
|
||||
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
// Avoid causing reentrant painting and reflowing by telling the editor
|
||||
// that we don't want it to force immediate view refreshes or force
|
||||
// immediate reflows during any editor calls.
|
||||
@ -1662,8 +1587,8 @@ nsTextControlFrame::InitEditor()
|
||||
|
||||
rv = mEditor->EnableUndo(PR_TRUE);
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv),"Transaction Manager must have failed");
|
||||
// Now restore the original editor flags.
|
||||
|
||||
// Now restore the original editor flags.
|
||||
rv = mEditor->SetFlags(editorFlags);
|
||||
|
||||
if (NS_FAILED(rv))
|
||||
@ -1685,6 +1610,8 @@ nsTextControlFrame::InitEditor()
|
||||
mEditor->EnableUndo(PR_FALSE);
|
||||
}
|
||||
|
||||
mEditor->PostCreate();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -1738,7 +1665,55 @@ nsTextControlFrame::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
|
||||
if (!aElements.AppendElement(mAnonymousDiv))
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
// rv = divContent->SetAttr(kNameSpaceID_None,nsGkAtoms::debug, NS_LITERAL_STRING("true"), PR_FALSE);
|
||||
// Create selection
|
||||
|
||||
mFrameSel = do_CreateInstance(kFrameSelectionCID, &rv);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
mFrameSel->SetScrollableViewProvider(this);
|
||||
|
||||
// Create a SelectionController
|
||||
|
||||
mSelCon = static_cast<nsISelectionController*>
|
||||
(new nsTextInputSelectionImpl(mFrameSel, shell,
|
||||
mAnonymousDiv));
|
||||
if (!mSelCon)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
mTextListener = new nsTextInputListener();
|
||||
if (!mTextListener)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
NS_ADDREF(mTextListener);
|
||||
|
||||
mTextListener->SetFrame(this);
|
||||
mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
|
||||
|
||||
// Get the caret and make it a selection listener.
|
||||
|
||||
nsRefPtr<nsISelection> domSelection;
|
||||
if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(domSelection))) &&
|
||||
domSelection) {
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(domSelection));
|
||||
nsRefPtr<nsCaret> caret;
|
||||
nsCOMPtr<nsISelectionListener> listener;
|
||||
if (NS_SUCCEEDED(shell->GetCaret(getter_AddRefs(caret))) && caret) {
|
||||
listener = do_QueryInterface(caret);
|
||||
if (listener) {
|
||||
selPriv->AddSelectionListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
selPriv->AddSelectionListener(static_cast<nsISelectionListener*>
|
||||
(mTextListener));
|
||||
}
|
||||
|
||||
NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
|
||||
"Someone forgot a script blocker?");
|
||||
|
||||
if (!nsContentUtils::AddScriptRunner(new EditorInitializer(this))) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -1930,11 +1905,7 @@ nsresult nsTextControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aV
|
||||
if (isUserInput) {
|
||||
SetFireChangeEventState(PR_TRUE);
|
||||
}
|
||||
if (mEditor && mUseEditor) {
|
||||
// If the editor exists, the control needs to be informed that the value
|
||||
// has changed.
|
||||
SetValueChanged(PR_TRUE);
|
||||
}
|
||||
SetValueChanged(PR_TRUE);
|
||||
nsresult rv = SetValue(aValue); // set new text value
|
||||
if (isUserInput) {
|
||||
SetFireChangeEventState(fireChangeEvent);
|
||||
@ -2387,7 +2358,8 @@ nsTextControlFrame::AttributeChanged(PRInt32 aNameSpaceID,
|
||||
PRInt32 aModType)
|
||||
{
|
||||
if (!mEditor || !mSelCon)
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
return nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);;
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
if (nsGkAtoms::maxlength == aAttribute)
|
||||
@ -2428,7 +2400,7 @@ nsTextControlFrame::AttributeChanged(PRInt32 aNameSpaceID,
|
||||
}
|
||||
mEditor->SetFlags(flags);
|
||||
}
|
||||
else if (mEditor && nsGkAtoms::disabled == aAttribute)
|
||||
else if (nsGkAtoms::disabled == aAttribute)
|
||||
{
|
||||
PRUint32 flags;
|
||||
mEditor->GetFlags(&flags);
|
||||
@ -2786,8 +2758,7 @@ nsTextControlFrame::SetInitialChildList(nsIAtom* aListName,
|
||||
nsIFrame* aChildList)
|
||||
{
|
||||
nsresult rv = nsBoxFrame::SetInitialChildList(aListName, aChildList);
|
||||
if (mEditor)
|
||||
mEditor->PostCreate();
|
||||
|
||||
//look for scroll view below this frame go along first child list
|
||||
nsIFrame* first = GetFirstChild(nsnull);
|
||||
|
||||
|
@ -119,8 +119,6 @@ public:
|
||||
|
||||
// nsIAnonymousContentCreator
|
||||
virtual nsresult CreateAnonymousContent(nsTArray<nsIContent*>& aElements);
|
||||
virtual nsIFrame* CreateFrameFor(nsIContent* aContent);
|
||||
virtual void PostCreateFrames();
|
||||
|
||||
// Utility methods to set current widget state
|
||||
|
||||
@ -139,7 +137,7 @@ public:
|
||||
|
||||
//==== END NSIFORMCONTROLFRAME
|
||||
|
||||
//==== NSIGFXTEXTCONTROLFRAME2
|
||||
//==== NSITEXTCONTROLFRAME
|
||||
|
||||
NS_IMETHOD GetEditor(nsIEditor **aEditor);
|
||||
NS_IMETHOD OwnsValue(PRBool* aOwnsValue);
|
||||
@ -157,7 +155,7 @@ public:
|
||||
|
||||
nsresult GetPhonetic(nsAString& aPhonetic);
|
||||
|
||||
//==== END NSIGFXTEXTCONTROLFRAME2
|
||||
//==== END NSITEXTCONTROLFRAME
|
||||
//==== OVERLOAD of nsIFrame
|
||||
virtual nsIAtom* GetType() const;
|
||||
|
||||
@ -195,8 +193,6 @@ public: //for methods who access nsTextControlFrame directly
|
||||
void SetValueChanged(PRBool aValueChanged);
|
||||
/** Called when the frame is focused, to remember the value for onChange. */
|
||||
nsresult InitFocusedValue();
|
||||
nsresult DOMPointToOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset, PRInt32 *aResult);
|
||||
nsresult OffsetToDOMPoint(PRInt32 aOffset, nsIDOMNode** aResult, PRInt32* aPosition);
|
||||
|
||||
void SetFireChangeEventState(PRBool aNewState)
|
||||
{
|
||||
@ -216,6 +212,34 @@ public: //for methods who access nsTextControlFrame directly
|
||||
void MaybeEndSecureKeyboardInput();
|
||||
|
||||
protected:
|
||||
class EditorInitializer;
|
||||
friend class EditorInitializer;
|
||||
|
||||
class EditorInitializer : public nsRunnable {
|
||||
public:
|
||||
EditorInitializer(nsTextControlFrame* aFrame) :
|
||||
mWeakFrame(aFrame),
|
||||
mFrame(aFrame) {}
|
||||
|
||||
NS_IMETHOD Run() {
|
||||
if (mWeakFrame) {
|
||||
mFrame->DelayedEditorInit();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsWeakFrame mWeakFrame;
|
||||
nsTextControlFrame* mFrame;
|
||||
};
|
||||
|
||||
// Init our editor and then make sure to focus our text input
|
||||
// listener if our content node has focus.
|
||||
void DelayedEditorInit();
|
||||
|
||||
nsresult DOMPointToOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset, PRInt32 *aResult);
|
||||
nsresult OffsetToDOMPoint(PRInt32 aOffset, nsIDOMNode** aResult, PRInt32* aPosition);
|
||||
|
||||
/**
|
||||
* Find out whether this control is scrollable (i.e. if it is not a single
|
||||
* line text control)
|
||||
@ -303,10 +327,6 @@ private:
|
||||
nsCOMPtr<nsFrameSelection> mFrameSel;
|
||||
nsTextInputListener* mTextListener;
|
||||
nsString mFocusedValue;
|
||||
|
||||
#ifdef DEBUG
|
||||
PRBool mCreateFrameForCalled;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -46,14 +46,12 @@
|
||||
#include "nsQueryFrame.h"
|
||||
#include "nsIContent.h"
|
||||
|
||||
class nsPresContext;
|
||||
class nsIFrame;
|
||||
template <class T> class nsTArray;
|
||||
|
||||
/**
|
||||
* Any source for anonymous content can implement this interface to provide it.
|
||||
* HTML frames like nsFileControlFrame currently use this as well as XUL frames
|
||||
* like nsScrollbarFrame and nsSliderFrame.
|
||||
* HTML frames like nsFileControlFrame currently use this.
|
||||
*
|
||||
* @see nsCSSFrameConstructor
|
||||
*/
|
||||
@ -80,12 +78,6 @@ public:
|
||||
* is created.
|
||||
*/
|
||||
virtual nsIFrame* CreateFrameFor(nsIContent* aContent) { return nsnull; }
|
||||
|
||||
/**
|
||||
* This gets called after the frames for the anonymous content have been
|
||||
* created and added to the frame tree. By default it does nothing.
|
||||
*/
|
||||
virtual void PostCreateFrames() {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user