Bug 263683 - Findbar's Highlight feature should not manipulate the DOM. Patch by Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>, r=me,roc. sr=roc.

This commit is contained in:
Asaf Romano 2008-07-30 23:48:56 +03:00
parent 2a471c8556
commit c9d1d9a951
12 changed files with 385 additions and 121 deletions

View File

@ -51,7 +51,7 @@ interface nsIDOMNode;
interface nsISelection;
interface nsISelectionDisplay;
[scriptable, uuid(5f8f41cb-f182-4010-9e38-7c873e8e8a9f)]
[scriptable, uuid(29a6d846-e100-4933-ad7a-2e19d91eb692)]
interface nsISelectionController : nsISelectionDisplay
{
const short SELECTION_NONE=0;
@ -62,7 +62,8 @@ interface nsISelectionController : nsISelectionDisplay
const short SELECTION_IME_CONVERTEDTEXT=16;
const short SELECTION_IME_SELECTEDCONVERTEDTEXT=32;
const short SELECTION_ACCESSIBILITY=64; // For accessibility API usage
const short NUM_SELECTIONTYPES=8;
const short SELECTION_FIND=128;
const short NUM_SELECTIONTYPES=9;
const short SELECTION_ANCHOR_REGION = 0;
const short SELECTION_FOCUS_REGION = 1;

View File

@ -610,7 +610,9 @@ GetIndexFromSelectionType(SelectionType aType)
case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: return 4; break;
case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: return 5; break;
case nsISelectionController::SELECTION_ACCESSIBILITY: return 6; break;
default:return -1;break;
case nsISelectionController::SELECTION_FIND: return 7; break;
default:
return -1; break;
}
/* NOTREACHED */
return 0;
@ -621,15 +623,16 @@ GetSelectionTypeFromIndex(PRInt8 aIndex)
{
switch (aIndex)
{
case 0: return nsISelectionController::SELECTION_NORMAL;break;
case 1: return nsISelectionController::SELECTION_SPELLCHECK;break;
case 2: return nsISelectionController::SELECTION_IME_RAWINPUT;break;
case 3: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT;break;
case 4: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT;break;
case 5: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT;break;
case 6: return nsISelectionController::SELECTION_ACCESSIBILITY;break;
case 0: return nsISelectionController::SELECTION_NORMAL; break;
case 1: return nsISelectionController::SELECTION_SPELLCHECK; break;
case 2: return nsISelectionController::SELECTION_IME_RAWINPUT; break;
case 3: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; break;
case 4: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; break;
case 5: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; break;
case 6: return nsISelectionController::SELECTION_ACCESSIBILITY; break;
case 7: return nsISelectionController::SELECTION_FIND; break;
default:
return nsISelectionController::SELECTION_NORMAL;break;
return nsISelectionController::SELECTION_NORMAL; break;
}
/* NOTREACHED */
return 0;

View File

@ -237,6 +237,8 @@ public:
*/
PRBool GetSelectionColors(nscolor* aForeColor,
nscolor* aBackColor);
void GetHighlightColors(nscolor* aForeColor,
nscolor* aBackColor);
void GetIMESelectionColors(PRInt32 aIndex,
nscolor* aForeColor,
nscolor* aBackColor);
@ -2835,6 +2837,24 @@ nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
return PR_TRUE;
}
void
nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
nscolor* aBackColor)
{
NS_ASSERTION(aForeColor, "aForeColor is null");
NS_ASSERTION(aBackColor, "aBackColor is null");
nsILookAndFeel* look = mPresContext->LookAndFeel();
nscolor foreColor, backColor;
look->GetColor(nsILookAndFeel::eColor_TextHighlightBackground,
backColor);
look->GetColor(nsILookAndFeel::eColor_TextSelectForeground,
foreColor);
EnsureSufficientContrast(&foreColor, &backColor);
*aForeColor = foreColor;
*aBackColor = backColor;
}
void
nsTextPaintStyle::GetIMESelectionColors(PRInt32 aIndex,
nscolor* aForeColor,
@ -3877,7 +3897,9 @@ static PRBool GetSelectionTextColors(SelectionType aType, nsTextPaintStyle& aTex
switch (aType) {
case nsISelectionController::SELECTION_NORMAL:
return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
case nsISelectionController::SELECTION_FIND:
aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
return PR_TRUE;
case nsISelectionController::SELECTION_IME_RAWINPUT:
aTextPaintStyle.GetIMESelectionColors(nsTextPaintStyle::eIndexRawInput,
aForeground, aBackground);

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Bug 263683 Layout Reftest</title>
</head>
<body style="color: #000000; background-color: #ffffff" onload="onLoad();">
<p>
The last word in this text should be <span style="background-color: #f0e020">selected</span>
</p>
</body>
</html>

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head>
<title>Bug 263683 Layout Reftest</title>
<script type="text/javascript">
var userSet = false;
var userValue = null;
var prefName = "ui.textHighlightBackground";
function onLoad() {
// Request security privileges
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
// Get pref branch.
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
// Check to see if this pref has a user-set value. If so, store it
userSet = prefs.prefHasUserValue(prefName);
if (userSet)
userValue = prefs.getCharPref(prefName);
// Set pref to test colour used in reference file
prefs.setCharPref(prefName, "#f0e020");
var textToSelect = document.getElementById("selecttext");
// Get docshell
var docShell = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShell);
// Get selection controller from docshell
var controller = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsISelectionDisplay)
.QueryInterface(Components.interfaces.nsISelectionController);
// Get selection
var findSelection = controller.getSelection(controller.SELECTION_FIND);
// Lastly add range
var range = document.createRange();
range.selectNodeContents(textToSelect);
findSelection.addRange(range);
document.documentElement.removeAttribute("class");
}
function onUnload() {
// Request security privileges
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
// Get the pref branch
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
prefs.clearUserPref(prefName);
if (!userSet)
return;
prefs.setCharPref(prefName, userValue);
}
</script>
</head>
<body style="color: #000000; background-color: #ffffff" onload="onLoad();" onunload="onUnload();">
<p>
The last word in this text should be <span id="selecttext">selected</span>
</p>
</body>
</html>

View File

@ -169,6 +169,7 @@ fails == 25888-3r.html 25888-3r-ref.html # bug 25888
== 253701-1.html 253701-1-ref.html
== 255820-1.html 255820-1-ref.html
== 262151-1.html 262151-1-ref.html
== 263683-1.html 263683-1-ref.html
== 267353-1.html 267353-1-ref.html
== 273681-1.html 273681-1-ref.html
fails-if(MOZ_WIDGET_TOOLKIT!="cocoa") HTTP == 289480.html#top 289480-ref.html # basically-verbatim acid2 test, HTTP for a 404 page -- bug 409329 for the non-Mac failures

View File

@ -55,6 +55,8 @@ _TEST_FILES = findbar_window.xul \
test_bug366992.xul \
bug331215_window.xul \
test_bug331215.xul \
bug263683_window.xul \
test_bug263683.xul \
test_popup_preventdefault_chrome.xul \
window_popup_preventdefault_chrome.xul \
test_largemenu.xul \

View File

@ -0,0 +1,123 @@
<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is Findbar Test code
-
- The Initial Developer of the Original Code is
- Mozilla Corporation.
- Portions created by the Initial Developer are Copyright (C) 2008
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window id="263683test"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
width="600"
height="600"
onload="onLoad();"
title="263683 test">
<script type="application/javascript"><![CDATA[
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
var gFindBar = null;
var gBrowser;
function ok(condition, message) {
window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
}
function finish() {
window.close();
window.opener.wrappedJSObject.SimpleTest.finish();
}
function onLoad() {
var _delayedOnLoad = function() {
gFindBar = document.getElementById("FindToolbar");
gBrowser = document.getElementById("content");
gBrowser.addEventListener("pageshow", onPageShow, false);
gBrowser.loadURI('data:text/html,<h2>Text mozilla</h2><input id="inp" type="text" />');
}
let tm = Cc["@mozilla.org/thread-manager;1"].
getService(Ci.nsIThreadManager);
tm.mainThread.dispatch({
run: function() _delayedOnLoad()
}, Ci.nsIThread.DISPATCH_NORMAL);
}
function onPageShow() {
gBrowser.removeEventListener("load", onPageShow, true);
gFindBar.open();
var search = "mozilla";
gFindBar._findField.value = search;
var matchCase = gFindBar.getElement("find-case-sensitive");
if (matchCase.checked)
matchCase.doCommand();
gFindBar._find();
var highlightButton = gFindBar.getElement("highlight");
if (!highlightButton.checked)
highlightButton.click();
var controller = gFindBar.browser.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
ok('SELECTION_FIND' in controller, "Correctly detects new selection type");
var selection = controller.getSelection(controller.SELECTION_FIND);
ok(selection.rangeCount == 1, "Correctly added a match to the selection type");
ok(selection.getRangeAt(0).toString().toLowerCase() == search, "Added the correct match");
highlightButton.click();
ok(selection.rangeCount == 0, "Correctly removed the range");
var input = gBrowser.contentDocument.getElementById("inp");
input.value = search;
highlightButton.click();
var inputController = input.editor.selectionController;
var inputSelection = inputController.getSelection(inputController.SELECTION_FIND);
ok(inputSelection.rangeCount == 1, "Correctly added a match from input to the selection type");
ok(inputSelection.getRangeAt(0).toString().toLowerCase() == search, "Added the correct match");
highlightButton.click();
ok(inputSelection.rangeCount == 0, "Correctly removed the range");
finish();
}
]]></script>
<browser type="content-primary" flex="1" id="content" src="about:blank"/>
<findbar id="FindToolbar" browserid="content"/>
</window>

View File

@ -0,0 +1,43 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet
href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=263683
-->
<window title="Mozilla Bug 263683"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<title>Test for Bug 263683</title>
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=263683">
Mozilla Bug 263683
</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
/** Test for Bug 263683 **/
SimpleTest.waitForExplicitFinish();
window.open("bug263683_window.xul", "263683test",
"chrome,width=600,height=600");
]]>
</script>
</window>

View File

@ -27,6 +27,7 @@
- Jason Barnabe <jason_barnabe@fastmail.fm>
- Asaf Romano <mano@mozilla.com>
- Ehsan Akhgari <ehsan.akhgari@gmail.com>
- Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
@ -430,10 +431,38 @@
]]></body>
</method>
<!--
- Gets the selection controller for the current browser
-->
<method name="_getSelectionController">
<body><![CDATA[
// Yuck. See bug 138068.
var Ci = Components.interfaces;
var controller = this.browser.docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
return controller;
]]></body>
</method>
<method name="_getEditableNode">
<parameter name="aNode"/>
<body><![CDATA[
while (aNode) {
if (aNode instanceof Components.interfaces.nsIDOMNSEditableElement) {
return aNode.editor ? aNode : null;
}
aNode = aNode.parentNode;
}
return null;
]]></body>
</method>
<!--
- Turns highlight on or off.
- @param aHighlight (boolean)
- Whether to turn on highlight
- Whether to turn the highlight on or off
-->
<method name="toggleHighlight">
<parameter name="aHighlight"/>
@ -442,14 +471,13 @@
if (aHighlight) {
// We have to update the status because we might still have the status
// of another tab
if (this._highlightDoc("yellow", "black", word))
if (this._highlightDoc(aHighlight, word))
this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
else
this._updateStatusUI(this.nsITypeAheadFind.FIND_NOTFOUND);
}
else {
this._highlightDoc(null, null, null);
this._lastHighlightString = null;
this._highlightDoc(aHighlight, this._lastHighlightString, null);
}
]]></body>
</method>
@ -457,11 +485,8 @@
<!--
- (Un)highlights each instance of the searched word in the passed
- window's content.
- @param aHighBackColor
- the background color for the highlight
- @param aHighTextColor
- the text color for the highlight, or null to make it
- unhighlight
- @param aHighlight (boolean)
- Whether to turn on highlight
- @param aWord
- the word to search for
- @param aWindow
@ -470,16 +495,19 @@
- @returns true if aWord was found
-->
<method name="_highlightDoc">
<parameter name="aHighBackColor"/>
<parameter name="aHighTextColor"/>
<parameter name="aHighlight"/>
<parameter name="aWord"/>
<parameter name="aWindow"/>
<body><![CDATA[
var win = aWindow || this.browser.contentWindow;
if (!aWord)
return false;
var textFound = false;
for (var i = 0; win.frames && i < win.frames.length; i++) {
if (this._highlightDoc(aHighBackColor, aHighTextColor, aWord, win.frames[i]))
if (this._highlightDoc(aHighlight, aWord, win.frames[i]))
textFound = true;
}
@ -502,85 +530,22 @@
this._endPt.setStart(body, count);
this._endPt.setEnd(body, count);
if (!aHighBackColor) {
// Remove highlighting. We use the find API again rather than
// searching for our span elements so that we gain access to the
// anonymous content that nsIFind searches.
if (!this._lastHighlightString)
return textFound;
var retRange = null;
var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"]
.createInstance(Components.interfaces.nsIFind);
while ((retRange = finder.Find(this._lastHighlightString,
this._searchRange, this._startPt,
this._endPt))) {
var startContainer = retRange.startContainer;
var elem = null;
try {
elem = startContainer.parentNode;
}
catch (ex) { }
if (elem && elem.className == "__mozilla-findbar-search") {
var child = null;
var docfrag = doc.createDocumentFragment();
var next = elem.nextSibling;
var parent = elem.parentNode;
while ((child = elem.firstChild)) {
docfrag.appendChild(child);
}
this._startPt = doc.createRange();
this._startPt.setStartAfter(elem);
parent.removeChild(elem);
parent.insertBefore(docfrag, next);
parent.normalize();
}
else {
// Somehow we didn't highlight this instance; just skip it.
this._startPt = doc.createRange();
this._startPt.setStart(retRange.endContainer,
retRange.endOffset);
}
this._startPt.collapse(true);
textFound = true;
}
return textFound;
var controller = this._getSelectionController();
if (!controller) {
// Without the selection controller,
// we are unable to (un)highlight any matches
return false;
}
var baseNode = doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
baseNode.style.backgroundColor = aHighBackColor;
baseNode.style.color = aHighTextColor;
baseNode.style.display = "inline";
baseNode.style.fontSize = "inherit";
baseNode.style.padding = "0";
baseNode.className = "__mozilla-findbar-search";
// This code should look for individual matches and highlight them
// However, as there's no way to access editable elements individual
// selection controllers from the top level doc, we also have to use
// this to find all the editable matches when clearing the highlight,
// which is horribly inefficient, but still faster than the DOM
// manipulation of old.
// Fixing bug 339400 would make this go away, and allow the removal case
// to be reduced to something like findSelection.removeAllRanges()
return this._highlightText(aWord, baseNode) || textFound;
]]></body>
</method>
<!--
- Highlights each instance of the searched word in the current range.
-
- @param aWord
- the word to search for.
- @param aBaseNode
- a node to use as a template for what will replace the searched
- word.
- @returns true if aWord was found
-->
<method name="_highlightText">
<parameter name="aWord"/>
<parameter name="aBaseNode"/>
<body><![CDATA[
var retRange = null;
var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"]
.createInstance()
@ -588,20 +553,26 @@
finder.caseSensitive = this._shouldBeCaseSensitive(aWord);
var textFound = false;
while((retRange = finder.Find(aWord, this._searchRange,
this._startPt, this._endPt))) {
// Highlight
var nodeSurround = aBaseNode.cloneNode(true);
var node = this._highlight(retRange, nodeSurround);
this._startPt = node.ownerDocument.createRange();
this._startPt.setStart(node, node.childNodes.length);
this._startPt.setEnd(node, node.childNodes.length);
this._highlight(aHighlight, retRange, controller);
this._startPt = retRange.endContainer.ownerDocument.createRange();
this._startPt.setStart(retRange.endContainer, retRange.endOffset);
this._startPt.setEnd(retRange.endContainer, retRange.endOffset);
textFound = true;
}
this._lastHighlightString = aWord;
if (textFound) {
if (aHighlight)
this._lastHighlightString = aWord;
else {
var sel = controller.getSelection(this.nsISelectionController.SELECTION_FIND);
sel.removeAllRanges();
}
controller.setDisplaySelection(this.nsISelectionController.SELECTION_ON);
controller.repaintSelection(this.nsISelectionController.SELECTION_FIND);
}
return textFound;
]]></body>
@ -610,25 +581,31 @@
<!--
- Highlights the word in the passed range.
-
- @param aHighlight
- whether the highlight should be on or off
- @param aRange
- the range that contains the word to highlight
- @param aNode
- the node replace the searched word with
- @returns the node that replaced the searched word
- @param aController
- the current document's selection controller
-->
<method name="_highlight">
<parameter name="aHighlight"/>
<parameter name="aRange"/>
<parameter name="aNode"/>
<parameter name="aController"/>
<body><![CDATA[
var startContainer = aRange.startContainer;
var startOffset = aRange.startOffset;
var endOffset = aRange.endOffset;
var docfrag = aRange.extractContents();
var before = startContainer.splitText(startOffset);
var parent = before.parentNode;
aNode.appendChild(docfrag);
parent.insertBefore(aNode, before);
return aNode;
var node = aRange.startContainer;
var controller = aController;
var isEditable = this._getEditableNode(node);
if (isEditable)
controller = isEditable.editor.selectionController;
var findSelection = controller.getSelection(this.nsISelectionController.SELECTION_FIND);
if (aHighlight)
findSelection.addRange(aRange);
if (isEditable) {
if (!aHighlight)
findSelection.removeAllRanges();
controller.repaintSelection(this.nsISelectionController.SELECTION_FIND);
}
]]></body>
</method>

View File

@ -75,6 +75,7 @@ public:
eColor_TextSelectForeground,
eColor_TextSelectBackgroundDisabled,
eColor_TextSelectBackgroundAttention,
eColor_TextHighlightBackground,
eColor_IMERawInputBackground,
eColor_IMERawInputForeground,

View File

@ -170,6 +170,7 @@ const char nsXPLookAndFeel::sColorPrefs[][38] =
"ui.textSelectForeground",
"ui.textSelectBackgroundDisabled",
"ui.textSelectBackgroundAttention",
"ui.textHighlightBackground",
"ui.IMERawInputBackground",
"ui.IMERawInputForeground",
"ui.IMERawInputUnderline",
@ -585,6 +586,13 @@ nsXPLookAndFeel::GetColor(const nsColorID aID, nscolor &aColor)
return NS_OK;
}
if (aID == eColor_TextHighlightBackground) {
// This makes the matched text stand out when findbar highlighting is on
// Used with nsISelectionController::SELECTION_FIND
aColor = NS_RGB(0xf0, 0xe0, 0x20);
return NS_OK;
}
if (NS_SUCCEEDED(NativeGetColor(aID, aColor))) {
if (gfxPlatform::IsCMSEnabled() && !IsSpecialColor(aID, aColor)) {
cmsHTRANSFORM transform = gfxPlatform::GetCMSInverseRGBTransform();