diff --git a/dom/browser-element/BrowserElementChildPreload.js b/dom/browser-element/BrowserElementChildPreload.js index 1b25df49b126..bec221857c5d 100644 --- a/dom/browser-element/BrowserElementChildPreload.js +++ b/dom/browser-element/BrowserElementChildPreload.js @@ -229,7 +229,10 @@ BrowserElementChild.prototype = { "activate-next-paint-listener": this._activateNextPaintListener.bind(this), "set-input-method-active": this._recvSetInputMethodActive.bind(this), "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this), - "do-command": this._recvDoCommand + "do-command": this._recvDoCommand, + "find-all": this._recvFindAll.bind(this), + "find-next": this._recvFindNext.bind(this), + "clear-match": this._recvClearMatch.bind(this), } addMessageListener("browser-element-api:call", function(aMessage) { @@ -1240,6 +1243,57 @@ BrowserElementChild.prototype = { } }, + _initFinder: function() { + if (!this._finder) { + try { + this._findLimit = Services.prefs.getIntPref("accessibility.typeaheadfind.matchesCountLimit"); + } catch (e) { + // Pref not available, assume 0, no match counting. + this._findLimit = 0; + } + + let {Finder} = Components.utils.import("resource://gre/modules/Finder.jsm", {}); + this._finder = new Finder(docShell); + this._finder.addResultListener({ + onMatchesCountResult: (data) => { + sendAsyncMsg('findchange', { + active: true, + searchString: this._finder.searchString, + searchLimit: this._findLimit, + activeMatchOrdinal: data.current, + numberOfMatches: data.total + }); + } + }); + } + }, + + _recvFindAll: function(data) { + this._initFinder(); + let searchString = data.json.searchString; + this._finder.caseSensitive = data.json.caseSensitive; + this._finder.fastFind(searchString, false, false); + this._finder.requestMatchesCount(searchString, this._findLimit, false); + }, + + _recvFindNext: function(data) { + if (!this._finder) { + debug("findNext() called before findAll()"); + return; + } + this._finder.findAgain(data.json.backward, false, false); + this._finder.requestMatchesCount(this._finder.searchString, this._findLimit, false); + }, + + _recvClearMatch: function(data) { + if (!this._finder) { + debug("clearMach() called before findAll()"); + return; + } + this._finder.removeSelection(); + sendAsyncMsg('findchange', {active: false}); + }, + _recvSetInputMethodActive: function(data) { let msgData = { id: data.json.id }; if (!this._isContentWindowCreated) { diff --git a/dom/browser-element/BrowserElementParent.js b/dom/browser-element/BrowserElementParent.js index 9e80ad887a41..b84d531760b9 100644 --- a/dom/browser-element/BrowserElementParent.js +++ b/dom/browser-element/BrowserElementParent.js @@ -206,6 +206,7 @@ BrowserElementParent.prototype = { "selectionstatechanged": this._handleSelectionStateChanged, "scrollviewchange": this._handleScrollViewChange, "caretstatechanged": this._handleCaretStateChanged, + "findchange": this._handleFindChange }; let mmSecuritySensitiveCalls = { @@ -475,6 +476,12 @@ BrowserElementParent.prototype = { this._frameElement.dispatchEvent(evt); }, + _handleFindChange: function(data) { + let evt = this._createEvent("findchange", data.json, + /* cancelable = */ false); + this._frameElement.dispatchEvent(evt); + }, + _createEvent: function(evtName, detail, cancelable) { // This will have to change if we ever want to send a CustomEvent with null // detail. For now, it's OK. @@ -646,6 +653,23 @@ BrowserElementParent.prototype = { getCanGoForward: defineDOMRequestMethod('get-can-go-forward'), getContentDimensions: defineDOMRequestMethod('get-contentdimensions'), + findAll: defineNoReturnMethod(function(searchString, caseSensitivity) { + return this._sendAsyncMsg('find-all', { + searchString, + caseSensitive: caseSensitivity == Ci.nsIBrowserElementAPI.FIND_CASE_SENSITIVE + }); + }), + + findNext: defineNoReturnMethod(function(direction) { + return this._sendAsyncMsg('find-next', { + backward: direction == Ci.nsIBrowserElementAPI.FIND_BACKWARD + }); + }), + + clearMatch: defineNoReturnMethod(function() { + return this._sendAsyncMsg('clear-match'); + }), + goBack: defineNoReturnMethod(function() { this._sendAsyncMsg('go-back'); }), diff --git a/dom/browser-element/mochitest/browserElement_Find.js b/dom/browser-element/mochitest/browserElement_Find.js new file mode 100644 index 000000000000..2ab378ff1884 --- /dev/null +++ b/dom/browser-element/mochitest/browserElement_Find.js @@ -0,0 +1,146 @@ +/* Any copyright is dedicated to the public domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Bug 1163961 - Test search API + +"use strict"; + +SimpleTest.waitForExplicitFinish(); +browserElementTestHelpers.setEnabledPref(true); +browserElementTestHelpers.addPermission(); + +function runTest() { + + let iframe = document.createElement('iframe'); + iframe.setAttribute('mozbrowser', 'true'); + iframe.src = 'data:text/html,foo bar foo XXX Foo BAR foobar foobar'; + + const once = (eventName) => { + return new Promise((resolve) => { + iframe.addEventListener(eventName, function onEvent(...args) { + iframe.removeEventListener(eventName, onEvent); + resolve(...args); + }); + }); + } + + // Test if all key=>value pairs in o1 are present in o2. + const c = (o1, o2) => Object.keys(o1).every(k => o1[k] == o2[k]); + + let testCount = 0; + + once('mozbrowserloadend').then(() => { + iframe.findAll('foo', 'case-insensitive'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'foo', + searchLimit: 100, + activeMatchOrdinal: 1, + numberOfMatches: 5, + }), `test ${testCount++}`); + iframe.findNext('forward'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'foo', + searchLimit: 100, + activeMatchOrdinal: 2, + numberOfMatches: 5, + }), `test ${testCount++}`); + iframe.findNext('backward'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'foo', + searchLimit: 100, + activeMatchOrdinal: 1, + numberOfMatches: 5, + }), `test ${testCount++}`); + iframe.findAll('xxx', 'case-sensitive'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'xxx', + searchLimit: 100, + activeMatchOrdinal: 0, + numberOfMatches: 0, + }), `test ${testCount++}`); + iframe.findAll('bar', 'case-insensitive'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'bar', + searchLimit: 100, + activeMatchOrdinal: 1, + numberOfMatches: 4, + }), `test ${testCount++}`); + iframe.findNext('forward'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'bar', + searchLimit: 100, + activeMatchOrdinal: 2, + numberOfMatches: 4, + }), `test ${testCount++}`); + iframe.findNext('forward'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'bar', + searchLimit: 100, + activeMatchOrdinal: 3, + numberOfMatches: 4, + }), `test ${testCount++}`); + iframe.findNext('forward'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'bar', + searchLimit: 100, + activeMatchOrdinal: 4, + numberOfMatches: 4, + }), `test ${testCount++}`); + iframe.findNext('forward'); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: true, + searchString: 'bar', + searchLimit: 100, + activeMatchOrdinal: 1, + numberOfMatches: 4, + }), `test ${testCount++}`); + iframe.clearMatch(); + return once('mozbrowserfindchange'); + }).then(({detail}) => { + ok(c(detail, { + msg_name: "findchange", + active: false + }), `test ${testCount++}`); + SimpleTest.finish(); + }); + + document.body.appendChild(iframe); + +} + +addEventListener('testready', runTest); diff --git a/dom/browser-element/mochitest/mochitest-oop.ini b/dom/browser-element/mochitest/mochitest-oop.ini index e0ef5136fc16..74d7cebf0ba8 100644 --- a/dom/browser-element/mochitest/mochitest-oop.ini +++ b/dom/browser-element/mochitest/mochitest-oop.ini @@ -7,6 +7,7 @@ skip-if = os == "android" || (toolkit == "cocoa" && debug) || buildapp == 'mulet support-files = browserElement_OpenMixedProcess.js file_browserElement_OpenMixedProcess.html + browserElement_Find.js browserElement_OpenTab.js [test_browserElement_oop_Viewmode.html] @@ -41,6 +42,7 @@ skip-if = (toolkit == 'gonk' && !debug) disabled = bug 1022281 [test_browserElement_oop_ErrorSecurity.html] skip-if = (toolkit == 'gonk' && !debug) +[test_browserElement_oop_Find.html] [test_browserElement_oop_FirstPaint.html] [test_browserElement_oop_ForwardName.html] [test_browserElement_oop_FrameWrongURI.html] diff --git a/dom/browser-element/mochitest/mochitest.ini b/dom/browser-element/mochitest/mochitest.ini index 157c752eeafd..049e8004e73c 100644 --- a/dom/browser-element/mochitest/mochitest.ini +++ b/dom/browser-element/mochitest/mochitest.ini @@ -29,6 +29,7 @@ support-files = browserElement_Download.js browserElement_ErrorSecurity.js browserElement_ExposableURI.js + browserElement_Find.js browserElement_FirstPaint.js browserElement_ForwardName.js browserElement_FrameWrongURI.js @@ -159,6 +160,7 @@ skip-if = os == "android" || toolkit == 'gonk' # embed-apps doesn't work in the [test_browserElement_inproc_Download.html] disabled = bug 1022281 [test_browserElement_inproc_ExposableURI.html] +[test_browserElement_inproc_Find.html] [test_browserElement_inproc_FirstPaint.html] [test_browserElement_inproc_ForwardName.html] [test_browserElement_inproc_FrameWrongURI.html] diff --git a/dom/browser-element/mochitest/test_browserElement_inproc_Find.html b/dom/browser-element/mochitest/test_browserElement_inproc_Find.html new file mode 100644 index 000000000000..e0333a6bb5db --- /dev/null +++ b/dom/browser-element/mochitest/test_browserElement_inproc_Find.html @@ -0,0 +1,19 @@ + + + +
+