首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > JavaScript >

firefox中input隐藏之后用js获取选择起始和结束位置引起错误(源码分析)

2012-12-18 
firefox中input隐藏之后用js获取选择起始和结束位置引起异常(源码分析)var textEl document.getElementB

firefox中input隐藏之后用js获取选择起始和结束位置引起异常(源码分析)

var textEl = document.getElementById("testText");textEl.style.display = "none";try{    var a = textEl.selectionStart;}catch(e){    alert(e);}
?

?

? ? textEl是一个很简单的html的input输入框。但是在设置隐藏之后获取选中的起始和结束位置就会报异常。

异常如下:

?

?

? ? 很明显这是从firefox内核中报出来的异常。

?

? ? 我们来看下获取input选中起始位置的源码,在content\html\content\src\nsHTMLInputElement.cpp文件中:

?

?

nsresultnsHTMLInputElement::GetSelectionRange(PRInt32* aSelectionStart,                                      PRInt32* aSelectionEnd){  nsresult rv = NS_ERROR_FAILURE;  nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);  if (formControlFrame) {    nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);    if (textControlFrame)      rv = textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);  }  return rv;}
?

?

?

? ? ?重要的是nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);这句代码,

在input隐藏的时候,它返回了null。为什么呢?因为传进去的参数是PR_TRUE!!!

ok,我们跟进去看为什么会返回null,经过中间几个小方法的调用,我们看content\html\content\src\nsGenericHTMLElement.cpp中的方法:

?

// staticnsIFormControlFrame*nsGenericHTMLElement::GetFormControlFrameFor(nsIContent* aContent,                                             nsIDocument* aDocument,                                             PRBool aFlushContent){  if (aFlushContent) {    // Cause a flush of the frames, so we get up-to-date frame information    aDocument->FlushPendingNotifications(Flush_Frames);  }  nsIFrame* frame = GetPrimaryFrameFor(aContent, aDocument);  if (frame) {    nsIFormControlFrame* form_frame = do_QueryFrame(frame);    if (form_frame) {      return form_frame;    }    // If we have generated content, the primary frame will be a    // wrapper frame..  out real frame will be in its child list.    for (frame = frame->GetFirstChild(nsnull);         frame;         frame = frame->GetNextSibling()) {      form_frame = do_QueryFrame(frame);      if (form_frame) {        return form_frame;      }    }  }  return nsnull;}
?

?

? ?这个方法里面用到了我们的PR_TRUE参数,是会调用aDocument->FlushPendingNotifications(Flush_Frames);

我们继续跟进,我们会来到layout\base\nsPresShell.cpp中的FlushPendingNotifications方法,在这个方法中,firefox会处理本shell(本iframe)中以前挂起的一些操作(比如说我们设置display为none),其中有一段代码就是处理挂起的样式操作,如下所示:

?

// Process pending restyles, since any flush of the presshell wants    // up-to-date style data.    if (!mIsDestroying) {      mPresContext->FlushPendingMediaFeatureValuesChanged();      // Flush any pending update of the user font set, since that could      // cause style changes (for updating ex/ch units, and to cause a      // reflow).      mPresContext->FlushUserFontSet();      nsAutoScriptBlocker scriptBlocker;      mFrameConstructor->ProcessPendingRestyles();    }
?

?

? ? 在处理pending restyles的时候会进入到mFrameConstructor->ProcessPendingRestyles()中,它会处理一个

mPendingRestyles列表中被添加的所有的pending restyles。 我们设置的display为none会产生一个hint为

nsChangeHint_ReconstructFrame的pending restyles。

firefox会根据这个hint重现构建这个frame(不可见的容器会从nsFrameManager中移除),就是调用的

RecreateFramesForContent这个方法,这个方法里调用ContentRemoved方法,ContentRemoved方法

::DeletingFrameSubtree(frameManager, childFrame)来从nsFrameManager中删除掉这个不可见的元素,所以

在FlushPendingNotifications之后我们就无法取到这个formControlFrame了,所以firefox内核返回了

NS_ERROR_FAILURE,导致了js抛出异常。

?

?

?

?

附1:?设置display为none是怎么加到pending restyles中的

看下调用堆栈

firefox中input隐藏之后用js获取选择起始和结束位置引起错误(源码分析)

?

?

?

voidnsCSSFrameConstructor::PostRestyleEvent(nsIContent* aContent,                                        nsReStyleHint aRestyleHint,                                        nsChangeHint aMinChangeHint){  if (NS_UNLIKELY(mPresShell->IsDestroying())) {    return;  }  if (aRestyleHint == 0 && !aMinChangeHint) {    // Nothing to do here    return;  }  NS_ASSERTION(aContent->IsNodeOfType(nsINode::eELEMENT),               "Shouldn't be trying to restyle non-elements directly");  RestyleData existingData;  existingData.mRestyleHint = nsReStyleHint(0);  existingData.mChangeHint = NS_STYLE_HINT_NONE;  mPendingRestyles.Get(aContent, &existingData);  existingData.mRestyleHint =    nsReStyleHint(existingData.mRestyleHint | aRestyleHint);  NS_UpdateHint(existingData.mChangeHint, aMinChangeHint);  mPendingRestyles.Put(aContent, existingData);  PostRestyleEventInternal();}
?

?

ok,就是在这个方法里面加入进去的。

?

?

附2:为什么得到input的value是没问题的

? ? input设置隐藏之后,即使从nsFrameManager移除掉对应的frame,也可以得到正确的value的值,这是为什么,

我们看下content\html\content\src\nsHTMLInputElement.cpp中对应的方法:

?

NS_IMETHODIMP nsHTMLInputElement::GetValue(nsAString& aValue){  if (mType == NS_FORM_INPUT_TEXT || mType == NS_FORM_INPUT_PASSWORD) {    // No need to flush here, if there's no frame created for this    // input yet, there won't be a value in it (that we don't already    // have) even if we force it to be created    nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE);    PRBool frameOwnsValue = PR_FALSE;    if (formControlFrame) {      nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);      if (textControlFrame) {        textControlFrame->OwnsValue(&frameOwnsValue);      } else {        // We assume if it's not a text control frame that it owns the value        frameOwnsValue = PR_TRUE;      }    }    if (frameOwnsValue) {      formControlFrame->GetFormProperty(nsGkAtoms::value, aValue);    } else {      if (!GET_BOOLBIT(mBitField, BF_VALUE_CHANGED) || !mValue) {        GetDefaultValue(aValue);      } else {        CopyUTF8toUTF16(mValue, aValue);      }    }    return NS_OK;  }  if (mType == NS_FORM_INPUT_FILE) {    if (nsContentUtils::IsCallerTrustedForCapability("UniversalFileRead")) {      if (!mFileNames.IsEmpty()) {        aValue = mFileNames[0];      }      else {        aValue.Truncate();      }    } else {      // Just return the leaf name      nsCOMArray<nsIFile> files;      GetFileArray(files);      if (files.Count() == 0 || NS_FAILED(files[0]->GetLeafName(aValue))) {        aValue.Truncate();      }    }        return NS_OK;  }  // Treat value == defaultValue for other input elements  if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue) &&      (mType == NS_FORM_INPUT_RADIO || mType == NS_FORM_INPUT_CHECKBOX)) {    // The default value of a radio or checkbox input is "on".    aValue.AssignLiteral("on");  }  if (mType != NS_FORM_INPUT_HIDDEN) {    aValue = nsContentUtils::TrimCharsInSet(kWhitespace, aValue);  }  return NS_OK;}
?

?

? ? GetValue方法也会首先去获取frame,但是它传进去的值是PR_FALSE,也就是说如果在设置隐藏后不首先调用

textEl.selectionStart之类的方法的话,此处得到的是正确的frame,那也就是可以得到正确的值。但是如果我们首先调用了textEl.selectionStart的话,此input对应的frame已经被移除,所以得到的frame将是空的,会得到错误的值么?不会!

? ? 其实nsHTMLInputElement使用了一种模型+控件的模式,你在设置值的时候,如果可以得到对应的frame(控件),则值被设置到控件上,如果得不到frame(input设置了隐藏等),则firefox会首先把值保存在模型上,而firefox会保持一些监听器,等控件重现展现的时候把模型的值同步到控件上。而你取值的时候也会保持同样的规则。

?

?

?

附3: 哪些方法在input隐藏后调用会报错

在nsHTMLInputElement使用到GetFormControlFrame(PR_TRUE)的有下面6个方法:

?

?

void

nsHTMLInputElement::SelectAll

?

?

NS_IMETHODIMP

nsHTMLInputElement::SetSelectionRange

?

?

NS_IMETHODIMP

nsHTMLInputElement::SetSelectionStart

?

?

NS_IMETHODIMP

nsHTMLInputElement::SetSelectionEnd

?

?

nsresult

nsHTMLInputElement::GetSelectionRange

?

?

NS_IMETHODIMP

nsHTMLInputElement::GetPhonetic

?

?

? ? ?但是由于SelectAll没有返回值,GetPhonetic即使frame为null也会返回NS_OK。所以在input框隐藏的时候,用js来设置和获取选中的起始和结束位置都会在js里抛出异常,而其他方法都是安全的。

?

? ? ? 估计Mozilla认为在文本框隐藏的时候,选择位置都是没有意义的,不应该在文本框隐藏的时候去涉及选择位置。

?

?

?

?

?

?

?

?

?

?

?

?

热点排行