mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-06 14:44:26 +00:00
Bug 1210410 Implement search messages for the Remote New Tab r=oyiptong
MozReview-Commit-ID: 8trDqRegP3G --HG-- extra : rebase_source : 81fa1478d2e51b06a2adb838a322d2c22925253e
This commit is contained in:
parent
11a106fd9b
commit
2f6e8ea7b8
@ -2,8 +2,11 @@
|
||||
NewTabWebChannel,
|
||||
NewTabPrefsProvider,
|
||||
PlacesProvider,
|
||||
PreviewProvider,
|
||||
NewTabSearchProvider,
|
||||
Preferences,
|
||||
XPCOMUtils
|
||||
XPCOMUtils,
|
||||
Task
|
||||
*/
|
||||
|
||||
/* exported NewTabMessages */
|
||||
@ -13,6 +16,7 @@
|
||||
const {utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesProvider",
|
||||
"resource:///modules/PlacesProvider.jsm");
|
||||
@ -20,12 +24,15 @@ XPCOMUtils.defineLazyModuleGetter(this, "PreviewProvider",
|
||||
"resource:///modules/PreviewProvider.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
|
||||
"resource:///modules/NewTabPrefsProvider.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabSearchProvider",
|
||||
"resource:///modules/NewTabSearchProvider.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
|
||||
"resource:///modules/NewTabWebChannel.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["NewTabMessages"];
|
||||
|
||||
const PREF_ENABLED = "browser.newtabpage.remote";
|
||||
const CURRENT_ENGINE = "browser-search-engine-modified";
|
||||
|
||||
// Action names are from the content's perspective. in from chrome == out from content
|
||||
// Maybe replace the ACTION objects by a bi-directional Map a bit later?
|
||||
@ -34,6 +41,13 @@ const ACTIONS = {
|
||||
"REQUEST_PREFS",
|
||||
"REQUEST_THUMB",
|
||||
"REQUEST_FRECENT",
|
||||
"REQUEST_UISTRINGS",
|
||||
"REQUEST_SEARCH_SUGGESTIONS",
|
||||
"REQUEST_MANAGE_ENGINES",
|
||||
"REQUEST_SEARCH_STATE",
|
||||
"REQUEST_REMOVE_FORM_HISTORY",
|
||||
"REQUEST_PERFORM_SEARCH",
|
||||
"REQUEST_CYCLE_ENGINE",
|
||||
],
|
||||
prefs: {
|
||||
inPrefs: "REQUEST_PREFS",
|
||||
@ -48,6 +62,23 @@ const ACTIONS = {
|
||||
outFrecent: "RECEIVE_FRECENT",
|
||||
outPlacesChange: "RECEIVE_PLACES_CHANGE",
|
||||
},
|
||||
search: {
|
||||
inSearch: {
|
||||
UIStrings: "REQUEST_UISTRINGS",
|
||||
suggestions: "REQUEST_SEARCH_SUGGESTIONS",
|
||||
manageEngines: "REQUEST_MANAGE_ENGINES",
|
||||
state: "REQUEST_SEARCH_STATE",
|
||||
removeFormHistory: "REQUEST_REMOVE_FORM_HISTORY",
|
||||
performSearch: "REQUEST_PERFORM_SEARCH",
|
||||
cycleEngine: "REQUEST_CYCLE_ENGINE"
|
||||
},
|
||||
outSearch: {
|
||||
UIStrings: "RECEIVE_UISTRINGS",
|
||||
suggestions: "RECEIVE_SEARCH_SUGGESTIONS",
|
||||
state: "RECEIVE_SEARCH_STATE",
|
||||
currentEngine: "RECEIVE_CURRENT_ENGINE"
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let NewTabMessages = {
|
||||
@ -75,6 +106,51 @@ let NewTabMessages = {
|
||||
NewTabWebChannel.send(ACTIONS.links.outFrecent, links, target);
|
||||
});
|
||||
break;
|
||||
case ACTIONS.search.inSearch.UIStrings:
|
||||
// Return to the originator all search strings to display
|
||||
let strings = NewTabSearchProvider.search.searchSuggestionUIStrings;
|
||||
NewTabWebChannel.send(ACTIONS.search.outSearch.UIStrings, strings, target);
|
||||
break;
|
||||
case ACTIONS.search.inSearch.suggestions:
|
||||
// Return to the originator all search suggestions
|
||||
Task.spawn(function*() {
|
||||
try {
|
||||
let {engineName, searchString} = data;
|
||||
let suggestions = yield NewTabSearchProvider.search.asyncGetSuggestions(engineName, searchString, target);
|
||||
NewTabWebChannel.send(ACTIONS.search.outSearch.suggestions, suggestions, target);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case ACTIONS.search.inSearch.manageEngines:
|
||||
// Open about:preferences to manage search state
|
||||
NewTabSearchProvider.search.manageEngines(target.browser);
|
||||
break;
|
||||
case ACTIONS.search.inSearch.state:
|
||||
// Return the state of the search component (i.e current engine and visible engine details)
|
||||
Task.spawn(function*() {
|
||||
try {
|
||||
let state = yield NewTabSearchProvider.search.asyncGetState();
|
||||
NewTabWebChannel.broadcast(ACTIONS.search.outSearch.state, state);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case ACTIONS.search.inSearch.removeFormHistory:
|
||||
// Remove a form history entry from the search component
|
||||
let suggestion = data;
|
||||
NewTabSearchProvider.search.removeFormHistory(target, suggestion);
|
||||
break;
|
||||
case ACTIONS.search.inSearch.performSearch:
|
||||
// Perform a search
|
||||
NewTabSearchProvider.search.asyncPerformSearch(target, data).catch(Cu.reportError);
|
||||
break;
|
||||
case ACTIONS.search.inSearch.cycleEngine:
|
||||
// Set the new current engine
|
||||
NewTabSearchProvider.search.asyncCycleEngine(data).catch(Cu.reportError);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -85,6 +161,14 @@ let NewTabMessages = {
|
||||
NewTabWebChannel.broadcast(ACTIONS.links.outPlacesChange, {type, data});
|
||||
},
|
||||
|
||||
/*
|
||||
* Broadcast current engine has changed to all open newtab pages
|
||||
*/
|
||||
_handleCurrentEngineChange(name, value) { //jshint unused: false
|
||||
let engine = value;
|
||||
NewTabWebChannel.broadcast(ACTIONS.search.outSearch.currentEngine, engine);
|
||||
},
|
||||
|
||||
/*
|
||||
* Broadcast preference changes to all open newtab pages
|
||||
*/
|
||||
@ -107,9 +191,11 @@ let NewTabMessages = {
|
||||
init() {
|
||||
this.handleContentRequest = this.handleContentRequest.bind(this);
|
||||
this._handleEnabledChange = this._handleEnabledChange.bind(this);
|
||||
this._handleCurrentEngineChange = this._handleCurrentEngineChange.bind(this);
|
||||
|
||||
PlacesProvider.links.init();
|
||||
NewTabPrefsProvider.prefs.init();
|
||||
NewTabSearchProvider.search.init();
|
||||
NewTabWebChannel.init();
|
||||
|
||||
this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
|
||||
@ -118,7 +204,9 @@ let NewTabMessages = {
|
||||
for (let action of ACTIONS.inboundActions) {
|
||||
NewTabWebChannel.on(action, this.handleContentRequest);
|
||||
}
|
||||
|
||||
NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handleEnabledChange);
|
||||
NewTabSearchProvider.search.on(CURRENT_ENGINE, this._handleCurrentEngineChange);
|
||||
|
||||
for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
|
||||
NewTabPrefsProvider.prefs.on(pref, this.handlePrefChange);
|
||||
@ -136,6 +224,7 @@ let NewTabMessages = {
|
||||
|
||||
if (this._prefs.enabled) {
|
||||
NewTabPrefsProvider.prefs.off(PREF_ENABLED, this._handleEnabledChange);
|
||||
NewTabSearchProvider.search.off(CURRENT_ENGINE, this._handleCurrentEngineChange);
|
||||
|
||||
for (let action of ACTIONS.inboundActions) {
|
||||
NewTabWebChannel.off(action, this.handleContentRequest);
|
||||
@ -148,6 +237,7 @@ let NewTabMessages = {
|
||||
|
||||
PlacesProvider.links.uninit();
|
||||
NewTabPrefsProvider.prefs.uninit();
|
||||
NewTabSearchProvider.search.uninit();
|
||||
NewTabWebChannel.uninit();
|
||||
}
|
||||
};
|
||||
|
@ -28,6 +28,7 @@ const gPrefsMap = new Map([
|
||||
["browser.newtabpage.blocked", "str"],
|
||||
["intl.locale.matchOS", "bool"],
|
||||
["general.useragent.locale", "localized"],
|
||||
["browser.search.hiddenOneOffs", "str"],
|
||||
]);
|
||||
|
||||
// prefs that are important for the newtab page
|
||||
@ -37,7 +38,8 @@ const gNewtabPagePrefs = new Set([
|
||||
"browser.newtabpage.pinned",
|
||||
"browser.newtabpage.blocked",
|
||||
"browser.newtabpage.introShown",
|
||||
"browser.newtabpage.updateIntroShown"
|
||||
"browser.newtabpage.updateIntroShown",
|
||||
"browser.search.hiddenOneOffs",
|
||||
]);
|
||||
|
||||
let PrefsProvider = function PrefsProvider() {
|
||||
|
112
browser/components/newtab/NewTabSearchProvider.jsm
Normal file
112
browser/components/newtab/NewTabSearchProvider.jsm
Normal file
@ -0,0 +1,112 @@
|
||||
/* global XPCOMUtils, ContentSearch, Task, Services, EventEmitter */
|
||||
/* exported NewTabSearchProvider */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["NewTabSearchProvider"];
|
||||
|
||||
const {utils: Cu, interfaces: Ci} = Components;
|
||||
const CURRENT_ENGINE = "browser-search-engine-modified";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
|
||||
"resource:///modules/ContentSearch.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
|
||||
const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
|
||||
return EventEmitter;
|
||||
});
|
||||
|
||||
function SearchProvider() {
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
SearchProvider.prototype = {
|
||||
|
||||
observe(subject, topic, data) { // jshint unused:false
|
||||
switch (data) {
|
||||
case "engine-current":
|
||||
if (topic === CURRENT_ENGINE) {
|
||||
Task.spawn(function* () {
|
||||
try {
|
||||
let state = yield ContentSearch.currentStateObj(true);
|
||||
let engine = state.currentEngine;
|
||||
this.emit(CURRENT_ENGINE, engine);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
break;
|
||||
case "engine-default":
|
||||
// engine-default is always sent with engine-current and isn't
|
||||
// relevant to content searches.
|
||||
break;
|
||||
default:
|
||||
Cu.reportError(new Error("NewTabSearchProvider observing unknown topic"));
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
try {
|
||||
Services.obs.addObserver(this, CURRENT_ENGINE, true);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference
|
||||
]),
|
||||
|
||||
uninit() {
|
||||
try {
|
||||
Services.obs.removeObserver(this, CURRENT_ENGINE, true);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
|
||||
get searchSuggestionUIStrings() {
|
||||
return ContentSearch.searchSuggestionUIStrings;
|
||||
},
|
||||
|
||||
removeFormHistory({browser}, suggestion) {
|
||||
ContentSearch.removeFormHistoryEntry({target: browser}, suggestion);
|
||||
},
|
||||
|
||||
manageEngines(browser) {
|
||||
const browserWin = browser.ownerDocument.defaultView;
|
||||
browserWin.openPreferences("paneSearch");
|
||||
},
|
||||
|
||||
asyncGetState: Task.async(function*() {
|
||||
let state = yield ContentSearch.currentStateObj(true);
|
||||
return state;
|
||||
}),
|
||||
|
||||
asyncPerformSearch: Task.async(function*({browser}, searchData) {
|
||||
ContentSearch.performSearch({target: browser}, searchData);
|
||||
yield ContentSearch.addFormHistoryEntry({target: browser}, searchData.searchString);
|
||||
}),
|
||||
|
||||
asyncCycleEngine: Task.async(function*(engineName) {
|
||||
Services.search.currentEngine = Services.search.getEngineByName(engineName);
|
||||
let state = yield ContentSearch.currentStateObj(true);
|
||||
let newEngine = state.currentEngine;
|
||||
this.emit(CURRENT_ENGINE, newEngine);
|
||||
}),
|
||||
|
||||
asyncGetSuggestions: Task.async(function*(engineName, searchString, target) {
|
||||
let suggestions = ContentSearch.getSuggestions(engineName, searchString, target.browser);
|
||||
return suggestions;
|
||||
}),
|
||||
};
|
||||
|
||||
const NewTabSearchProvider = {
|
||||
search: new SearchProvider(),
|
||||
};
|
@ -14,6 +14,7 @@ EXTRA_JS_MODULES += [
|
||||
'NewTabMessages.jsm',
|
||||
'NewTabPrefsProvider.jsm',
|
||||
'NewTabRemoteResources.jsm',
|
||||
'NewTabSearchProvider.jsm',
|
||||
'NewTabURL.jsm',
|
||||
'NewTabWebChannel.jsm',
|
||||
'PlacesProvider.jsm',
|
||||
|
@ -6,6 +6,7 @@ support-files =
|
||||
newtabmessages_places.html
|
||||
newtabmessages_prefs.html
|
||||
newtabmessages_preview.html
|
||||
newtabmessages_search.html
|
||||
|
||||
[browser_PreviewProvider.js]
|
||||
[browser_remotenewtab_pageloads.js]
|
||||
|
@ -1,8 +1,10 @@
|
||||
/* globals Cu, XPCOMUtils, Preferences, is, registerCleanupFunction, NewTabWebChannel, PlacesTestUtils, Task */
|
||||
/* globals Cu, XPCOMUtils, Preferences, is, registerCleanupFunction, NewTabWebChannel,
|
||||
PlacesTestUtils, NewTabMessages, ok, Services, PlacesUtils, NetUtil, Task */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
|
||||
"resource:///modules/NewTabWebChannel.jsm");
|
||||
@ -161,3 +163,60 @@ add_task(function* placesMessages_request() {
|
||||
});
|
||||
yield cleanup();
|
||||
});
|
||||
|
||||
/*
|
||||
* Sanity tests for search messages
|
||||
*/
|
||||
add_task(function* searchMessages_request() {
|
||||
yield setup();
|
||||
let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_search.html";
|
||||
|
||||
// create dummy test engines
|
||||
Services.search.addEngineWithDetails("Engine1", "", "", "", "GET",
|
||||
"http://example.com/?q={searchTerms}");
|
||||
Services.search.addEngineWithDetails("Engine2", "", "", "", "GET",
|
||||
"http://example.com/?q={searchTerms}");
|
||||
|
||||
let tabOptions = {
|
||||
gBrowser,
|
||||
url: testURL
|
||||
};
|
||||
|
||||
let UIStringsResponseAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("UIStringsAck", (_, msg) => {
|
||||
ok(true, "a search request response for UI string has been received");
|
||||
ok(msg.data, "received the UI Strings");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
let suggestionsResponseAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("suggestionsAck", (_, msg) => {
|
||||
ok(true, "a search request response for suggestions has been received");
|
||||
ok(msg.data, "received the suggestions");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
let stateResponseAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("stateAck", (_, msg) => {
|
||||
ok(true, "a search request response for state has been received");
|
||||
ok(msg.data, "received a state object");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
let currentEngineResponseAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("currentEngineAck", (_, msg) => {
|
||||
ok(true, "a search request response for current engine has been received");
|
||||
ok(msg.data, "received a current engine");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
yield BrowserTestUtils.withNewTab(tabOptions, function*() {
|
||||
yield UIStringsResponseAck;
|
||||
yield suggestionsResponseAck;
|
||||
yield stateResponseAck;
|
||||
yield currentEngineResponseAck;
|
||||
});
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
@ -0,0 +1,113 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Newtab WebChannel test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
let suggestionsData = {
|
||||
engineName: "Engine1",
|
||||
searchString: "test",
|
||||
};
|
||||
let removeFormHistoryData = "test";
|
||||
let performSearchData = {
|
||||
engineName: "Engine1",
|
||||
healthReportKey: "1",
|
||||
searchPurpose: "d",
|
||||
searchString: "test",
|
||||
};
|
||||
let cycleEngineData = "Engine2";
|
||||
|
||||
window.addEventListener("WebChannelMessageToContent", function(e) {
|
||||
if (e.detail.message) {
|
||||
let reply;
|
||||
switch (e.detail.message.type) {
|
||||
case "RECEIVE_UISTRINGS":
|
||||
reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "UIStringsAck", data: e.detail.message.data}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
break;
|
||||
case "RECEIVE_SEARCH_SUGGESTIONS":
|
||||
reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "suggestionsAck", data: e.detail.message.data}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
break;
|
||||
case "RECEIVE_SEARCH_STATE":
|
||||
reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "stateAck", data: e.detail.message.data}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
break;
|
||||
case "RECEIVE_CURRENT_ENGINE":
|
||||
reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "currentEngineAck", data: e.detail.message.data}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
if (document.readyState === "complete") {
|
||||
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_UISTRINGS"}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_SEARCH_SUGGESTIONS", data: suggestionsData}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_SEARCH_STATE"}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_REMOVE_FORM_HISTORY", data: removeFormHistoryData}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_PERFORM_SEARCH", data: performSearchData}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_CYCLE_ENGINE", data: cycleEngineData}),
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,84 @@
|
||||
"use strict";
|
||||
|
||||
/* global XPCOMUtils, NewTabSearchProvider, run_next_test, ok, equal, do_check_true, do_get_profile, Services */
|
||||
/* exported run_test */
|
||||
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabSearchProvider",
|
||||
"resource:///modules/NewTabSearchProvider.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
|
||||
"resource:///modules/ContentSearch.jsm");
|
||||
|
||||
// ensure a profile exists
|
||||
do_get_profile();
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function hasProp(obj) {
|
||||
return function(aProp) {
|
||||
ok(obj.hasOwnProperty(aProp), `expect to have property ${aProp}`);
|
||||
};
|
||||
}
|
||||
|
||||
add_task(function* test_search() {
|
||||
ContentSearch.init();
|
||||
let observerPromise = new Promise(resolve => {
|
||||
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
|
||||
if (aData === "init-complete" && aTopic === "browser-search-service") {
|
||||
Services.obs.removeObserver(observer, "browser-search-service");
|
||||
resolve();
|
||||
}
|
||||
}, "browser-search-service", false);
|
||||
});
|
||||
Services.search.init();
|
||||
yield observerPromise;
|
||||
do_check_true(Services.search.isInitialized);
|
||||
|
||||
// get initial state of search and check it has correct properties
|
||||
let state = yield NewTabSearchProvider.search.asyncGetState();
|
||||
let stateProps = hasProp(state);
|
||||
["engines", "currentEngine"].forEach(stateProps);
|
||||
|
||||
// check that the current engine is correct and has correct properties
|
||||
let {currentEngine} = state;
|
||||
equal(currentEngine.name, Services.search.currentEngine.name, "Current engine has been correctly set");
|
||||
var engineProps = hasProp(currentEngine);
|
||||
["name", "placeholder", "iconBuffer"].forEach(engineProps);
|
||||
|
||||
//create dummy test engines to test observer
|
||||
Services.search.addEngineWithDetails("TestSearch1", "", "", "", "GET",
|
||||
"http://example.com/?q={searchTerms}");
|
||||
Services.search.addEngineWithDetails("TestSearch2", "", "", "", "GET",
|
||||
"http://example.com/?q={searchTerms}");
|
||||
|
||||
// set one of the dummy test engines to the default engine
|
||||
Services.search.defaultEngine = Services.search.getEngineByName("TestSearch1");
|
||||
|
||||
// test that the event emitter is working by setting a new current engine "TestSearch2"
|
||||
let engineName = "TestSearch2";
|
||||
NewTabSearchProvider.search.init();
|
||||
|
||||
// event emitter will fire when current engine is changed
|
||||
let promise = new Promise(resolve => {
|
||||
NewTabSearchProvider.search.once("browser-search-engine-modified", (name, data) => { // jshint ignore:line
|
||||
resolve([name, data.name]);
|
||||
});
|
||||
});
|
||||
|
||||
// set a new current engine
|
||||
Services.search.currentEngine = Services.search.getEngineByName(engineName);
|
||||
let expectedEngineName = Services.search.currentEngine.name;
|
||||
|
||||
// emitter should fire and return the new engine
|
||||
let [eventName, actualEngineName] = yield promise;
|
||||
equal(eventName, "browser-search-engine-modified", `emitter sent the correct event ${eventName}`);
|
||||
equal(expectedEngineName, actualEngineName, `emitter set the correct engine ${expectedEngineName}`);
|
||||
NewTabSearchProvider.search.uninit();
|
||||
});
|
||||
|
@ -6,5 +6,6 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_AboutNewTabService.js]
|
||||
[test_NewTabPrefsProvider.js]
|
||||
[test_NewTabSearchProvider.js]
|
||||
[test_NewTabURL.js]
|
||||
[test_PlacesProvider.js]
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* globals XPCOMUtils, Services, Task, Promise, SearchSuggestionController, FormHistory, PrivateBrowsingUtils */
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
@ -73,10 +73,10 @@ const MAX_SUGGESTIONS = 6;
|
||||
* data: see _currentEngineObj
|
||||
* CurrentState
|
||||
* Broadcast when the current search state changes.
|
||||
* data: see _currentStateObj
|
||||
* data: see currentStateObj
|
||||
* State
|
||||
* Sent in reply to GetState.
|
||||
* data: see _currentStateObj
|
||||
* data: see currentStateObj
|
||||
* Strings
|
||||
* Sent in reply to GetStrings
|
||||
* data: Object containing string names and values for the current locale.
|
||||
@ -126,6 +126,7 @@ this.ContentSearch = {
|
||||
let searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties");
|
||||
let stringNames = ["searchHeader", "searchPlaceholder", "searchForSomethingWith",
|
||||
"searchWithHeader", "searchSettings"];
|
||||
|
||||
for (let name of stringNames) {
|
||||
this._searchSuggestionUIStrings[name] = searchBundle.GetStringFromName(name);
|
||||
}
|
||||
@ -144,7 +145,8 @@ this.ContentSearch = {
|
||||
Services.obs.removeObserver(this, "shutdown-leaks-before-check");
|
||||
|
||||
this._eventQueue.length = 0;
|
||||
return this._destroyedPromise = Promise.resolve(this._currentEventPromise);
|
||||
this._destroyedPromise = Promise.resolve(this._currentEventPromise);
|
||||
return this._destroyedPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -177,7 +179,7 @@ this.ContentSearch = {
|
||||
|
||||
// Search requests cause cancellation of all Suggestion requests from the
|
||||
// same browser.
|
||||
if (msg.data.type == "Search") {
|
||||
if (msg.data.type === "Search") {
|
||||
this._cancelSuggestions(msg);
|
||||
}
|
||||
|
||||
@ -205,6 +207,155 @@ this.ContentSearch = {
|
||||
}
|
||||
},
|
||||
|
||||
removeFormHistoryEntry: function (msg, entry) {
|
||||
let browserData = this._suggestionDataForBrowser(msg.target);
|
||||
if (browserData && browserData.previousFormHistoryResult) {
|
||||
let { previousFormHistoryResult } = browserData;
|
||||
for (let i = 0; i < previousFormHistoryResult.matchCount; i++) {
|
||||
if (previousFormHistoryResult.getValueAt(i) === entry) {
|
||||
previousFormHistoryResult.removeValueAt(i, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
performSearch: function (msg, data) {
|
||||
this._ensureDataHasProperties(data, [
|
||||
"engineName",
|
||||
"searchString",
|
||||
"healthReportKey",
|
||||
"searchPurpose",
|
||||
]);
|
||||
let engine = Services.search.getEngineByName(data.engineName);
|
||||
let submission = engine.getSubmission(data.searchString, "", data.searchPurpose);
|
||||
let browser = msg.target;
|
||||
let win;
|
||||
try {
|
||||
win = browser.ownerDocument.defaultView;
|
||||
}
|
||||
catch (err) {
|
||||
// The browser may have been closed between the time its content sent the
|
||||
// message and the time we handle it. In that case, trying to call any
|
||||
// method on it will throw.
|
||||
return;
|
||||
}
|
||||
|
||||
let where = win.whereToOpenLink(data.originalEvent);
|
||||
|
||||
// There is a chance that by the time we receive the search message, the user
|
||||
// has switched away from the tab that triggered the search. If, based on the
|
||||
// event, we need to load the search in the same tab that triggered it (i.e.
|
||||
// where === "current"), openUILinkIn will not work because that tab is no
|
||||
// longer the current one. For this case we manually load the URI.
|
||||
if (where === "current") {
|
||||
browser.loadURIWithFlags(submission.uri.spec,
|
||||
Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null,
|
||||
submission.postData);
|
||||
} else {
|
||||
let params = {
|
||||
postData: submission.postData,
|
||||
inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"),
|
||||
};
|
||||
win.openUILinkIn(submission.uri.spec, where, params);
|
||||
}
|
||||
win.BrowserSearch.recordSearchInTelemetry(engine, data.healthReportKey,
|
||||
data.selection || null);
|
||||
return;
|
||||
},
|
||||
|
||||
getSuggestions: Task.async(function* (engineName, searchString, browser, remoteTimeout=null) {
|
||||
let engine = Services.search.getEngineByName(engineName);
|
||||
if (!engine) {
|
||||
throw new Error("Unknown engine name: " + engineName);
|
||||
}
|
||||
|
||||
let browserData = this._suggestionDataForBrowser(browser, true);
|
||||
let { controller } = browserData;
|
||||
let ok = SearchSuggestionController.engineOffersSuggestions(engine);
|
||||
controller.maxLocalResults = ok ? MAX_LOCAL_SUGGESTIONS : MAX_SUGGESTIONS;
|
||||
controller.maxRemoteResults = ok ? MAX_SUGGESTIONS : 0;
|
||||
controller.remoteTimeout = remoteTimeout || undefined;
|
||||
let priv = PrivateBrowsingUtils.isBrowserPrivate(browser);
|
||||
// fetch() rejects its promise if there's a pending request, but since we
|
||||
// process our event queue serially, there's never a pending request.
|
||||
this._currentSuggestion = { controller: controller, target: browser };
|
||||
let suggestions = yield controller.fetch(searchString, priv, engine);
|
||||
this._currentSuggestion = null;
|
||||
|
||||
// suggestions will be null if the request was cancelled
|
||||
let result = {};
|
||||
if (!suggestions) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Keep the form history result so RemoveFormHistoryEntry can remove entries
|
||||
// from it. Keeping only one result isn't foolproof because the client may
|
||||
// try to remove an entry from one set of suggestions after it has requested
|
||||
// more but before it's received them. In that case, the entry may not
|
||||
// appear in the new suggestions. But that should happen rarely.
|
||||
browserData.previousFormHistoryResult = suggestions.formHistoryResult;
|
||||
result = {
|
||||
engineName,
|
||||
term: suggestions.term,
|
||||
local: suggestions.local,
|
||||
remote: suggestions.remote,
|
||||
};
|
||||
return result;
|
||||
}),
|
||||
|
||||
addFormHistoryEntry: Task.async(function* (browser, entry="") {
|
||||
let isPrivate = false;
|
||||
try {
|
||||
// isBrowserPrivate assumes that the passed-in browser has all the normal
|
||||
// properties, which won't be true if the browser has been destroyed.
|
||||
// That may be the case here due to the asynchronous nature of messaging.
|
||||
isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser.target);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
if (isPrivate || entry === "") {
|
||||
return false;
|
||||
}
|
||||
let browserData = this._suggestionDataForBrowser(browser.target, true);
|
||||
FormHistory.update({
|
||||
op: "bump",
|
||||
fieldname: browserData.controller.formHistoryParam,
|
||||
value: entry,
|
||||
}, {
|
||||
handleCompletion: () => {},
|
||||
handleError: err => {
|
||||
Cu.reportError("Error adding form history entry: " + err);
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
|
||||
currentStateObj: Task.async(function* (uriFlag=false) {
|
||||
let state = {
|
||||
engines: [],
|
||||
currentEngine: yield this._currentEngineObj(),
|
||||
};
|
||||
if (uriFlag) {
|
||||
state.currentEngine.iconBuffer = Services.search.currentEngine.getIconURLBySize(16, 16);
|
||||
}
|
||||
let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
|
||||
let hiddenList = pref ? pref.split(",") : [];
|
||||
for (let engine of Services.search.getVisibleEngines()) {
|
||||
let uri = engine.getIconURLBySize(16, 16);
|
||||
let iconBuffer = uri;
|
||||
if (!uriFlag) {
|
||||
iconBuffer = yield this._arrayBufferFromDataURI(uri);
|
||||
}
|
||||
state.engines.push({
|
||||
name: engine.name,
|
||||
iconBuffer,
|
||||
hidden: hiddenList.indexOf(engine.name) !== -1,
|
||||
});
|
||||
}
|
||||
return state;
|
||||
}),
|
||||
|
||||
_processEventQueue: function () {
|
||||
if (this._currentEventPromise || !this._eventQueue.length) {
|
||||
return;
|
||||
@ -227,14 +378,14 @@ this.ContentSearch = {
|
||||
_cancelSuggestions: function (msg) {
|
||||
let cancelled = false;
|
||||
// cancel active suggestion request
|
||||
if (this._currentSuggestion && this._currentSuggestion.target == msg.target) {
|
||||
if (this._currentSuggestion && this._currentSuggestion.target === msg.target) {
|
||||
this._currentSuggestion.controller.stop();
|
||||
cancelled = true;
|
||||
}
|
||||
// cancel queued suggestion requests
|
||||
for (let i = 0; i < this._eventQueue.length; i++) {
|
||||
let m = this._eventQueue[i].data;
|
||||
if (msg.target == m.target && m.data.type == "GetSuggestions") {
|
||||
if (msg.target === m.target && m.data.type === "GetSuggestions") {
|
||||
this._eventQueue.splice(i, 1);
|
||||
cancelled = true;
|
||||
i--;
|
||||
@ -255,7 +406,7 @@ this.ContentSearch = {
|
||||
}),
|
||||
|
||||
_onMessageGetState: function (msg, data) {
|
||||
return this._currentStateObj().then(state => {
|
||||
return this.currentStateObj().then(state => {
|
||||
this._reply(msg, "State", state);
|
||||
});
|
||||
},
|
||||
@ -265,58 +416,16 @@ this.ContentSearch = {
|
||||
},
|
||||
|
||||
_onMessageSearch: function (msg, data) {
|
||||
this._ensureDataHasProperties(data, [
|
||||
"engineName",
|
||||
"searchString",
|
||||
"healthReportKey",
|
||||
"searchPurpose",
|
||||
]);
|
||||
let engine = Services.search.getEngineByName(data.engineName);
|
||||
let submission = engine.getSubmission(data.searchString, "", data.searchPurpose);
|
||||
let browser = msg.target;
|
||||
let win;
|
||||
try {
|
||||
win = browser.ownerDocument.defaultView;
|
||||
}
|
||||
catch (err) {
|
||||
// The browser may have been closed between the time its content sent the
|
||||
// message and the time we handle it. In that case, trying to call any
|
||||
// method on it will throw.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let where = win.whereToOpenLink(data.originalEvent);
|
||||
|
||||
// There is a chance that by the time we receive the search message, the user
|
||||
// has switched away from the tab that triggered the search. If, based on the
|
||||
// event, we need to load the search in the same tab that triggered it (i.e.
|
||||
// where == "current"), openUILinkIn will not work because that tab is no
|
||||
// longer the current one. For this case we manually load the URI.
|
||||
if (where == "current") {
|
||||
browser.loadURIWithFlags(submission.uri.spec,
|
||||
Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null,
|
||||
submission.postData);
|
||||
} else {
|
||||
let params = {
|
||||
postData: submission.postData,
|
||||
inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"),
|
||||
};
|
||||
win.openUILinkIn(submission.uri.spec, where, params);
|
||||
}
|
||||
win.BrowserSearch.recordSearchInTelemetry(engine, data.healthReportKey,
|
||||
data.selection || null);
|
||||
return Promise.resolve();
|
||||
this.performSearch(msg, data);
|
||||
},
|
||||
|
||||
_onMessageSetCurrentEngine: function (msg, data) {
|
||||
Services.search.currentEngine = Services.search.getEngineByName(data);
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
_onMessageManageEngines: function (msg, data) {
|
||||
let browserWin = msg.target.ownerDocument.defaultView;
|
||||
browserWin.openPreferences("paneSearch");
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
_onMessageGetSuggestions: Task.async(function* (msg, data) {
|
||||
@ -324,36 +433,8 @@ this.ContentSearch = {
|
||||
"engineName",
|
||||
"searchString",
|
||||
]);
|
||||
|
||||
let engine = Services.search.getEngineByName(data.engineName);
|
||||
if (!engine) {
|
||||
throw new Error("Unknown engine name: " + data.engineName);
|
||||
}
|
||||
|
||||
let browserData = this._suggestionDataForBrowser(msg.target, true);
|
||||
let { controller } = browserData;
|
||||
let ok = SearchSuggestionController.engineOffersSuggestions(engine);
|
||||
controller.maxLocalResults = ok ? MAX_LOCAL_SUGGESTIONS : MAX_SUGGESTIONS;
|
||||
controller.maxRemoteResults = ok ? MAX_SUGGESTIONS : 0;
|
||||
controller.remoteTimeout = data.remoteTimeout || undefined;
|
||||
let priv = PrivateBrowsingUtils.isBrowserPrivate(msg.target);
|
||||
// fetch() rejects its promise if there's a pending request, but since we
|
||||
// process our event queue serially, there's never a pending request.
|
||||
this._currentSuggestion = { controller: controller, target: msg.target };
|
||||
let suggestions = yield controller.fetch(data.searchString, priv, engine);
|
||||
this._currentSuggestion = null;
|
||||
|
||||
// suggestions will be null if the request was cancelled
|
||||
if (!suggestions) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the form history result so RemoveFormHistoryEntry can remove entries
|
||||
// from it. Keeping only one result isn't foolproof because the client may
|
||||
// try to remove an entry from one set of suggestions after it has requested
|
||||
// more but before it's received them. In that case, the entry may not
|
||||
// appear in the new suggestions. But that should happen rarely.
|
||||
browserData.previousFormHistoryResult = suggestions.formHistoryResult;
|
||||
let {engineName, searchString} = data;
|
||||
let suggestions = yield this.getSuggestions(engineName, searchString, msg.target);
|
||||
|
||||
this._reply(msg, "Suggestions", {
|
||||
engineName: data.engineName,
|
||||
@ -363,45 +444,12 @@ this.ContentSearch = {
|
||||
});
|
||||
}),
|
||||
|
||||
_onMessageAddFormHistoryEntry: function (msg, entry) {
|
||||
let isPrivate = true;
|
||||
try {
|
||||
// isBrowserPrivate assumes that the passed-in browser has all the normal
|
||||
// properties, which won't be true if the browser has been destroyed.
|
||||
// That may be the case here due to the asynchronous nature of messaging.
|
||||
isPrivate = PrivateBrowsingUtils.isBrowserPrivate(msg.target);
|
||||
} catch (err) {}
|
||||
if (isPrivate || entry === "") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let browserData = this._suggestionDataForBrowser(msg.target, true);
|
||||
if (FormHistory.enabled) {
|
||||
FormHistory.update({
|
||||
op: "bump",
|
||||
fieldname: browserData.controller.formHistoryParam,
|
||||
value: entry,
|
||||
}, {
|
||||
handleCompletion: () => {},
|
||||
handleError: err => {
|
||||
Cu.reportError("Error adding form history entry: " + err);
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
_onMessageAddFormHistoryEntry: Task.async(function* (msg, entry) {
|
||||
yield this.addFormHistoryEntry(msg, entry);
|
||||
}),
|
||||
|
||||
_onMessageRemoveFormHistoryEntry: function (msg, entry) {
|
||||
let browserData = this._suggestionDataForBrowser(msg.target);
|
||||
if (browserData && browserData.previousFormHistoryResult) {
|
||||
let { previousFormHistoryResult } = browserData;
|
||||
for (let i = 0; i < previousFormHistoryResult.matchCount; i++) {
|
||||
if (previousFormHistoryResult.getValueAt(i) == entry) {
|
||||
previousFormHistoryResult.removeValueAt(i, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
this.removeFormHistoryEntry(msg, entry);
|
||||
},
|
||||
|
||||
_onMessageSpeculativeConnect: function (msg, engineName) {
|
||||
@ -417,14 +465,14 @@ this.ContentSearch = {
|
||||
},
|
||||
|
||||
_onObserve: Task.async(function* (data) {
|
||||
if (data == "engine-current") {
|
||||
if (data === "engine-current") {
|
||||
let engine = yield this._currentEngineObj();
|
||||
this._broadcast("CurrentEngine", engine);
|
||||
}
|
||||
else if (data != "engine-default") {
|
||||
else if (data !== "engine-default") {
|
||||
// engine-default is always sent with engine-current and isn't otherwise
|
||||
// relevant to content searches.
|
||||
let state = yield this._currentStateObj();
|
||||
let state = yield this.currentStateObj();
|
||||
this._broadcast("CurrentState", state);
|
||||
}
|
||||
}),
|
||||
@ -464,24 +512,6 @@ this.ContentSearch = {
|
||||
}];
|
||||
},
|
||||
|
||||
_currentStateObj: Task.async(function* () {
|
||||
let state = {
|
||||
engines: [],
|
||||
currentEngine: yield this._currentEngineObj(),
|
||||
};
|
||||
let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
|
||||
let hiddenList = pref ? pref.split(",") : [];
|
||||
for (let engine of Services.search.getVisibleEngines()) {
|
||||
let uri = engine.getIconURLBySize(16, 16);
|
||||
state.engines.push({
|
||||
name: engine.name,
|
||||
iconBuffer: yield this._arrayBufferFromDataURI(uri),
|
||||
hidden: hiddenList.indexOf(engine.name) != -1,
|
||||
});
|
||||
}
|
||||
return state;
|
||||
}),
|
||||
|
||||
_currentEngineObj: Task.async(function* () {
|
||||
let engine = Services.search.currentEngine;
|
||||
let favicon = engine.getIconURLBySize(16, 16);
|
||||
|
Loading…
x
Reference in New Issue
Block a user