Bug 541817 - Fennec needs find in page functionality [r=mfinkle]

This commit is contained in:
Vivien Nicolas 2010-07-19 16:51:03 +02:00
parent ff2b05499a
commit e557411e0c
10 changed files with 312 additions and 60 deletions

View File

@ -142,6 +142,9 @@ pref("formhelper.enabled", true);
pref("formhelper.autozoom", true);
pref("formhelper.restore", false);
/* find helper */
pref("findhelper.autozoom", true);
/* autocomplete */
pref("browser.formfill.enable", true);

View File

@ -1077,6 +1077,85 @@
</handlers>
</binding>
<binding id="content-navigator">
<content pack="end">
<children includes="arrowscrollbox|vbox"/>
<xul:hbox class="panel-dark" pack="center">
<children includes="textbox"/>
<xul:button anonid="previous-button" class="button-dark" label="&contentNavigator.previous;" xbl:inherits="command=previous"/>
<xul:button anonid="next-button" class="button-dark" label="&contentNavigator.next;" xbl:inherits="command=next"/>
<xul:spacer flex="1"/>
<xul:toolbarbutton class="close-button" xbl:inherits="command=close"/>
</xul:hbox>
</content>
<implementation>
<field name="_previousButton">
document.getAnonymousElementByAttribute(this, "anonid", "previous-button");
</field>
<field name="_nextButton">
document.getAnonymousElementByAttribute(this, "anonid", "next-button");
</field>
<field name="_spacer">
document.getElementById(this.getAttribute("spacer"));
</field>
<method name="contentHasChanged">
<body><![CDATA[
let height = Math.floor(this.getBoundingClientRect().height);
this.top = window.innerHeight - height;
this._spacer.setAttribute("height", height);
]]></body>
</method>
<field name="_model">null</field>
<method name="show">
<parameter name="aModel" />
<body><![CDATA[
// call the hide callback of the current object if any
if (this._model && this._model.type != aModel.type)
this._model.hide();
this.setAttribute("type", aModel.type);
this.setAttribute("next", aModel.commands.next);
this.setAttribute("previous", aModel.commands.previous);
this.setAttribute("close", aModel.commands.close);
// buttons attributes sync with commands doesn not look updated when
// we dynamically switch the "command" attribute so we need to ensure
// the disabled state of the buttons is right when switching commands
this._previousButton.disabled = document.getElementById(aModel.commands.previous).disabled;
this._nextButton.disabled = document.getElementById(aModel.commands.next).disabled;
this._model = aModel;
this.contentHasChanged();
this._spacer.hidden = false;
]]></body>
</method>
<method name="hide">
<parameter name="aModel" />
<body><![CDATA[
this.removeAttribute("next");
this.removeAttribute("previous");
this.removeAttribute("close");
this.removeAttribute("type");
this._model = null;
this._spacer.hidden = false;
// give the form spacer area back to the content
// XXX this should probably be removed with layers
Browser.forceChromeReflow();
Browser.contentScrollboxScroller.scrollBy(0, 0);
Browser._browserView.onAfterVisibleMove();
]]></body>
</method>
</implementation>
</binding>
<binding id="menulist" display="xul:box" extends="chrome://global/content/bindings/menulist.xml#menulist">
<handlers>
<handler event="mousedown" phase="capturing">

View File

@ -339,9 +339,9 @@ var BrowserUI = {
popup.height = windowH - this.toolbarH;
popup.width = windowW;
// form helper
let formHelper = document.getElementById("form-helper-container");
formHelper.top = windowH - formHelper.getBoundingClientRect().height;
// content navigator helper
let contentHelper = document.getElementById("content-navigator");
contentHelper.top = windowH - contentHelper.getBoundingClientRect().height;
},
init: function() {
@ -399,6 +399,7 @@ var BrowserUI = {
});
FormHelperUI.init();
FindHelperUI.init();
},
uninit: function() {
@ -1431,6 +1432,92 @@ var BookmarkList = {
}
};
var FindHelperUI = {
type: "find",
commands: {
next: "cmd_findNext",
previous: "cmd_findPrevious",
close: "cmd_findClose"
},
init: function findHelperInit() {
this._textbox = document.getElementById("find-helper-textbox");
this._container = document.getElementById("content-navigator");
// Listen for form assistant messages from content
messageManager.addMessageListener("FindAssist:Show", this);
// Listen for events where form assistant should be closed
document.getElementById("tabs").addEventListener("TabSelect", this, true);
document.getElementById("browsers").addEventListener("URLChanged", this, true);
},
receiveMessage: function findHelperReceiveMessage(aMessage) {
let json = aMessage.json;
switch(aMessage.name) {
case "FindAssist:Show":
if (json.rect)
this._zoom(Rect.fromRect(json.rect));
break;
}
},
handleEvent: function findHelperHandleEvent(aEvent) {
if (aEvent.type == "TabSelect" || aEvent.type == "URLChanged")
this.hide();
},
show: function findHelperShow() {
Browser._browserView.ignorePageScroll(true);
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Find", { searchString: "" });
this._container.show(this);
this._textbox.focus();
},
hide: function findHelperHide() {
Browser._browserView.ignorePageScroll(false);
this._textbox.value = "";
this._container.hide(this);
},
goToPrevious: function findHelperGoToPrevious() {
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Previous", { });
},
goToNext: function findHelperGoToNext() {
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Next", { });
},
search: function findHelperSearch(aValue) {
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Find", { searchString: aValue });
},
updateFindInPage: function findHelperUpdateFindInPage() {
PageActions.removeItems("findinpage");
let title = Elements.browserBundle.getString("pageactions.findInPage");
let node = PageActions.appendItem("findinpage", title, "");
node.onclick = function(event) {
BrowserUI._hidePopup();
FindHelperUI.show();
}
},
_zoom: function _findHelperZoom(aElementRect) {
let bv = Browser._browserView;
let zoomRect = bv.getVisibleRect();
// Zoom to a specified Rect
if (aElementRect && bv.allowZoom && Services.prefs.getBoolPref("findhelper.autozoom")) {
let zoomLevel = Browser._getZoomLevelForRect(bv.browserToViewportRect(aElementRect.clone()));
zoomLevel = Math.min(Math.max(kBrowserFormZoomLevelMin, zoomLevel), kBrowserFormZoomLevelMax);
zoomRect = Browser._getZoomRectForPoint(aElementRect.center().x, aElementRect.y, zoomLevel);
Browser.animatedZoomTo(zoomRect);
}
}
};
/**
* Responsible for navigating forms and filling in information.
* - Navigating forms is handled by next and previous commands.
@ -1440,12 +1527,18 @@ var BookmarkList = {
* - Provides autocomplete box for input fields.
*/
var FormHelperUI = {
type: "form",
commands: {
next: "cmd_formNext",
previous: "cmd_formPrevious",
close: "cmd_formClose"
},
init: function formHelperInit() {
this._container = document.getElementById("form-helper-container");
this._cmdPrevious = document.getElementById("cmd_formPrevious");
this._cmdNext = document.getElementById("cmd_formNext");
this._helperSpacer = document.getElementById("form-helper-spacer");
this._container = document.getElementById("content-navigator");
this._autofillContainer = document.getElementById("form-helper-autofill");
this._cmdPrevious = document.getElementById(this.commands.previous);
this._cmdNext = document.getElementById(this.commands.next);
// Listen for form assistant messages from content
messageManager.addMessageListener("FormAssist:Show", this);
@ -1485,7 +1578,7 @@ var FormHelperUI = {
this._open = false;
},
handleEvent: function(aEvent) {
handleEvent: function formHelperHandleEvent(aEvent) {
if (aEvent.type == "TabSelect" || aEvent.type == "URLChanged")
this.hide();
},
@ -1508,7 +1601,7 @@ var FormHelperUI = {
case "FormAssist:AutoComplete":
this._updateAutocompleteFor(json.current);
this._updateHelperSize();
this._container.contentHasChanged();
break;
case "FormAssist:Update":
@ -1532,7 +1625,7 @@ var FormHelperUI = {
},
get _open() {
return !this._container.hidden;
return (this._container.getAttribute("type") == this.type);
},
set _open(aVal) {
@ -1542,20 +1635,14 @@ var FormHelperUI = {
let bv = Browser._browserView;
bv.ignorePageScroll(aVal);
this._container.hidden = !aVal;
this._helperSpacer.hidden = !aVal;
if (aVal) {
this._zoomStart();
this._container.show(this);
} else {
this._currentElement = null;
this._zoomFinish();
// give the form spacer area back to the content
// XXX this should probably be removed with layers
let bv = Browser._browserView;
Browser.forceChromeReflow();
Browser.contentScrollboxScroller.scrollBy(0, 0);
bv.onAfterVisibleMove();
this._currentElement = null;
this._container.hide(this);
}
let evt = document.createEvent("UIEvents");
@ -1609,15 +1696,7 @@ var FormHelperUI = {
// Setup autofill UI
this._updateAutocompleteFor(aCurrentElement);
this._updateHelperSize();
},
_updateHelperSize: function _formHelperUpdateHelperSize() {
let height = Math.floor(this._container.getBoundingClientRect().height);
this._container.top = window.innerHeight - height;
let containerHeight = this._container.getBoundingClientRect().height;
this._helperSpacer.setAttribute("height", containerHeight);
this._container.contentHasChanged();
},
/** Helper for _updateContainer that handles the case where the new element is a select. */
@ -1637,7 +1716,7 @@ var FormHelperUI = {
},
/** Zoom and move viewport so that element is legible and touchable. */
_zoom: function formHelperZoom(aElementRect, aCaretRect) {
_zoom: function _formHelperZoom(aElementRect, aCaretRect) {
let bv = Browser._browserView;
let zoomRect = bv.getVisibleRect();
@ -1681,7 +1760,7 @@ var FormHelperUI = {
/** Element is no longer selected. Restore zoom level if setting is enabled. */
_zoomFinish: function _formHelperZoomFinish() {
if(!gPrefService.getBoolPref("formhelper.restore"))
if(!Services.prefs.getBoolPref("formhelper.restore"))
return;
let restore = this._restore;
@ -1690,7 +1769,7 @@ var FormHelperUI = {
Browser.pageScrollboxScroller.scrollTo(restore.pageScrollOffset.x, restore.pageScrollOffset.y);
},
_getOffsetForCaret: function formHelper_getOffsetForCaret(aCaretRect, aRect) {
_getOffsetForCaret: function _formHelperGetOffsetForCaret(aCaretRect, aRect) {
// Determine if we need to move left or right to bring the caret into view
let deltaX = 0;
if (aCaretRect.right > aRect.right)
@ -2008,10 +2087,10 @@ var ContextHelper = {
BrowserUI.pushPopup(this, [container]);
},
hide: function ch_hide() {
this.popupState = null;
let container = document.getElementById("context-popup");
container.hidden = true;

View File

@ -10,6 +10,10 @@ browser[remote="true"] {
-moz-binding: url("chrome://browser/content/bindings.xml#autocomplete-aligned");
}
#content-navigator {
-moz-binding: url("chrome://browser/content/bindings.xml#content-navigator");
}
#tabs {
-moz-binding: url("chrome://browser/content/tabs.xml#tablist");
}

View File

@ -1831,6 +1831,9 @@ IdentityHandler.prototype = {
this._identityPopupContentSupp.textContent = supplemental;
this._identityPopupContentVerif.textContent = verifier;
// Update the find in page in-site menu
FindHelperUI.updateFindInPage();
// Update the search engines results
BrowserSearch.updatePageSearchEngines();

View File

@ -151,6 +151,11 @@
<command id="cmd_formPrevious" oncommand="FormHelperUI.goToPrevious();"/>
<command id="cmd_formNext" oncommand="FormHelperUI.goToNext();"/>
<command id="cmd_formClose" oncommand="FormHelperUI.hide();"/>
<!-- find navigation -->
<command id="cmd_findPrevious" oncommand="FindHelperUI.goToPrevious();"/>
<command id="cmd_findNext" oncommand="FindHelperUI.goToNext();"/>
<command id="cmd_findClose" oncommand="FindHelperUI.hide();"/>
</commandset>
<keyset id="mainKeyset">
@ -263,22 +268,16 @@
<html:canvas id="view-buffer" style="display: none;" moz-opaque="true">
</html:canvas>
</stack>
<box id="form-helper-spacer" hidden="true"/>
<box id="content-navigator-spacer" hidden="true"/>
</vbox>
</vbox>
</scrollbox>
<!-- popup for form helper -->
<vbox id="form-helper-container" hidden="true" class="window-width" top="0" pack="end">
<!-- popup for content navigator helper -->
<vbox id="content-navigator" class="window-width" top="0" spacer="content-navigator-spacer">
<arrowscrollbox id="form-helper-autofill" collapsed="true" align="center" flex="1" orient="horizontal"
onclick="FormHelperUI.doAutoComplete(event.target);"/>
<hbox id="form-buttons" class="panel-dark" pack="center">
<button id="form-helper-previous" class="button-dark" label="&formHelper.previous;" command="cmd_formPrevious"/>
<button id="form-helper-next" class="button-dark" label="&formHelper.next;" command="cmd_formNext"/>
<spacer flex="1"/>
<toolbarbutton id="form-helper-close" class="close-button" command="cmd_formClose"/>
</hbox>
<textbox id="find-helper-textbox" oncommand="FindHelperUI.search(this.value)" type="search" flex="1"/>
</vbox>
</stack>
@ -286,7 +285,7 @@
<vbox class="panel-dark">
<!-- Because of the stack + fixed position of the urlbar when it is in
locked mode the event on the top-right part of the urlbar are
swallow by this spacer, but not with the mousethrough attribute
swallow by this spacer, but not with the mousethrough attribute
-->
<spacer class="toolbar-height" mousethrough="always"/>
@ -492,7 +491,7 @@
<vbox id="select-container-inner" class="dialog-dark" flex="1">
<scrollbox id="select-list" flex="1" orient="vertical"/>
<hbox id="select-buttons" pack="center">
<button id="select-buttons-done" class="button-dark" label="&formHelper.done;" oncommand="SelectHelperUI.hide();"/>
<button id="select-buttons-done" class="button-dark" label="&selectHelper.done;" oncommand="SelectHelperUI.hide();"/>
</hbox>
</vbox>
<spacer flex="1000"/>

View File

@ -831,3 +831,70 @@ var FormSubmitObserver = {
};
FormSubmitObserver.init();
var FindHandler = {
get _fastFind() {
delete this._fastFind;
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
this._fastFind.init(docShell);
return this._fastFind;
},
get _selectionController() {
delete this._selectionController;
return this._selectionController = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
},
init: function findHandlerInit() {
addMessageListener("FindAssist:Find", this);
addMessageListener("FindAssist:Next", this);
addMessageListener("FindAssist:Previous", this);
},
receiveMessage: function findHandlerReceiveMessage(aMessage) {
let findResult = Ci.nsITypeAheadFind.FIND_NOTFOUND;
let json = aMessage.json;
switch (aMessage.name) {
case "FindAssist:Find":
findResult = this._fastFind.find(json.searchString, false);
break;
case "FindAssist:Previous":
findResult = this._fastFind.findAgain(true, false);
break;
case "FindAssist:Next":
findResult = this._fastFind.findAgain(false, false);
break;
}
if (findResult == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
sendAsyncMessage("FindAssist:Show", { rect: null , result: findResult });
return;
}
let controller = this._selectionController.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
if (!controller.rangeCount) {
// The selection can be into an input or a textarea element
let nodes = content.document.querySelectorAll("input[type='text'], textarea");
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (node instanceof Ci.nsIDOMNSEditableElement && node.editor) {
controller = node.editor.selectionController.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
if (controller.rangeCount)
break;
}
}
}
let range = controller.getRangeAt(0);
let scroll = Util.getScrollOffset(content);
let rect = range.getBoundingClientRect();
rect = new Rect(scroll.x + rect.left, scroll.y + rect.top, rect.width, rect.height);
sendAsyncMessage("FindAssist:Show", { rect: rect.isEmpty() ? null: rect , result: findResult });
}
};
FindHandler.init();

View File

@ -27,9 +27,10 @@
<!ENTITY editBookmarkDone.label "Done">
<!ENTITY editBookmarkTags.label "Add tags here">
<!ENTITY formHelper.previous "Previous">
<!ENTITY formHelper.next "Next">
<!ENTITY formHelper.done "Done">
<!ENTITY contentNavigator.previous "Previous">
<!ENTITY contentNavigator.next "Next">
<!ENTITY selectHelper.done "Done">
<!ENTITY addonsHeader.label "Add-ons">
<!ENTITY addonsLocal.label "Your Add-ons">

View File

@ -149,6 +149,7 @@ pageactions.geo=Location
pageactions.popup=Popups
pageactions.offline-app=Offline Storage
pageactions.password=Password
pageactions.findInPage=Find
# Helper App Dialog (Save/Open)
helperApp.title=Opening File

View File

@ -206,9 +206,11 @@ toolbarbutton.urlbar-cap-button {
border-bottom: 3px solid #262629 !important;
-moz-border-radius: 0;
}
#urlbar-edit > hbox > .textbox-input-box {
margin: 0;
}
#urlbar-edit > hbox > hbox > .textbox-input {
min-height: 60px;
text-indent: 3px;
@ -1096,8 +1098,28 @@ pageaction .pageaction-desc[value=""] {
font-size: 18px !important;
}
/* form popup -------------------------------------------------------------- */
#form-helper-container > #select-container > #select-container-inner {
/* navigator popup -------------------------------------------------------------- */
#content-navigator,
#content-navigator #select-buttons {
display: none;
}
#content-navigator[type="find"],
#content-navigator[type="form"] {
display: -moz-box;
}
#content-navigator:not([type="form"]) > #form-helper-autofill {
visibility: collapse;
}
#content-navigator:not([type="form"]) > #select-container,
#content-navigator:not([type="find"]) > #find-helper-textbox {
display: none;
}
#content-navigator > #select-container > #select-container-inner {
border-width: 0;
-moz-border-radius-topleft: 8px;
-moz-border-radius-topright: 8px;
-moz-border-radius-bottomright: 0;
@ -1106,26 +1128,20 @@ pageaction .pageaction-desc[value=""] {
-moz-box-flex: 0;
}
#form-helper-container > #select-container > #select-container-inner > scrollbox {
#content-navigator > #select-container > #select-container-inner > scrollbox {
min-height: 70px;
}
#form-helper-container > #select-container > spacer {
#content-navigator > #select-container > spacer {
display: none;
}
#form-helper-container > #select-container > #select-container-inner,
#form-buttons {
border: 1px solid gray;
border-bottom: 0;
}
#form-buttons,
#content-navigator > hbox,
#select-buttons {
padding: 4px 8px; /* row size & core spacing */
}
#form-buttons > button,
#content-navigator > hbox > button,
#select-buttons > button {
-moz-user-focus: ignore;
-moz-user-select: none;