mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
Bug 1210410 - Implement search messages for the Remote New Tab page r=emtwo r=oyiptong
--HG-- extra : commitid : AV5EJ8ZskmP
This commit is contained in:
parent
9d15b19f48
commit
bb1ca7d91d
@ -1,9 +1,9 @@
|
||||
/* 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 Services, XPCOMUtils, Task, SearchProvider, RemoteNewTabUtils, BackgroundPageThumbs,
|
||||
RemotePages, PageThumbs, RemoteDirectoryLinksProvider, RemoteNewTabLocation*/
|
||||
|
||||
/* globals Services, XPCOMUtils, RemotePages, RemoteNewTabLocation, RemoteNewTabUtils, Task */
|
||||
/* globals BackgroundPageThumbs, PageThumbs, RemoteDirectoryLinksProvider */
|
||||
/* exported RemoteAboutNewTab */
|
||||
|
||||
"use strict";
|
||||
@ -31,6 +31,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "RemoteDirectoryLinksProvider",
|
||||
"resource:///modules/RemoteDirectoryLinksProvider.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabLocation",
|
||||
"resource:///modules/RemoteNewTabLocation.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SearchProvider",
|
||||
"resource:///modules/SearchProvider.jsm");
|
||||
|
||||
let RemoteAboutNewTab = {
|
||||
|
||||
@ -44,13 +46,69 @@ let RemoteAboutNewTab = {
|
||||
this.pageListener.addMessageListener("NewTab:InitializeGrid", this.initializeGrid.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:UpdateGrid", this.updateGrid.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:CaptureBackgroundPageThumbs",
|
||||
this.captureBackgroundPageThumb.bind(this));
|
||||
this.captureBackgroundPageThumb.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:PageThumbs", this.createPageThumb.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:Search", this.search.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:GetState", this.getState.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:GetStrings", this.getStrings.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:GetSuggestions", this.getSuggestions.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:RemoveFormHistoryEntry", this.removeFormHistoryEntry.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:ManageEngines", this.manageEngines.bind(this));
|
||||
this.pageListener.addMessageListener("NewTab:SetCurrentEngine", this.setCurrentEngine.bind(this));
|
||||
this.pageListener.addMessageListener("NewTabFrame:GetInit", this.initContentFrame.bind(this));
|
||||
|
||||
this._addObservers();
|
||||
},
|
||||
|
||||
search: function(message) {
|
||||
SearchProvider.performSearch(message.target.browser, message.data);
|
||||
},
|
||||
|
||||
getState: Task.async(function* (message) {
|
||||
let state = yield SearchProvider.state;
|
||||
message.target.sendAsyncMessage("NewTab:ContentSearchService", {
|
||||
state,
|
||||
name: "State",
|
||||
});
|
||||
}),
|
||||
|
||||
getStrings: function(message) {
|
||||
let strings = SearchProvider.searchSuggestionUIStrings;
|
||||
message.target.sendAsyncMessage("NewTab:ContentSearchService", {
|
||||
strings,
|
||||
name: "Strings",
|
||||
});
|
||||
},
|
||||
|
||||
getSuggestions: Task.async(function* (message) {
|
||||
try {
|
||||
let suggestion = yield SearchProvider.getSuggestions(message.target.browser, message.data);
|
||||
|
||||
// In the case where there is no suggestion available, do not send a message.
|
||||
if (suggestion !== null) {
|
||||
message.target.sendAsyncMessage("NewTab:ContentSearchService", {
|
||||
suggestion,
|
||||
name: "Suggestions",
|
||||
});
|
||||
}
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}),
|
||||
|
||||
removeFormHistoryEntry: function(message) {
|
||||
SearchProvider.removeFormHistoryEntry(message.target.browser, message.data.suggestionStr);
|
||||
},
|
||||
|
||||
manageEngines: function(message) {
|
||||
let browserWin = message.target.browser.ownerDocument.defaultView;
|
||||
browserWin.openPreferences("paneSearch");
|
||||
},
|
||||
|
||||
setCurrentEngine: function(message) {
|
||||
Services.search.currentEngine = Services.search.getEngineByName(message.data.engineName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the grid for the first time when the page loads.
|
||||
* Fetch all the links and send them down to the child to populate
|
||||
@ -147,7 +205,7 @@ let RemoteAboutNewTab = {
|
||||
let canvas = doc.createElementNS(XHTML_NAMESPACE, "canvas");
|
||||
let enhanced = Services.prefs.getBoolPref("browser.newtabpage.enhanced");
|
||||
|
||||
img.onload = function(e) { // jshint ignore:line
|
||||
img.onload = function() {
|
||||
canvas.width = img.naturalWidth;
|
||||
canvas.height = img.naturalHeight;
|
||||
var ctx = canvas.getContext("2d");
|
||||
@ -179,14 +237,14 @@ let RemoteAboutNewTab = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens for a preference change or session purge for all pages and sends
|
||||
* a message to update the pages that are open. If a session purge occured,
|
||||
* also clear the links cache and update the set of links to display, as they
|
||||
* may have changed, then proceed with the page update.
|
||||
* Listens for a preference change, a session purge for all pages, or if the
|
||||
* current search engine is modified, and sends a message to update the pages
|
||||
* that are open. If a session purge occured, also clear the links cache and
|
||||
* update the set of links to display, as they may have changed, then proceed
|
||||
* with the page update.
|
||||
*/
|
||||
observe: function(aSubject, aTopic, aData) { // jshint ignore:line
|
||||
let extraData;
|
||||
let refreshPage = false;
|
||||
if (aTopic === "browser:purge-session-history") {
|
||||
RemoteNewTabUtils.links.resetCache();
|
||||
RemoteNewTabUtils.links.populateCache(() => {
|
||||
@ -195,6 +253,28 @@ let RemoteAboutNewTab = {
|
||||
enhancedLinks: this.getEnhancedLinks(),
|
||||
});
|
||||
});
|
||||
} else if (aTopic === "browser-search-engine-modified" && aData === "engine-current") {
|
||||
Task.spawn(function* () {
|
||||
try {
|
||||
let engine = yield SearchProvider.currentEngine;
|
||||
this.pageListener.sendAsyncMessage("NewTab:ContentSearchService", {
|
||||
engine, name: "CurrentEngine"
|
||||
});
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}.bind(this));
|
||||
} else if (aTopic === "nsPref:changed" && aData === "browser.search.hiddenOneOffs") {
|
||||
Task.spawn(function* () {
|
||||
try {
|
||||
let state = yield SearchProvider.state;
|
||||
this.pageListener.sendAsyncMessage("NewTab:ContentSearchService", {
|
||||
state, name: "CurrentState"
|
||||
});
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
if (extraData !== undefined || aTopic === "page-thumbnail:create") {
|
||||
@ -202,7 +282,10 @@ let RemoteAboutNewTab = {
|
||||
// Change the topic for enhanced and enabled observers.
|
||||
aTopic = aData;
|
||||
}
|
||||
this.pageListener.sendAsyncMessage("NewTab:Observe", {topic: aTopic, data: extraData});
|
||||
this.pageListener.sendAsyncMessage("NewTab:Observe", {
|
||||
topic: aTopic,
|
||||
data: extraData
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -212,6 +295,8 @@ let RemoteAboutNewTab = {
|
||||
_addObservers: function() {
|
||||
Services.obs.addObserver(this, "page-thumbnail:create", true);
|
||||
Services.obs.addObserver(this, "browser:purge-session-history", true);
|
||||
Services.prefs.addObserver("browser.search.hiddenOneOffs", this, false);
|
||||
Services.obs.addObserver(this, "browser-search-engine-modified", true);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -220,10 +305,13 @@ let RemoteAboutNewTab = {
|
||||
_removeObservers: function() {
|
||||
Services.obs.removeObserver(this, "page-thumbnail:create");
|
||||
Services.obs.removeObserver(this, "browser:purge-session-history");
|
||||
Services.prefs.removeObserver("browser.search.hiddenOneOffs", this);
|
||||
Services.obs.removeObserver(this, "browser-search-engine-modified");
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
Ci.nsISupportsWeakReference
|
||||
]),
|
||||
|
||||
uninit: function() {
|
||||
this._removeObservers();
|
||||
|
@ -8,7 +8,7 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.importGlobalProperties(["URL"]);
|
||||
|
||||
// TODO: will get dynamically set in bug 1210478
|
||||
const DEFAULT_PAGE_LOCATION = "https://newtab.cdn.mozilla.net/v0/nightly/en-US/index.html";
|
||||
const DEFAULT_PAGE_LOCATION = "https://newtab.cdn.mozilla.net/v2/nightly/en-US/index.html";
|
||||
|
||||
this.RemoteNewTabLocation = {
|
||||
_url: new URL(DEFAULT_PAGE_LOCATION),
|
||||
|
300
browser/components/newtab/SearchProvider.jsm
Normal file
300
browser/components/newtab/SearchProvider.jsm
Normal file
@ -0,0 +1,300 @@
|
||||
/* 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 Components, Services, XPCOMUtils, Task, SearchSuggestionController, PrivateBrowsingUtils, FormHistory*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"SearchProvider",
|
||||
];
|
||||
|
||||
const {
|
||||
classes: Cc,
|
||||
interfaces: Ci,
|
||||
utils: Cu,
|
||||
} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.importGlobalProperties(["URL", "Blob"]);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
|
||||
"resource://gre/modules/FormHistory.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SearchSuggestionController",
|
||||
"resource://gre/modules/SearchSuggestionController.jsm");
|
||||
|
||||
const stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
|
||||
const searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties");
|
||||
const MAX_LOCAL_SUGGESTIONS = 3;
|
||||
const MAX_SUGGESTIONS = 6;
|
||||
// splits data urls into component parts [url, type, data]
|
||||
const dataURLParts = /(?:^data:)(.+)(?:;base64,)(.*)$/;
|
||||
const l10nKeysNames = [
|
||||
"searchHeader",
|
||||
"searchPlaceholder",
|
||||
"searchWithHeader",
|
||||
"searchSettings",
|
||||
"searchForSomethingWith",
|
||||
];
|
||||
|
||||
this.SearchProvider = {
|
||||
// This is used to handle search suggestions. It maps xul:browsers to objects
|
||||
// { controller, previousFormHistoryResult }. See getSuggestions().
|
||||
_suggestionMap: new WeakMap(),
|
||||
|
||||
_searchSuggestionUIStrings: new Map(),
|
||||
|
||||
/**
|
||||
* Makes a copy of the current search suggestions.
|
||||
* @return {Object} Key/value pairs representing the search suggestions.
|
||||
*/
|
||||
get searchSuggestionUIStrings() {
|
||||
let result = Object.create(null);
|
||||
Array.from(this._searchSuggestionUIStrings.entries())
|
||||
.forEach(([key, value]) => result[key] = value);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the state
|
||||
* @return {Promise} Resolves to an object:
|
||||
* engines {Object[]}: list of engines.
|
||||
* currentEngine {Object}: the current engine.
|
||||
*/
|
||||
get state() {
|
||||
return Task.spawn(function* () {
|
||||
let state = {
|
||||
engines: [],
|
||||
currentEngine: yield this.currentEngine,
|
||||
};
|
||||
let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
|
||||
let hiddenList = pref ? pref.split(",") : [];
|
||||
for (let engine of Services.search.getVisibleEngines()) {
|
||||
if (hiddenList.indexOf(engine.name) !== -1) {
|
||||
continue;
|
||||
}
|
||||
let uri = engine.getIconURLBySize(16, 16);
|
||||
state.engines.push({
|
||||
name: engine.name,
|
||||
iconBuffer: yield this._arrayBufferFromDataURL(uri),
|
||||
});
|
||||
}
|
||||
return state;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a browser to peform a search by opening a new window.
|
||||
* @param {XULBrowser} browser The browser that performs the search.
|
||||
* @param {Object} data The data used to perform the search.
|
||||
* @return {Window} win The window that is performing the search.
|
||||
*/
|
||||
performSearch(browser, data) {
|
||||
return Task.spawn(function* () {
|
||||
let engine = Services.search.getEngineByName(data.engineName);
|
||||
let submission = engine.getSubmission(data.searchString, "", data.searchPurpose);
|
||||
// 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.
|
||||
let win = browser.ownerDocument.defaultView;
|
||||
|
||||
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.recordSearchInHealthReport(engine, data.healthReportKey,
|
||||
data.selection || null);
|
||||
|
||||
yield this.addFormHistoryEntry(browser, data.searchString);
|
||||
return win;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current search engine.
|
||||
* @return {Object} An object the describes the current engine.
|
||||
*/
|
||||
get currentEngine() {
|
||||
return Task.spawn(function* () {
|
||||
let engine = Services.search.currentEngine;
|
||||
let favicon = engine.getIconURLBySize(16, 16);
|
||||
let uri1x = engine.getIconURLBySize(65, 26);
|
||||
let uri2x = engine.getIconURLBySize(130, 52);
|
||||
let placeholder = stringBundle.formatStringFromName(
|
||||
"searchWithEngine", [engine.name], 1);
|
||||
let obj = {
|
||||
name: engine.name,
|
||||
placeholder: placeholder,
|
||||
iconBuffer: yield this._arrayBufferFromDataURL(favicon),
|
||||
logoBuffer: yield this._arrayBufferFromDataURL(uri1x),
|
||||
logo2xBuffer: yield this._arrayBufferFromDataURL(uri2x),
|
||||
preconnectOrigin: new URL(engine.searchForm).origin,
|
||||
};
|
||||
return obj;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
getSuggestions: function(browser, data) {
|
||||
return Task.spawn(function* () {
|
||||
let engine = Services.search.getEngineByName(data.engineName);
|
||||
if (!engine) {
|
||||
throw new Error(`Unknown engine name: ${data.engineName}`);
|
||||
}
|
||||
let {
|
||||
controller
|
||||
} = this._suggestionDataForBrowser(browser);
|
||||
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 isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser);
|
||||
|
||||
let suggestions;
|
||||
try {
|
||||
// If fetch() rejects due to it's asynchronous behaviour, the suggestions
|
||||
// is null and is then handled.
|
||||
suggestions = yield controller.fetch(data.searchString, isPrivate, engine);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
let result = null;
|
||||
if (suggestions) {
|
||||
this._suggestionMap.get(browser)
|
||||
.previousFormHistoryResult = suggestions.formHistoryResult;
|
||||
|
||||
result = {
|
||||
engineName: data.engineName,
|
||||
searchString: suggestions.term,
|
||||
formHistory: suggestions.local,
|
||||
remote: suggestions.remote,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
addFormHistoryEntry: function(browser, entry = "") {
|
||||
return Task.spawn(function* () {
|
||||
let isPrivate = false;
|
||||
try {
|
||||
isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser);
|
||||
} catch (err) {
|
||||
// The browser might have already been destroyed.
|
||||
return false;
|
||||
}
|
||||
if (isPrivate || entry === "") {
|
||||
return false;
|
||||
}
|
||||
let {
|
||||
controller
|
||||
} = this._suggestionDataForBrowser(browser);
|
||||
let result = yield new Promise((resolve, reject) => {
|
||||
let ops = {
|
||||
op: "bump",
|
||||
fieldname: controller.formHistoryParam,
|
||||
value: entry,
|
||||
};
|
||||
let callbacks = {
|
||||
handleCompletion: () => resolve(true),
|
||||
handleError: reject,
|
||||
};
|
||||
FormHistory.update(ops, callbacks);
|
||||
});
|
||||
return result;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an entry from the form history for a given browser.
|
||||
*
|
||||
* @param {XULBrowser} browser the browser to delete from.
|
||||
* @param {String} suggestion The suggestion to delete.
|
||||
* @return {Boolean} True if removed, false otherwise.
|
||||
*/
|
||||
removeFormHistoryEntry(browser, suggestion) {
|
||||
let {
|
||||
previousFormHistoryResult
|
||||
} = this._suggestionMap.get(browser);
|
||||
if (!previousFormHistoryResult) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < previousFormHistoryResult.matchCount; i++) {
|
||||
if (previousFormHistoryResult.getValueAt(i) === suggestion) {
|
||||
previousFormHistoryResult.removeValueAt(i, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_suggestionDataForBrowser(browser) {
|
||||
let data = this._suggestionMap.get(browser);
|
||||
if (!data) {
|
||||
// Since one SearchSuggestionController instance is meant to be used per
|
||||
// autocomplete widget, this means that we assume each xul:browser has at
|
||||
// most one such widget.
|
||||
data = {
|
||||
controller: new SearchSuggestionController(),
|
||||
previousFormHistoryResult: undefined,
|
||||
};
|
||||
this._suggestionMap.set(browser, data);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
_arrayBufferFromDataURL(dataURL = "") {
|
||||
if (!dataURL) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let fileReader = Cc["@mozilla.org/files/filereader;1"]
|
||||
.createInstance(Ci.nsIDOMFileReader);
|
||||
let [type, data] = dataURLParts.exec(dataURL).slice(1);
|
||||
let bytes = atob(data);
|
||||
let uInt8Array = new Uint8Array(bytes.length);
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
uInt8Array[i] = bytes.charCodeAt(i);
|
||||
}
|
||||
let blob = new Blob([uInt8Array], {
|
||||
type
|
||||
});
|
||||
fileReader.onload = () => resolve(fileReader.result);
|
||||
fileReader.onerror = () => resolve(null);
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
init() {
|
||||
// Perform localization
|
||||
l10nKeysNames.map(
|
||||
name => [name, searchBundle.GetStringFromName(name)]
|
||||
).forEach(
|
||||
([key, value]) => this._searchSuggestionUIStrings.set(key, value)
|
||||
);
|
||||
}
|
||||
};
|
||||
this.SearchProvider.init();
|
@ -17,4 +17,5 @@ EXTRA_JS_MODULES += [
|
||||
'RemoteDirectoryLinksProvider.jsm',
|
||||
'RemoteNewTabLocation.jsm',
|
||||
'RemoteNewTabUtils.jsm',
|
||||
'SearchProvider.jsm',
|
||||
]
|
||||
|
@ -3,3 +3,4 @@ support-files =
|
||||
dummy_page.html
|
||||
|
||||
[browser_remotenewtab_pageloads.js]
|
||||
[browser_SearchProvider.js]
|
||||
|
@ -0,0 +1,168 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* globals ok, is, Services */
|
||||
"use strict";
|
||||
let imports = {};
|
||||
Components.utils.import("resource:///modules/SearchProvider.jsm", imports);
|
||||
|
||||
// create test engine called MozSearch
|
||||
Services.search.addEngineWithDetails("TestSearch", "", "", "", "GET",
|
||||
"http://example.com/?q={searchTerms}");
|
||||
Services.search.defaultEngine = Services.search.getEngineByName("TestSearch");
|
||||
|
||||
function hasProp(obj) {
|
||||
return function(aProp) {
|
||||
ok(obj.hasOwnProperty(aProp), `expect to have property ${aProp}`);
|
||||
};
|
||||
}
|
||||
|
||||
add_task(function* testState() {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "about:newTab",
|
||||
}, function* () {
|
||||
|
||||
ok(imports.SearchProvider, "Search provider was created");
|
||||
|
||||
// state returns promise and eventually returns a state object
|
||||
var state = yield imports.SearchProvider.state;
|
||||
var stateProps = hasProp(state);
|
||||
["engines", "currentEngine"].forEach(stateProps);
|
||||
|
||||
var {
|
||||
engines
|
||||
} = state;
|
||||
|
||||
// engines should be an iterable
|
||||
var proto = Object.getPrototypeOf(engines);
|
||||
var isIterable = Object.getOwnPropertySymbols(proto)[0] === Symbol.iterator;
|
||||
ok(isIterable, "Engines should be iterable.");
|
||||
|
||||
// current engine should be the current engine from Services.search
|
||||
var {
|
||||
currentEngine
|
||||
} = state;
|
||||
is(currentEngine.name, Services.search.currentEngine.name, "Current engine has been correctly set to default engine");
|
||||
|
||||
// current engine should properties
|
||||
var engineProps = hasProp(currentEngine);
|
||||
["name", "placeholder", "iconBuffer", "logoBuffer", "logo2xBuffer", "preconnectOrigin"].forEach(engineProps);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* testSearch() {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "about:newTab",
|
||||
}, function* () {
|
||||
|
||||
// perform a search
|
||||
var searchData = {
|
||||
engineName: Services.search.currentEngine.name,
|
||||
searchString: "test",
|
||||
healthReportKey: "newtab",
|
||||
searchPurpose: "newtab",
|
||||
originalEvent: {
|
||||
shiftKey: false,
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
button: false,
|
||||
},
|
||||
};
|
||||
|
||||
// adding an entry to the form history will trigger a 'formhistory-add' notification, so we need to wait for
|
||||
// this to resolve before checking that the search string has been added to the suggestions list
|
||||
let addHistoryPromise = new Promise((resolve, reject) => {
|
||||
Services.obs.addObserver(function onAdd(subject, topic, data) { // jshint ignore:line
|
||||
if (data === "formhistory-add") {
|
||||
Services.obs.removeObserver(onAdd, "satchel-storage-changed");
|
||||
resolve(data);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}, "satchel-storage-changed", false);
|
||||
});
|
||||
|
||||
var win = yield imports.SearchProvider.performSearch(gBrowser, searchData);
|
||||
var result = yield new Promise(resolve => {
|
||||
const pageShow = function() {
|
||||
win.gBrowser.tabs[1].linkedBrowser.removeEventListener("pageshow", pageShow);
|
||||
var tab = win.gBrowser.tabContainer.tabbrowser;
|
||||
var url = tab.selectedTab.linkedBrowser.contentWindow.location.href;
|
||||
BrowserTestUtils.removeTab(tab.selectedTab);
|
||||
resolve(url);
|
||||
};
|
||||
|
||||
ok(win.gBrowser.tabs[1], "search opened a new tab");
|
||||
win.gBrowser.tabs[1].linkedBrowser.addEventListener("pageshow", pageShow);
|
||||
});
|
||||
is(result, "http://example.com/?q=test", "should match search URL of default engine.");
|
||||
|
||||
// suggestions has correct properties
|
||||
var suggestionData = {
|
||||
engineName: Services.search.currentEngine.name,
|
||||
searchString: "test",
|
||||
};
|
||||
var suggestions = yield imports.SearchProvider.getSuggestions(gBrowser, suggestionData);
|
||||
var suggestionProps = hasProp(suggestions);
|
||||
["engineName", "searchString", "formHistory", "remote"].forEach(suggestionProps);
|
||||
|
||||
// ensure that the search string has been added to the form history suggestions
|
||||
yield addHistoryPromise;
|
||||
suggestions = yield imports.SearchProvider.getSuggestions(gBrowser, suggestionData);
|
||||
var {
|
||||
formHistory
|
||||
} = suggestions;
|
||||
ok(formHistory.length !== 0, "a form history was created");
|
||||
is(formHistory[0], searchData.searchString, "the search string has been added to form history");
|
||||
|
||||
// remove the entry we just added from the form history and ensure it no longer appears as a suggestion
|
||||
let removeHistoryPromise = new Promise((resolve, reject) => {
|
||||
Services.obs.addObserver(function onAdd(subject, topic, data) { // jshint ignore:line
|
||||
if (data === "formhistory-remove") {
|
||||
Services.obs.removeObserver(onAdd, "satchel-storage-changed");
|
||||
resolve(data);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}, "satchel-storage-changed", false);
|
||||
});
|
||||
|
||||
yield imports.SearchProvider.removeFormHistoryEntry(gBrowser, searchData.searchString);
|
||||
yield removeHistoryPromise;
|
||||
suggestions = yield imports.SearchProvider.getSuggestions(gBrowser, suggestionData);
|
||||
ok(suggestions.formHistory.length === 0, "entry has been removed from form history");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* testFetchFailure() {
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "about:newTab",
|
||||
}, function* () {
|
||||
|
||||
// ensure that the fetch failure is handled when the suggestions return a null value due to their
|
||||
// asynchronous nature
|
||||
let { controller } = imports.SearchProvider._suggestionDataForBrowser(gBrowser);
|
||||
let oldFetch = controller.fetch;
|
||||
controller.fetch = function(searchTerm, privateMode, engine) { //jshint ignore:line
|
||||
let promise = new Promise((resolve, reject) => { //jshint ignore:line
|
||||
reject();
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
// this should throw, since the promise rejected
|
||||
let suggestionData = {
|
||||
engineName: Services.search.currentEngine.name,
|
||||
searchString: "test",
|
||||
};
|
||||
|
||||
let suggestions = yield imports.SearchProvider.getSuggestions(gBrowser, suggestionData);
|
||||
|
||||
ok(suggestions === null, "suggestions returned null and the function handled the rejection");
|
||||
controller.fetch = oldFetch;
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user