mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-16 04:07:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
2
browser/components/newtab/NewTabComponents.manifest
Normal file
2
browser/components/newtab/NewTabComponents.manifest
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
component {dfcd2adc-7867-4d3a-ba70-17501f208142} aboutNewTabService.js
|
||||
contract @mozilla.org/browser/aboutnewtab-service;1 {dfcd2adc-7867-4d3a-ba70-17501f208142}
|
||||
242
browser/components/newtab/NewTabMessages.jsm
Normal file
242
browser/components/newtab/NewTabMessages.jsm
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
/* global
|
||||
NewTabWebChannel,
|
||||
NewTabPrefsProvider,
|
||||
PlacesProvider,
|
||||
PreviewProvider,
|
||||
NewTabSearchProvider,
|
||||
Preferences,
|
||||
XPCOMUtils,
|
||||
Task
|
||||
*/
|
||||
|
||||
/* exported NewTabMessages */
|
||||
|
||||
"use strict";
|
||||
|
||||
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");
|
||||
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?
|
||||
const ACTIONS = {
|
||||
inboundActions: [
|
||||
"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",
|
||||
outPrefs: "RECEIVE_PREFS",
|
||||
},
|
||||
preview: {
|
||||
inThumb: "REQUEST_THUMB",
|
||||
outThumb: "RECEIVE_THUMB",
|
||||
},
|
||||
links: {
|
||||
inFrecent: "REQUEST_FRECENT",
|
||||
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 = {
|
||||
|
||||
_prefs: {},
|
||||
|
||||
/** NEWTAB EVENT HANDLERS **/
|
||||
|
||||
handleContentRequest(actionName, {data, target}) {
|
||||
switch (actionName) {
|
||||
case ACTIONS.prefs.inPrefs:
|
||||
// Return to the originator all newtabpage prefs
|
||||
let results = NewTabPrefsProvider.prefs.newtabPagePrefs;
|
||||
NewTabWebChannel.send(ACTIONS.prefs.outPrefs, results, target);
|
||||
break;
|
||||
case ACTIONS.preview.inThumb:
|
||||
// Return to the originator a preview URL
|
||||
PreviewProvider.getThumbnail(data).then(imgData => {
|
||||
NewTabWebChannel.send(ACTIONS.preview.outThumb, {url: data, imgData}, target);
|
||||
});
|
||||
break;
|
||||
case ACTIONS.links.inFrecent:
|
||||
// Return to the originator the top frecent links
|
||||
PlacesProvider.links.getLinks().then(links => {
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Broadcast places change to all open newtab pages
|
||||
*/
|
||||
handlePlacesChange(type, data) {
|
||||
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
|
||||
*/
|
||||
handlePrefChange(actionName, value) {
|
||||
let prefChange = {};
|
||||
prefChange[actionName] = value;
|
||||
NewTabWebChannel.broadcast(ACTIONS.prefs.outPrefs, prefChange);
|
||||
},
|
||||
|
||||
_handleEnabledChange(prefName, value) {
|
||||
if (prefName === PREF_ENABLED) {
|
||||
if (this._prefs.enabled && !value) {
|
||||
this.uninit();
|
||||
} else if (!this._prefs.enabled && value) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
|
||||
if (this._prefs.enabled) {
|
||||
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);
|
||||
}
|
||||
|
||||
PlacesProvider.links.on("deleteURI", this.handlePlacesChange);
|
||||
PlacesProvider.links.on("clearHistory", this.handlePlacesChange);
|
||||
PlacesProvider.links.on("linkChanged", this.handlePlacesChange);
|
||||
PlacesProvider.links.on("manyLinksChanged", this.handlePlacesChange);
|
||||
}
|
||||
},
|
||||
|
||||
uninit() {
|
||||
this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
|
||||
NewTabPrefsProvider.prefs.off(pref, this.handlePrefChange);
|
||||
}
|
||||
}
|
||||
|
||||
NewTabPrefsProvider.prefs.uninit();
|
||||
NewTabSearchProvider.search.uninit();
|
||||
NewTabWebChannel.uninit();
|
||||
}
|
||||
};
|
||||
114
browser/components/newtab/NewTabPrefsProvider.jsm
Normal file
114
browser/components/newtab/NewTabPrefsProvider.jsm
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/* global Services, Preferences, EventEmitter, XPCOMUtils */
|
||||
/* exported NewTabPrefsProvider */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["NewTabPrefsProvider"];
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
|
||||
const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
|
||||
return EventEmitter;
|
||||
});
|
||||
|
||||
// Supported prefs and data type
|
||||
const gPrefsMap = new Map([
|
||||
["browser.newtabpage.remote", "bool"],
|
||||
["browser.newtabpage.remote.mode", "str"],
|
||||
["browser.newtabpage.remote.version", "str"],
|
||||
["browser.newtabpage.enabled", "bool"],
|
||||
["browser.newtabpage.enhanced", "bool"],
|
||||
["browser.newtabpage.introShown", "bool"],
|
||||
["browser.newtabpage.updateIntroShown", "bool"],
|
||||
["browser.newtabpage.pinned", "str"],
|
||||
["browser.newtabpage.blocked", "str"],
|
||||
["intl.locale.matchOS", "bool"],
|
||||
["general.useragent.locale", "localized"],
|
||||
["browser.search.hiddenOneOffs", "str"],
|
||||
]);
|
||||
|
||||
// prefs that are important for the newtab page
|
||||
const gNewtabPagePrefs = new Set([
|
||||
"browser.newtabpage.enabled",
|
||||
"browser.newtabpage.enhanced",
|
||||
"browser.newtabpage.pinned",
|
||||
"browser.newtabpage.blocked",
|
||||
"browser.newtabpage.introShown",
|
||||
"browser.newtabpage.updateIntroShown",
|
||||
"browser.search.hiddenOneOffs",
|
||||
]);
|
||||
|
||||
let PrefsProvider = function PrefsProvider() {
|
||||
EventEmitter.decorate(this);
|
||||
};
|
||||
|
||||
PrefsProvider.prototype = {
|
||||
|
||||
observe(subject, topic, data) { // jshint ignore:line
|
||||
if (topic === "nsPref:changed") {
|
||||
if (gPrefsMap.has(data)) {
|
||||
switch (gPrefsMap.get(data)) {
|
||||
case "bool":
|
||||
this.emit(data, Preferences.get(data, false));
|
||||
break;
|
||||
case "str":
|
||||
this.emit(data, Preferences.get(data, ""));
|
||||
break;
|
||||
case "localized":
|
||||
try {
|
||||
this.emit(data, Preferences.get(data, "", Ci.nsIPrefLocalizedString));
|
||||
} catch (e) {
|
||||
this.emit(data, Preferences.get(data, ""));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.emit(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Cu.reportError(new Error("NewTabPrefsProvider observing unknown topic"));
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Return the preferences that are important to the newtab page
|
||||
*/
|
||||
get newtabPagePrefs() {
|
||||
let results = {};
|
||||
for (let pref of gNewtabPagePrefs) {
|
||||
results[pref] = Preferences.get(pref, null);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
get prefsMap() {
|
||||
return gPrefsMap;
|
||||
},
|
||||
|
||||
init() {
|
||||
for (let pref of gPrefsMap.keys()) {
|
||||
Services.prefs.addObserver(pref, this, false);
|
||||
}
|
||||
},
|
||||
|
||||
uninit() {
|
||||
for (let pref of gPrefsMap.keys()) {
|
||||
Services.prefs.removeObserver(pref, this, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton that serves as the default new tab pref provider for the grid.
|
||||
*/
|
||||
const gPrefs = new PrefsProvider();
|
||||
|
||||
let NewTabPrefsProvider = {
|
||||
prefs: gPrefs,
|
||||
newtabPagePrefSet: gNewtabPagePrefs,
|
||||
};
|
||||
15
browser/components/newtab/NewTabRemoteResources.jsm
Normal file
15
browser/components/newtab/NewTabRemoteResources.jsm
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* exported NewTabRemoteResources */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["NewTabRemoteResources"];
|
||||
|
||||
const NewTabRemoteResources = {
|
||||
MODE_CHANNEL_MAP: {
|
||||
production: {origin: "https://content.cdn.mozilla.net"},
|
||||
staging: {origin: "https://s3_proxy_tiles.stage.mozaws.net"},
|
||||
test: {origin: "https://example.com"},
|
||||
test2: {origin: "http://mochi.test:8888"},
|
||||
dev: {origin: "http://localhost:8888"}
|
||||
}
|
||||
};
|
||||
103
browser/components/newtab/NewTabSearchProvider.jsm
Normal file
103
browser/components/newtab/NewTabSearchProvider.jsm
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* 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
|
||||
// all other topics are not relevant to content searches and can be
|
||||
// ignored by NewTabSearchProvider
|
||||
if (data === "engine-current" && 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));
|
||||
}
|
||||
},
|
||||
|
||||
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.ownerGlobal;
|
||||
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(),
|
||||
};
|
||||
36
browser/components/newtab/NewTabURL.jsm
Normal file
36
browser/components/newtab/NewTabURL.jsm
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* 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, aboutNewTabService*/
|
||||
/* exported NewTabURL */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["NewTabURL"];
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
|
||||
"@mozilla.org/browser/aboutnewtab-service;1",
|
||||
"nsIAboutNewTabService");
|
||||
|
||||
this.NewTabURL = {
|
||||
|
||||
get: function() {
|
||||
return aboutNewTabService.newTabURL;
|
||||
},
|
||||
|
||||
get overridden() {
|
||||
return aboutNewTabService.overridden;
|
||||
},
|
||||
|
||||
override: function(newURL) {
|
||||
aboutNewTabService.newTabURL = newURL;
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
aboutNewTabService.resetNewTabURL();
|
||||
}
|
||||
};
|
||||
299
browser/components/newtab/NewTabWebChannel.jsm
Normal file
299
browser/components/newtab/NewTabWebChannel.jsm
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
/* global
|
||||
NewTabPrefsProvider,
|
||||
Services,
|
||||
EventEmitter,
|
||||
Preferences,
|
||||
XPCOMUtils,
|
||||
WebChannel,
|
||||
NewTabRemoteResources
|
||||
*/
|
||||
/* exported NewTabWebChannel */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["NewTabWebChannel"];
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
|
||||
"resource:///modules/NewTabPrefsProvider.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
|
||||
"resource:///modules/NewTabRemoteResources.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
|
||||
"resource://gre/modules/WebChannel.jsm");
|
||||
XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
|
||||
const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
|
||||
return EventEmitter;
|
||||
});
|
||||
|
||||
const CHAN_ID = "newtab";
|
||||
const PREF_ENABLED = "browser.newtabpage.remote";
|
||||
const PREF_MODE = "browser.newtabpage.remote.mode";
|
||||
|
||||
/**
|
||||
* NewTabWebChannel is the conduit for all communication with unprivileged newtab instances.
|
||||
*
|
||||
* It allows for the ability to broadcast to all newtab browsers.
|
||||
* If the browser.newtab.remote pref is false, the object will be in an uninitialized state.
|
||||
*
|
||||
* Mode choices:
|
||||
* 'production': pages from our production CDN
|
||||
* 'staging': pages from our staging CDN
|
||||
* 'test': intended for tests
|
||||
* 'test2': intended for tests
|
||||
* 'dev': intended for development
|
||||
*
|
||||
* An unknown mode will result in 'production' mode, which is the default
|
||||
*
|
||||
* Incoming messages are expected to be JSON-serialized and in the format:
|
||||
*
|
||||
* {
|
||||
* type: "REQUEST_SCREENSHOT",
|
||||
* data: {
|
||||
* url: "https://example.com"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Or:
|
||||
*
|
||||
* {
|
||||
* type: "REQUEST_SCREENSHOT",
|
||||
* }
|
||||
*
|
||||
* Outgoing messages are expected to be objects serializable by structured cloning, in a similar format:
|
||||
* {
|
||||
* type: "RECEIVE_SCREENSHOT",
|
||||
* data: {
|
||||
* "url": "https://example.com",
|
||||
* "image": "dataURi:....."
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
let NewTabWebChannelImpl = function NewTabWebChannelImpl() {
|
||||
EventEmitter.decorate(this);
|
||||
this._handlePrefChange = this._handlePrefChange.bind(this);
|
||||
this._incomingMessage = this._incomingMessage.bind(this);
|
||||
};
|
||||
|
||||
NewTabWebChannelImpl.prototype = {
|
||||
_prefs: {},
|
||||
_channel: null,
|
||||
|
||||
// a WeakMap containing browsers as keys and a weak ref to their principal
|
||||
// as value
|
||||
_principals: null,
|
||||
|
||||
// a Set containing weak refs to browsers
|
||||
_browsers: null,
|
||||
|
||||
/*
|
||||
* Returns current channel's ID
|
||||
*/
|
||||
get chanId() {
|
||||
return CHAN_ID;
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns the number of browsers currently tracking
|
||||
*/
|
||||
get numBrowsers() {
|
||||
return this._getBrowserRefs().length;
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns current channel's origin
|
||||
*/
|
||||
get origin() {
|
||||
if (!(this._prefs.mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
|
||||
this._prefs.mode = "production";
|
||||
}
|
||||
return NewTabRemoteResources.MODE_CHANNEL_MAP[this._prefs.mode].origin;
|
||||
},
|
||||
|
||||
/*
|
||||
* Unloads all browsers and principals
|
||||
*/
|
||||
_unloadAll() {
|
||||
if (this._principals != null) {
|
||||
this._principals = new WeakMap();
|
||||
}
|
||||
this._browsers = new Set();
|
||||
this.emit("targetUnloadAll");
|
||||
},
|
||||
|
||||
/*
|
||||
* Checks if a browser is known
|
||||
*
|
||||
* This will cause an iteration through all known browsers.
|
||||
* That's ok, we don't expect a lot of browsers
|
||||
*/
|
||||
_isBrowserKnown(browser) {
|
||||
for (let bRef of this._getBrowserRefs()) {
|
||||
let b = bRef.get();
|
||||
if (b && b.permanentKey === browser.permanentKey) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/*
|
||||
* Obtains all known browser refs
|
||||
*/
|
||||
_getBrowserRefs() {
|
||||
// Some code may try to emit messages after teardown.
|
||||
if (!this._browsers) {
|
||||
return [];
|
||||
}
|
||||
let refs = [];
|
||||
for (let bRef of this._browsers) {
|
||||
/*
|
||||
* even though we hold a weak ref to browser, it seems that browser
|
||||
* objects aren't gc'd immediately after a tab closes. They stick around
|
||||
* in memory, but thankfully they don't have a documentURI in that case
|
||||
*/
|
||||
let browser = bRef.get();
|
||||
if (browser && browser.documentURI) {
|
||||
refs.push(bRef);
|
||||
} else {
|
||||
// need to clean up principals because the browser object is not gc'ed
|
||||
// immediately
|
||||
this._principals.delete(browser);
|
||||
this._browsers.delete(bRef);
|
||||
this.emit("targetUnload");
|
||||
}
|
||||
}
|
||||
return refs;
|
||||
},
|
||||
|
||||
/*
|
||||
* Receives a message from content.
|
||||
*
|
||||
* Keeps track of browsers for broadcast, relays messages to listeners.
|
||||
*/
|
||||
_incomingMessage(id, message, target) {
|
||||
if (this.chanId !== id) {
|
||||
Cu.reportError(new Error("NewTabWebChannel unexpected message destination"));
|
||||
}
|
||||
|
||||
/*
|
||||
* need to differentiate by browser, because event targets are created each
|
||||
* time a message is sent.
|
||||
*/
|
||||
if (!this._isBrowserKnown(target.browser)) {
|
||||
this._browsers.add(Cu.getWeakReference(target.browser));
|
||||
this._principals.set(target.browser, Cu.getWeakReference(target.principal));
|
||||
this.emit("targetAdd");
|
||||
}
|
||||
|
||||
try {
|
||||
let msg = JSON.parse(message);
|
||||
this.emit(msg.type, {data: msg.data, target: target});
|
||||
} catch (err) {
|
||||
Cu.reportError(err);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Sends a message to all known browsers
|
||||
*/
|
||||
broadcast(actionType, message) {
|
||||
for (let bRef of this._getBrowserRefs()) {
|
||||
let browser = bRef.get();
|
||||
try {
|
||||
let principal = this._principals.get(browser).get();
|
||||
if (principal && browser && browser.documentURI) {
|
||||
this._channel.send({type: actionType, data: message}, {browser, principal});
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(new Error("NewTabWebChannel WeakRef is dead"));
|
||||
this._principals.delete(browser);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Sends a message to a specific target
|
||||
*/
|
||||
send(actionType, message, target) {
|
||||
try {
|
||||
this._channel.send({type: actionType, data: message}, target);
|
||||
} catch (e) {
|
||||
// Web Channel might be dead
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Pref change observer callback
|
||||
*/
|
||||
_handlePrefChange(prefName, newState, forceState) { // eslint-disable-line no-unused-vars
|
||||
switch (prefName) {
|
||||
case PREF_ENABLED:
|
||||
if (!this._prefs.enabled && newState) {
|
||||
// changing state from disabled to enabled
|
||||
this.setupState();
|
||||
} else if (this._prefs.enabled && !newState) {
|
||||
// changing state from enabled to disabled
|
||||
this.tearDownState();
|
||||
}
|
||||
break;
|
||||
case PREF_MODE:
|
||||
if (this._prefs.mode !== newState) {
|
||||
// changing modes
|
||||
this.tearDownState();
|
||||
this.setupState();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Sets up the internal state
|
||||
*/
|
||||
setupState() {
|
||||
this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
|
||||
|
||||
let mode = Preferences.get(PREF_MODE, "production");
|
||||
if (!(mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
|
||||
mode = "production";
|
||||
}
|
||||
this._prefs.mode = mode;
|
||||
this._principals = new WeakMap();
|
||||
this._browsers = new Set();
|
||||
|
||||
if (this._prefs.enabled) {
|
||||
this._channel = new WebChannel(this.chanId, Services.io.newURI(this.origin, null, null));
|
||||
this._channel.listen(this._incomingMessage);
|
||||
}
|
||||
},
|
||||
|
||||
tearDownState() {
|
||||
if (this._channel) {
|
||||
this._channel.stopListening();
|
||||
}
|
||||
this._prefs = {};
|
||||
this._unloadAll();
|
||||
this._channel = null;
|
||||
this._principals = null;
|
||||
this._browsers = null;
|
||||
},
|
||||
|
||||
init() {
|
||||
this.setupState();
|
||||
NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handlePrefChange);
|
||||
NewTabPrefsProvider.prefs.on(PREF_MODE, this._handlePrefChange);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
this.tearDownState();
|
||||
NewTabPrefsProvider.prefs.off(PREF_ENABLED, this._handlePrefChange);
|
||||
NewTabPrefsProvider.prefs.off(PREF_MODE, this._handlePrefChange);
|
||||
}
|
||||
};
|
||||
|
||||
let NewTabWebChannel = new NewTabWebChannelImpl();
|
||||
211
browser/components/newtab/PlacesProvider.jsm
Normal file
211
browser/components/newtab/PlacesProvider.jsm
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/* 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/. */
|
||||
|
||||
/* global XPCOMUtils, Services, PlacesUtils, EventEmitter */
|
||||
/* global gLinks */
|
||||
/* exported PlacesProvider */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["PlacesProvider"];
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
|
||||
const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
|
||||
return EventEmitter;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
|
||||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
|
||||
// The maximum number of results PlacesProvider retrieves from history.
|
||||
const HISTORY_RESULTS_LIMIT = 100;
|
||||
|
||||
/* Queries history to retrieve the most visited sites. Emits events when the
|
||||
* history changes.
|
||||
* Implements the EventEmitter interface.
|
||||
*/
|
||||
let Links = function Links() {
|
||||
EventEmitter.decorate(this);
|
||||
};
|
||||
|
||||
Links.prototype = {
|
||||
/**
|
||||
* Set this to change the maximum number of links the provider will provide.
|
||||
*/
|
||||
get maxNumLinks() {
|
||||
// getter, so it can't be replaced dynamically
|
||||
return HISTORY_RESULTS_LIMIT;
|
||||
},
|
||||
|
||||
/**
|
||||
* A set of functions called by @mozilla.org/browser/nav-historyservice
|
||||
* All history events are emitted from this object.
|
||||
*/
|
||||
historyObserver: {
|
||||
onDeleteURI: function historyObserver_onDeleteURI(aURI) {
|
||||
// let observers remove sensetive data associated with deleted visit
|
||||
gLinks.emit("deleteURI", {
|
||||
url: aURI.spec,
|
||||
});
|
||||
},
|
||||
|
||||
onClearHistory: function historyObserver_onClearHistory() {
|
||||
gLinks.emit("clearHistory");
|
||||
},
|
||||
|
||||
onFrecencyChanged: function historyObserver_onFrecencyChanged(aURI,
|
||||
aNewFrecency, aGUID, aHidden, aLastVisitDate) { // jshint ignore:line
|
||||
// The implementation of the query in getLinks excludes hidden and
|
||||
// unvisited pages, so it's important to exclude them here, too.
|
||||
if (!aHidden && aLastVisitDate &&
|
||||
NewTabUtils.linkChecker.checkLoadURI(aURI.spec)) {
|
||||
gLinks.emit("linkChanged", {
|
||||
url: aURI.spec,
|
||||
frecency: aNewFrecency,
|
||||
lastVisitDate: aLastVisitDate,
|
||||
type: "history",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onManyFrecenciesChanged: function historyObserver_onManyFrecenciesChanged() {
|
||||
// Called when frecencies are invalidated and also when clearHistory is called
|
||||
// See toolkit/components/places/tests/unit/test_frecency_observers.js
|
||||
gLinks.emit("manyLinksChanged");
|
||||
},
|
||||
|
||||
onTitleChanged: function historyObserver_onTitleChanged(aURI, aNewTitle) {
|
||||
if (NewTabUtils.linkChecker.checkLoadURI(aURI.spec)) {
|
||||
gLinks.emit("linkChanged", {
|
||||
url: aURI.spec,
|
||||
title: aNewTitle
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
|
||||
Ci.nsISupportsWeakReference])
|
||||
},
|
||||
|
||||
/**
|
||||
* Must be called before the provider is used.
|
||||
* Makes it easy to disable under pref
|
||||
*/
|
||||
init: function PlacesProvider_init() {
|
||||
try {
|
||||
PlacesUtils.history.addObserver(this.historyObserver, true);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current set of links delivered by this provider.
|
||||
*
|
||||
* @returns {Promise} Returns a promise with the array of links as payload.
|
||||
*/
|
||||
getLinks: Task.async(function*() {
|
||||
// Select a single page per host with highest frecency, highest recency.
|
||||
// Choose N top such pages. Note +rev_host, to turn off optimizer per :mak
|
||||
// suggestion.
|
||||
let sqlQuery = `SELECT url, title, frecency,
|
||||
last_visit_date as lastVisitDate,
|
||||
"history" as type
|
||||
FROM moz_places
|
||||
WHERE frecency in (
|
||||
SELECT MAX(frecency) as frecency
|
||||
FROM moz_places
|
||||
WHERE hidden = 0 AND last_visit_date NOTNULL
|
||||
GROUP BY +rev_host
|
||||
ORDER BY frecency DESC
|
||||
LIMIT :limit
|
||||
)
|
||||
GROUP BY rev_host HAVING MAX(lastVisitDate)
|
||||
ORDER BY frecency DESC, lastVisitDate DESC, url`;
|
||||
|
||||
let links = yield this.executePlacesQuery(sqlQuery, {
|
||||
columns: ["url", "title", "lastVisitDate", "frecency", "type"],
|
||||
params: {limit: this.maxNumLinks}
|
||||
});
|
||||
|
||||
return links.filter(link => NewTabUtils.linkChecker.checkLoadURI(link.url));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Executes arbitrary query against places database
|
||||
*
|
||||
* @param {String} aSql
|
||||
* SQL query to execute
|
||||
* @param {Object} [optional] aOptions
|
||||
* aOptions.columns - an array of column names. if supplied the returned
|
||||
* items will consist of objects keyed on column names. Otherwise
|
||||
* an array of raw values is returned in the select order
|
||||
* aOptions.param - an object of SQL binding parameters
|
||||
* aOptions.callback - a callback to handle query rows
|
||||
*
|
||||
* @returns {Promise} Returns a promise with the array of retrieved items
|
||||
*/
|
||||
executePlacesQuery: Task.async(function*(aSql, aOptions={}) {
|
||||
let {columns, params, callback} = aOptions;
|
||||
let items = [];
|
||||
let queryError = null;
|
||||
let conn = yield PlacesUtils.promiseDBConnection();
|
||||
yield conn.executeCached(aSql, params, aRow => {
|
||||
try {
|
||||
// check if caller wants to handle query raws
|
||||
if (callback) {
|
||||
callback(aRow);
|
||||
}
|
||||
// otherwise fill in the item and add items array
|
||||
else {
|
||||
let item = null;
|
||||
// if columns array is given construct an object
|
||||
if (columns && Array.isArray(columns)) {
|
||||
item = {};
|
||||
columns.forEach(column => {
|
||||
item[column] = aRow.getResultByName(column);
|
||||
});
|
||||
} else {
|
||||
// if no columns - make an array of raw values
|
||||
item = [];
|
||||
for (let i = 0; i < aRow.numEntries; i++) {
|
||||
item.push(aRow.getResultByIndex(i));
|
||||
}
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
} catch (e) {
|
||||
queryError = e;
|
||||
throw StopIteration;
|
||||
}
|
||||
});
|
||||
if (queryError) {
|
||||
throw new Error(queryError);
|
||||
}
|
||||
return items;
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton that serves as the default link provider for the grid.
|
||||
*/
|
||||
const gLinks = new Links(); // jshint ignore:line
|
||||
|
||||
let PlacesProvider = {
|
||||
links: gLinks,
|
||||
};
|
||||
|
||||
// Kept only for backwards-compatibility
|
||||
XPCOMUtils.defineLazyGetter(PlacesProvider, "LinkChecker",
|
||||
() => NewTabUtils.linkChecker);
|
||||
|
||||
49
browser/components/newtab/PreviewProvider.jsm
Normal file
49
browser/components/newtab/PreviewProvider.jsm
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* global XPCOMUtils, BackgroundPageThumbs, FileUtils, PageThumbsStorage, Task, MIMEService */
|
||||
/* exported PreviewProvider */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["PreviewProvider"];
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
const {OS} = Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "BackgroundPageThumbs",
|
||||
"resource://gre/modules/BackgroundPageThumbs.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "MIMEService",
|
||||
"@mozilla.org/mime;1", "nsIMIMEService");
|
||||
|
||||
let PreviewProvider = {
|
||||
/**
|
||||
* Returns a thumbnail as a data URI for a url, creating it if necessary
|
||||
*
|
||||
* @param {String} url
|
||||
* a url to obtain a thumbnail for
|
||||
* @return {Promise} A Promise that resolves with a base64 encoded thumbnail
|
||||
*/
|
||||
getThumbnail: Task.async(function* PreviewProvider_getThumbnail(url) {
|
||||
try {
|
||||
yield BackgroundPageThumbs.captureIfMissing(url);
|
||||
let imgPath = PageThumbsStorage.getFilePathForURL(url);
|
||||
|
||||
// OS.File object used to easily read off-thread
|
||||
let file = yield OS.File.open(imgPath, {read: true, existing: true});
|
||||
|
||||
// nsIFile object needed for MIMEService
|
||||
let nsFile = FileUtils.File(imgPath);
|
||||
|
||||
let contentType = MIMEService.getTypeFromFile(nsFile);
|
||||
let bytes = yield file.read();
|
||||
let encodedData = btoa(String.fromCharCode.apply(null, bytes));
|
||||
file.close();
|
||||
return `data:${contentType};base64,${encodedData}`;
|
||||
} catch (err) {
|
||||
Cu.reportError(`PreviewProvider_getThumbnail error: ${err}`);
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
};
|
||||
289
browser/components/newtab/aboutNewTabService.js
Normal file
289
browser/components/newtab/aboutNewTabService.js
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* 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, NewTabPrefsProvider, Services,
|
||||
Locale, UpdateUtils, NewTabRemoteResources
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu, interfaces: Ci} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
|
||||
"resource:///modules/NewTabPrefsProvider.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
|
||||
"resource://gre/modules/Locale.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
|
||||
"resource:///modules/NewTabRemoteResources.jsm");
|
||||
|
||||
const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml";
|
||||
|
||||
const REMOTE_NEWTAB_PATH = "/newtab/v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
|
||||
|
||||
const ABOUT_URL = "about:newtab";
|
||||
|
||||
// Pref that tells if remote newtab is enabled
|
||||
const PREF_REMOTE_ENABLED = "browser.newtabpage.remote";
|
||||
|
||||
// Pref branch necesssary for testing
|
||||
const PREF_REMOTE_CS_TEST = "browser.newtabpage.remote.content-signing-test";
|
||||
|
||||
// The preference that tells whether to match the OS locale
|
||||
const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
|
||||
|
||||
// The preference that tells what locale the user selected
|
||||
const PREF_SELECTED_LOCALE = "general.useragent.locale";
|
||||
|
||||
// The preference that tells what remote mode is enabled.
|
||||
const PREF_REMOTE_MODE = "browser.newtabpage.remote.mode";
|
||||
|
||||
// The preference that tells which remote version is expected.
|
||||
const PREF_REMOTE_VERSION = "browser.newtabpage.remote.version";
|
||||
|
||||
const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
|
||||
|
||||
function AboutNewTabService() {
|
||||
NewTabPrefsProvider.prefs.on(PREF_REMOTE_ENABLED, this._handleToggleEvent.bind(this));
|
||||
|
||||
this._updateRemoteMaybe = this._updateRemoteMaybe.bind(this);
|
||||
|
||||
// trigger remote change if needed, according to pref
|
||||
this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED));
|
||||
}
|
||||
|
||||
/*
|
||||
* A service that allows for the overriding, at runtime, of the newtab page's url.
|
||||
* Additionally, the service manages pref state between a remote and local newtab page.
|
||||
*
|
||||
* There is tight coupling with browser/about/AboutRedirector.cpp.
|
||||
*
|
||||
* 1. Browser chrome access:
|
||||
*
|
||||
* When the user issues a command to open a new tab page, usually clicking a button
|
||||
* in the browser chrome or using shortcut keys, the browser chrome code invokes the
|
||||
* service to obtain the newtab URL. It then loads that URL in a new tab.
|
||||
*
|
||||
* When not overridden, the default URL emitted by the service is "about:newtab".
|
||||
* When overridden, it returns the overriden URL.
|
||||
*
|
||||
* 2. Redirector Access:
|
||||
*
|
||||
* When the URL loaded is about:newtab, the default behavior, or when entered in the
|
||||
* URL bar, the redirector is hit. The service is then called to return either of
|
||||
* two URLs, a chrome or remote one, based on the browser.newtabpage.remote pref.
|
||||
*
|
||||
* NOTE: "about:newtab" will always result in a default newtab page, and never an overridden URL.
|
||||
*
|
||||
* Access patterns:
|
||||
*
|
||||
* The behavior is different when accessing the service via browser chrome or via redirector
|
||||
* largely to maintain compatibility with expectations of add-on developers.
|
||||
*
|
||||
* Loading a chrome resource, or an about: URL in the redirector with either the
|
||||
* LOAD_NORMAL or LOAD_REPLACE flags yield unexpected behaviors, so a roundtrip
|
||||
* to the redirector from browser chrome is avoided.
|
||||
*/
|
||||
AboutNewTabService.prototype = {
|
||||
|
||||
_newTabURL: ABOUT_URL,
|
||||
_remoteEnabled: false,
|
||||
_remoteURL: null,
|
||||
_overridden: false,
|
||||
|
||||
classID: Components.ID("{dfcd2adc-7867-4d3a-ba70-17501f208142}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutNewTabService]),
|
||||
_xpcom_categories: [{
|
||||
service: true
|
||||
}],
|
||||
|
||||
_handleToggleEvent(prefName, stateEnabled, forceState) { // jshint unused:false
|
||||
if (this.toggleRemote(stateEnabled, forceState)) {
|
||||
Services.obs.notifyObservers(null, "newtab-url-changed", ABOUT_URL);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* React to changes to the remote newtab pref.
|
||||
*
|
||||
* If browser.newtabpage.remote is true, this will change the default URL to the
|
||||
* remote newtab page URL. If browser.newtabpage.remote is false, the default URL
|
||||
* will be a local chrome URL.
|
||||
*
|
||||
* This will only act if there is a change of state and if not overridden.
|
||||
*
|
||||
* @returns {Boolean} Returns if there has been a state change
|
||||
*
|
||||
* @param {Boolean} stateEnabled remote state to set to
|
||||
* @param {Boolean} forceState force state change
|
||||
*/
|
||||
toggleRemote(stateEnabled, forceState) {
|
||||
|
||||
if (!forceState && (this._overriden || stateEnabled === this._remoteEnabled)) {
|
||||
// exit there is no change of state
|
||||
return false;
|
||||
}
|
||||
|
||||
let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
|
||||
if (stateEnabled) {
|
||||
if (!csTest) {
|
||||
this._remoteURL = this.generateRemoteURL();
|
||||
} else {
|
||||
this._remoteURL = this._newTabURL;
|
||||
}
|
||||
NewTabPrefsProvider.prefs.on(
|
||||
PREF_SELECTED_LOCALE,
|
||||
this._updateRemoteMaybe);
|
||||
NewTabPrefsProvider.prefs.on(
|
||||
PREF_MATCH_OS_LOCALE,
|
||||
this._updateRemoteMaybe);
|
||||
NewTabPrefsProvider.prefs.on(
|
||||
PREF_REMOTE_MODE,
|
||||
this._updateRemoteMaybe);
|
||||
NewTabPrefsProvider.prefs.on(
|
||||
PREF_REMOTE_VERSION,
|
||||
this._updateRemoteMaybe);
|
||||
this._remoteEnabled = true;
|
||||
} else {
|
||||
NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateRemoteMaybe);
|
||||
NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateRemoteMaybe);
|
||||
NewTabPrefsProvider.prefs.off(PREF_REMOTE_MODE, this._updateRemoteMaybe);
|
||||
NewTabPrefsProvider.prefs.off(PREF_REMOTE_VERSION, this._updateRemoteMaybe);
|
||||
this._remoteEnabled = false;
|
||||
}
|
||||
if (!csTest) {
|
||||
this._newTabURL = ABOUT_URL;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/*
|
||||
* Generate a default url based on remote mode, version, locale and update channel
|
||||
*/
|
||||
generateRemoteURL() {
|
||||
let releaseName = this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
|
||||
let path = REMOTE_NEWTAB_PATH
|
||||
.replace("%VERSION%", this.remoteVersion)
|
||||
.replace("%LOCALE%", Locale.getLocale())
|
||||
.replace("%CHANNEL%", releaseName);
|
||||
let mode = Services.prefs.getCharPref(PREF_REMOTE_MODE, "production");
|
||||
if (!(mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
|
||||
mode = "production";
|
||||
}
|
||||
return NewTabRemoteResources.MODE_CHANNEL_MAP[mode].origin + path;
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns the default URL.
|
||||
*
|
||||
* This URL only depends on the browser.newtabpage.remote pref. Overriding
|
||||
* the newtab page has no effect on the result of this function.
|
||||
*
|
||||
* The result is also the remote URL if this is in a test (PREF_REMOTE_CS_TEST)
|
||||
*
|
||||
* @returns {String} the default newtab URL, remote or local depending on browser.newtabpage.remote
|
||||
*/
|
||||
get defaultURL() {
|
||||
let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
|
||||
if (this._remoteEnabled || csTest) {
|
||||
return this._remoteURL;
|
||||
}
|
||||
return LOCAL_NEWTAB_URL;
|
||||
},
|
||||
|
||||
/*
|
||||
* Updates the remote location when the page is not overriden.
|
||||
*
|
||||
* Useful when there is a dependent pref change
|
||||
*/
|
||||
_updateRemoteMaybe() {
|
||||
if (!this._remoteEnabled || this._overridden) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = this.generateRemoteURL();
|
||||
if (url !== this._remoteURL) {
|
||||
this._remoteURL = url;
|
||||
Services.obs.notifyObservers(null, "newtab-url-changed",
|
||||
this._remoteURL);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the release name from an Update Channel name
|
||||
*
|
||||
* @returns {String} a release name based on the update channel. Defaults to nightly
|
||||
*/
|
||||
releaseFromUpdateChannel(channelName) {
|
||||
return VALID_CHANNELS.has(channelName) ? channelName : "nightly";
|
||||
},
|
||||
|
||||
get newTabURL() {
|
||||
return this._newTabURL;
|
||||
},
|
||||
|
||||
get remoteVersion() {
|
||||
return Services.prefs.getCharPref(PREF_REMOTE_VERSION, "1");
|
||||
},
|
||||
|
||||
get remoteReleaseName() {
|
||||
return this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
|
||||
},
|
||||
|
||||
set newTabURL(aNewTabURL) {
|
||||
let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
|
||||
aNewTabURL = aNewTabURL.trim();
|
||||
if (aNewTabURL === ABOUT_URL) {
|
||||
// avoid infinite redirects in case one sets the URL to about:newtab
|
||||
this.resetNewTabURL();
|
||||
return;
|
||||
} else if (aNewTabURL === "") {
|
||||
aNewTabURL = "about:blank";
|
||||
}
|
||||
let remoteURL = this.generateRemoteURL();
|
||||
let prefRemoteEnabled = Services.prefs.getBoolPref(PREF_REMOTE_ENABLED);
|
||||
let isResetLocal = !prefRemoteEnabled && aNewTabURL === LOCAL_NEWTAB_URL;
|
||||
let isResetRemote = prefRemoteEnabled && aNewTabURL === remoteURL;
|
||||
|
||||
if (isResetLocal || isResetRemote) {
|
||||
if (this._overriden && !csTest) {
|
||||
// only trigger a reset if previously overridden and this is no test
|
||||
this.resetNewTabURL();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// turn off remote state if needed
|
||||
if (!csTest) {
|
||||
this.toggleRemote(false);
|
||||
} else {
|
||||
// if this is a test, we want the remoteURL to be set
|
||||
this._remoteURL = aNewTabURL;
|
||||
}
|
||||
this._newTabURL = aNewTabURL;
|
||||
this._overridden = true;
|
||||
Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
|
||||
},
|
||||
|
||||
get overridden() {
|
||||
return this._overridden;
|
||||
},
|
||||
|
||||
get remoteEnabled() {
|
||||
return this._remoteEnabled;
|
||||
},
|
||||
|
||||
resetNewTabURL() {
|
||||
this._overridden = false;
|
||||
this._newTabURL = ABOUT_URL;
|
||||
this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED), true);
|
||||
Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
|
||||
}
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutNewTabService]);
|
||||
33
browser/components/newtab/moz.build
Normal file
33
browser/components/newtab/moz.build
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
'tests/xpcshell/xpcshell.ini',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'NewTabMessages.jsm',
|
||||
'NewTabPrefsProvider.jsm',
|
||||
'NewTabRemoteResources.jsm',
|
||||
'NewTabSearchProvider.jsm',
|
||||
'NewTabURL.jsm',
|
||||
'NewTabWebChannel.jsm',
|
||||
'PlacesProvider.jsm',
|
||||
'PreviewProvider.jsm'
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIAboutNewTabService.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'browser-newtab'
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'aboutNewTabService.js',
|
||||
'NewTabComponents.manifest',
|
||||
]
|
||||
63
browser/components/newtab/nsIAboutNewTabService.idl
Normal file
63
browser/components/newtab/nsIAboutNewTabService.idl
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* Allows to override about:newtab to point to a different location
|
||||
* than the one specified within AboutRedirector.cpp
|
||||
*/
|
||||
|
||||
[scriptable, uuid(dfcd2adc-7867-4d3a-ba70-17501f208142)]
|
||||
interface nsIAboutNewTabService : nsISupports
|
||||
{
|
||||
/**
|
||||
* Returns the url of the resource for the newtab page if not overridden,
|
||||
* otherwise a string represenation of the new URL.
|
||||
*/
|
||||
attribute ACString newTabURL;
|
||||
|
||||
/**
|
||||
* Returns the default URL (remote or local depending on pref)
|
||||
*/
|
||||
attribute ACString defaultURL;
|
||||
|
||||
/**
|
||||
* Returns true if the default resource got overridden.
|
||||
*/
|
||||
readonly attribute bool overridden;
|
||||
|
||||
/**
|
||||
* Returns true if the default resource is remotely hosted and isn't
|
||||
* overridden
|
||||
*/
|
||||
readonly attribute bool remoteEnabled;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the version of the remote newtab page expected
|
||||
*/
|
||||
readonly attribute ACString remoteVersion;
|
||||
|
||||
/**
|
||||
* Returns the expected channel for the remote the newtab page
|
||||
*/
|
||||
readonly attribute ACString remoteReleaseName;
|
||||
|
||||
/**
|
||||
* Generates and returns the remote newtab page url
|
||||
*/
|
||||
ACString generateRemoteURL();
|
||||
|
||||
/**
|
||||
* Returns a remote new tab release name given an update channel name
|
||||
*/
|
||||
ACString releaseFromUpdateChannel(in ACString channelName);
|
||||
|
||||
/**
|
||||
* Resets to the default resource and also resets the
|
||||
* overridden attribute to false.
|
||||
*/
|
||||
void resetNewTabURL();
|
||||
};
|
||||
7
browser/components/newtab/tests/browser/.eslintrc.js
Normal file
7
browser/components/newtab/tests/browser/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/mochitest/browser.eslintrc.js"
|
||||
]
|
||||
};
|
||||
9
browser/components/newtab/tests/browser/blue_page.html
Normal file
9
browser/components/newtab/tests/browser/blue_page.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body style="background-color: blue">
|
||||
</body>
|
||||
</html>
|
||||
16
browser/components/newtab/tests/browser/browser.ini
Normal file
16
browser/components/newtab/tests/browser/browser.ini
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
blue_page.html
|
||||
dummy_page.html
|
||||
newtabwebchannel_basic.html
|
||||
newtabmessages_places.html
|
||||
newtabmessages_prefs.html
|
||||
newtabmessages_preview.html
|
||||
newtabmessages_search.html
|
||||
|
||||
[browser_PreviewProvider.js]
|
||||
[browser_remotenewtab_pageloads.js]
|
||||
[browser_newtab_overrides.js]
|
||||
[browser_newtabmessages.js]
|
||||
skip-if = true # Bug 1271177, bug 1262719
|
||||
[browser_newtabwebchannel.js]
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/* globals XPCOMUtils, Services, PreviewProvider, registerCleanupFunction */
|
||||
"use strict";
|
||||
|
||||
let Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PreviewProvider",
|
||||
"resource:///modules/PreviewProvider.jsm");
|
||||
|
||||
var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
|
||||
Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeTab(gBrowser.tabs[1]);
|
||||
}
|
||||
Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref);
|
||||
});
|
||||
|
||||
const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
|
||||
|
||||
function pixelsForDataURI(dataURI, options) {
|
||||
return new Promise(resolve => {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
let {width, height} = options;
|
||||
if (!width) {
|
||||
width = 100;
|
||||
}
|
||||
if (!height) {
|
||||
height = 100;
|
||||
}
|
||||
|
||||
let htmlns = "http://www.w3.org/1999/xhtml";
|
||||
let img = document.createElementNS(htmlns, "img");
|
||||
img.setAttribute("src", dataURI);
|
||||
|
||||
img.addEventListener("load", function onLoad() {
|
||||
img.removeEventListener("load", onLoad, true);
|
||||
let canvas = document.createElementNS(htmlns, "canvas");
|
||||
canvas.setAttribute("width", width);
|
||||
canvas.setAttribute("height", height);
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
let result = ctx.getImageData(0, 0, width, height).data;
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function* chunk_four(listData) {
|
||||
let index = 0;
|
||||
while (index < listData.length) {
|
||||
yield listData.slice(index, index + 5);
|
||||
index += 4;
|
||||
}
|
||||
}
|
||||
|
||||
add_task(function* open_page() {
|
||||
let dataURI = yield PreviewProvider.getThumbnail(TEST_URL);
|
||||
let pixels = yield pixelsForDataURI(dataURI, {width: 10, height: 10});
|
||||
let rgbCount = {r: 0, g: 0, b: 0, a: 0};
|
||||
for (let [r, g, b, a] of chunk_four(pixels)) {
|
||||
if (r === 255) {
|
||||
rgbCount.r += 1;
|
||||
}
|
||||
if (g === 255) {
|
||||
rgbCount.g += 1;
|
||||
}
|
||||
if (b === 255) {
|
||||
rgbCount.b += 1;
|
||||
}
|
||||
if (a === 255) {
|
||||
rgbCount.a += 1;
|
||||
}
|
||||
}
|
||||
Assert.equal(`${rgbCount.r},${rgbCount.g},${rgbCount.b},${rgbCount.a}`,
|
||||
"0,0,100,100", "there should be 100 blue-only pixels at full opacity");
|
||||
});
|
||||
|
||||
add_task(function* invalid_url() {
|
||||
try {
|
||||
yield PreviewProvider.getThumbnail("invalid:URL");
|
||||
} catch (err) {
|
||||
Assert.ok(true, "URL Failed");
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
/* globals
|
||||
XPCOMUtils,
|
||||
aboutNewTabService,
|
||||
Services,
|
||||
ContentTask,
|
||||
TestUtils,
|
||||
BrowserOpenTab,
|
||||
registerCleanupFunction,
|
||||
is,
|
||||
content
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
let Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
|
||||
"@mozilla.org/browser/aboutnewtab-service;1",
|
||||
"nsIAboutNewTabService");
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.setBoolPref("browser.newtabpage.remote", false);
|
||||
aboutNewTabService.resetNewTabURL();
|
||||
});
|
||||
|
||||
/*
|
||||
* Tests that the default newtab page is always returned when one types "about:newtab" in the URL bar,
|
||||
* even when overridden.
|
||||
*/
|
||||
add_task(function* redirector_ignores_override() {
|
||||
let overrides = [
|
||||
"chrome://browser/content/downloads/contentAreaDownloadsView.xul",
|
||||
"about:home",
|
||||
];
|
||||
|
||||
for (let overrideURL of overrides) {
|
||||
let notificationPromise = nextChangeNotificationPromise(overrideURL, `newtab page now points to ${overrideURL}`);
|
||||
aboutNewTabService.newTabURL = overrideURL;
|
||||
|
||||
yield notificationPromise;
|
||||
Assert.ok(aboutNewTabService.overridden, "url has been overridden");
|
||||
|
||||
let tabOptions = {
|
||||
gBrowser,
|
||||
url: "about:newtab",
|
||||
};
|
||||
|
||||
/*
|
||||
* Simulate typing "about:newtab" in the url bar.
|
||||
*
|
||||
* Bug 1240169 - We expect the redirector to lead the user to "about:newtab", the default URL,
|
||||
* due to invoking AboutRedirector. A user interacting with the chrome otherwise would lead
|
||||
* to the overriding URLs.
|
||||
*/
|
||||
yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
|
||||
yield ContentTask.spawn(browser, {}, function*() {
|
||||
Assert.equal(content.location.href, "about:newtab", "Got right URL");
|
||||
Assert.equal(content.document.location.href, "about:newtab", "Got right URL");
|
||||
Assert.equal(content.document.nodePrincipal,
|
||||
Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
"nodePrincipal should match systemPrincipal");
|
||||
});
|
||||
}); // jshint ignore:line
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Tests loading an overridden newtab page by simulating opening a newtab page from chrome
|
||||
*/
|
||||
add_task(function* override_loads_in_browser() {
|
||||
let overrides = [
|
||||
"chrome://browser/content/downloads/contentAreaDownloadsView.xul",
|
||||
"about:home",
|
||||
" about:home",
|
||||
];
|
||||
|
||||
for (let overrideURL of overrides) {
|
||||
let notificationPromise = nextChangeNotificationPromise(overrideURL.trim(), `newtab page now points to ${overrideURL}`);
|
||||
aboutNewTabService.newTabURL = overrideURL;
|
||||
|
||||
yield notificationPromise;
|
||||
Assert.ok(aboutNewTabService.overridden, "url has been overridden");
|
||||
|
||||
// simulate a newtab open as a user would
|
||||
BrowserOpenTab(); // jshint ignore:line
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
yield ContentTask.spawn(browser, {url: overrideURL}, function*(args) {
|
||||
Assert.equal(content.location.href, args.url.trim(), "Got right URL");
|
||||
Assert.equal(content.document.location.href, args.url.trim(), "Got right URL");
|
||||
}); // jshint ignore:line
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Tests edge cases when someone overrides the newtabpage with whitespace
|
||||
*/
|
||||
add_task(function* override_blank_loads_in_browser() {
|
||||
let overrides = [
|
||||
"",
|
||||
" ",
|
||||
"\n\t",
|
||||
" about:blank",
|
||||
];
|
||||
|
||||
for (let overrideURL of overrides) {
|
||||
let notificationPromise = nextChangeNotificationPromise("about:blank", "newtab page now points to about:blank");
|
||||
aboutNewTabService.newTabURL = overrideURL;
|
||||
|
||||
yield notificationPromise;
|
||||
Assert.ok(aboutNewTabService.overridden, "url has been overridden");
|
||||
|
||||
// simulate a newtab open as a user would
|
||||
BrowserOpenTab(); // jshint ignore:line
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
yield ContentTask.spawn(browser, {}, function*() {
|
||||
Assert.equal(content.location.href, "about:blank", "Got right URL");
|
||||
Assert.equal(content.document.location.href, "about:blank", "Got right URL");
|
||||
}); // jshint ignore:line
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
}
|
||||
});
|
||||
|
||||
function nextChangeNotificationPromise(aNewURL, testMessage) {
|
||||
return TestUtils.topicObserved("newtab-url-changed", function observer(aSubject, aData) { // jshint unused:false
|
||||
Assert.equal(aData, aNewURL, testMessage);
|
||||
return true;
|
||||
}.bind(this));
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
/* 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");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
|
||||
"resource:///modules/NewTabMessages.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
|
||||
"resource://testing-common/PlacesTestUtils.jsm");
|
||||
|
||||
let setup = Task.async(function*() {
|
||||
Preferences.set("browser.newtabpage.enhanced", true);
|
||||
Preferences.set("browser.newtabpage.remote.mode", "test");
|
||||
Preferences.set("browser.newtabpage.remote", true);
|
||||
NewTabMessages.init();
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
});
|
||||
|
||||
let cleanup = Task.async(function*() {
|
||||
NewTabMessages.uninit();
|
||||
Preferences.set("browser.newtabpage.remote", false);
|
||||
Preferences.set("browser.newtabpage.remote.mode", "production");
|
||||
});
|
||||
registerCleanupFunction(cleanup);
|
||||
|
||||
/*
|
||||
* Sanity tests for pref messages
|
||||
*/
|
||||
add_task(function* prefMessages_request() {
|
||||
yield setup();
|
||||
|
||||
let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_prefs.html";
|
||||
|
||||
let tabOptions = {
|
||||
gBrowser,
|
||||
url: testURL
|
||||
};
|
||||
|
||||
let prefResponseAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("responseAck", () => {
|
||||
ok(true, "a request response has been received");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
yield BrowserTestUtils.withNewTab(tabOptions, function*() {
|
||||
yield prefResponseAck;
|
||||
let prefChangeAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("responseAck", () => {
|
||||
ok(true, "a change response has been received");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
Preferences.set("browser.newtabpage.enhanced", false);
|
||||
yield prefChangeAck;
|
||||
});
|
||||
yield cleanup();
|
||||
});
|
||||
|
||||
/*
|
||||
* Sanity tests for preview messages
|
||||
*/
|
||||
add_task(function* previewMessages_request() {
|
||||
yield setup();
|
||||
var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
|
||||
Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
|
||||
|
||||
let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_preview.html";
|
||||
|
||||
let tabOptions = {
|
||||
gBrowser,
|
||||
url: testURL
|
||||
};
|
||||
|
||||
let previewResponseAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("responseAck", () => {
|
||||
ok(true, "a request response has been received");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
yield BrowserTestUtils.withNewTab(tabOptions, function*() {
|
||||
yield previewResponseAck;
|
||||
});
|
||||
yield cleanup();
|
||||
Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref);
|
||||
});
|
||||
|
||||
/*
|
||||
* Sanity tests for places messages
|
||||
*/
|
||||
add_task(function* placesMessages_request() {
|
||||
yield setup();
|
||||
let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_places.html";
|
||||
|
||||
// url prefix for test history population
|
||||
const TEST_URL = "https://mozilla.com/";
|
||||
// time when the test starts execution
|
||||
const TIME_NOW = (new Date()).getTime();
|
||||
|
||||
// utility function to compute past timestamp
|
||||
function timeDaysAgo(numDays) {
|
||||
return TIME_NOW - (numDays * 24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
// utility function to make a visit for insertion into places db
|
||||
function makeVisit(index, daysAgo, isTyped, domain=TEST_URL) {
|
||||
let {
|
||||
TRANSITION_TYPED,
|
||||
TRANSITION_LINK
|
||||
} = PlacesUtils.history;
|
||||
|
||||
return {
|
||||
uri: NetUtil.newURI(`${domain}${index}`),
|
||||
visitDate: timeDaysAgo(daysAgo),
|
||||
transition: (isTyped) ? TRANSITION_TYPED : TRANSITION_LINK,
|
||||
};
|
||||
}
|
||||
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
|
||||
// all four visits must come from different domains to avoid deduplication
|
||||
let visits = [
|
||||
makeVisit(0, 0, true, "http://bar.com/"), // frecency 200, today
|
||||
makeVisit(1, 0, true, "http://foo.com/"), // frecency 200, today
|
||||
makeVisit(2, 2, true, "http://buz.com/"), // frecency 200, 2 days ago
|
||||
makeVisit(3, 2, false, "http://aaa.com/"), // frecency 10, 2 days ago, transition
|
||||
];
|
||||
|
||||
yield PlacesTestUtils.addVisits(visits);
|
||||
|
||||
/** Test Begins **/
|
||||
|
||||
let tabOptions = {
|
||||
gBrowser,
|
||||
url: testURL
|
||||
};
|
||||
|
||||
let placesResponseAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("numItemsAck", (_, msg) => {
|
||||
ok(true, "a request response has been received");
|
||||
is(msg.data, visits.length + 1, "received an expected number of history items");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
yield BrowserTestUtils.withNewTab(tabOptions, function*() {
|
||||
yield placesResponseAck;
|
||||
ok(true, "a change response has been received");
|
||||
let placesChangeAck = new Promise(resolve => {
|
||||
NewTabWebChannel.once("clearHistoryAck", (_, msg) => {
|
||||
is(msg.data, "clearHistory", "a clear history message has been received");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
yield placesChangeAck;
|
||||
});
|
||||
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,251 @@
|
|||
/* globals XPCOMUtils, Cu, Preferences, NewTabWebChannel, is, registerCleanupFunction */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
|
||||
"resource:///modules/NewTabWebChannel.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
|
||||
"resource:///modules/NewTabMessages.jsm");
|
||||
|
||||
const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
|
||||
const TEST_URL_2 = "http://mochi.test:8888/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
|
||||
|
||||
function setup(mode = "test") {
|
||||
Preferences.set("browser.newtabpage.remote.mode", mode);
|
||||
Preferences.set("browser.newtabpage.remote", true);
|
||||
NewTabWebChannel.init();
|
||||
NewTabMessages.init();
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
NewTabMessages.uninit();
|
||||
NewTabWebChannel.uninit();
|
||||
Preferences.set("browser.newtabpage.remote", false);
|
||||
Preferences.set("browser.newtabpage.remote.mode", "production");
|
||||
}
|
||||
registerCleanupFunction(cleanup);
|
||||
|
||||
/*
|
||||
* Tests flow of messages from newtab to chrome and chrome to newtab
|
||||
*/
|
||||
add_task(function* open_webchannel_basic() {
|
||||
setup();
|
||||
|
||||
let tabOptions = {
|
||||
gBrowser,
|
||||
url: TEST_URL
|
||||
};
|
||||
|
||||
let messagePromise = new Promise(resolve => {
|
||||
NewTabWebChannel.once("foo", function(name, msg) {
|
||||
is(name, "foo", "Correct message type sent: foo");
|
||||
is(msg.data, "bar", "Correct data sent: bar");
|
||||
resolve(msg.target);
|
||||
});
|
||||
});
|
||||
|
||||
let replyPromise = new Promise(resolve => {
|
||||
NewTabWebChannel.once("reply", function(name, msg) {
|
||||
is(name, "reply", "Correct message type sent: reply");
|
||||
is(msg.data, "quuz", "Correct data sent: quuz");
|
||||
resolve(msg.target);
|
||||
});
|
||||
});
|
||||
|
||||
let unloadPromise = new Promise(resolve => {
|
||||
NewTabWebChannel.once("targetUnload", function(name) {
|
||||
is(name, "targetUnload", "Correct message type sent: targetUnload");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
|
||||
yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
|
||||
let target = yield messagePromise;
|
||||
is(NewTabWebChannel.numBrowsers, 1, "One target expected");
|
||||
is(target.browser, browser, "Same browser");
|
||||
NewTabWebChannel.send("respond", null, target);
|
||||
yield replyPromise;
|
||||
});
|
||||
|
||||
Cu.forceGC();
|
||||
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
|
||||
yield unloadPromise;
|
||||
cleanup();
|
||||
});
|
||||
|
||||
/*
|
||||
* Tests message broadcast reaches all open newtab pages
|
||||
*/
|
||||
add_task(function* webchannel_broadcast() {
|
||||
setup();
|
||||
|
||||
let countingMessagePromise = new Promise(resolve => {
|
||||
let count = 0;
|
||||
NewTabWebChannel.on("foo", function test_message(name, msg) {
|
||||
count += 1;
|
||||
if (count === 2) {
|
||||
NewTabWebChannel.off("foo", test_message);
|
||||
resolve(msg.target);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
let countingReplyPromise = new Promise(resolve => {
|
||||
let count = 0;
|
||||
NewTabWebChannel.on("reply", function test_message(name, msg) {
|
||||
count += 1;
|
||||
if (count === 2) {
|
||||
NewTabWebChannel.off("reply", test_message);
|
||||
resolve(msg.target);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
let countingUnloadPromise = new Promise(resolve => {
|
||||
let count = 0;
|
||||
NewTabWebChannel.on("targetUnload", function test_message() {
|
||||
count += 1;
|
||||
if (count === 2) {
|
||||
NewTabWebChannel.off("targetUnload", test_message);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let tabs = [];
|
||||
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
|
||||
tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
|
||||
tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
|
||||
|
||||
yield countingMessagePromise;
|
||||
is(NewTabWebChannel.numBrowsers, 2, "Two targets expected");
|
||||
|
||||
NewTabWebChannel.broadcast("respond", null);
|
||||
yield countingReplyPromise;
|
||||
|
||||
for (let tab of tabs) {
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
Cu.forceGC();
|
||||
|
||||
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
|
||||
yield countingUnloadPromise;
|
||||
cleanup();
|
||||
});
|
||||
|
||||
/*
|
||||
* Tests switching modes
|
||||
*/
|
||||
add_task(function* webchannel_switch() {
|
||||
setup();
|
||||
|
||||
function newMessagePromise() {
|
||||
return new Promise(resolve => {
|
||||
NewTabWebChannel.once("foo", function(name, msg) {
|
||||
resolve(msg.target);
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
let replyCount = 0;
|
||||
function newReplyPromise() {
|
||||
return new Promise(resolve => {
|
||||
NewTabWebChannel.on("reply", function() {
|
||||
replyCount += 1;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let unloadPromise = new Promise(resolve => {
|
||||
NewTabWebChannel.once("targetUnload", function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
let unloadAllPromise = new Promise(resolve => {
|
||||
NewTabWebChannel.once("targetUnloadAll", function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
let tabs = [];
|
||||
let messagePromise;
|
||||
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
|
||||
|
||||
messagePromise = newMessagePromise();
|
||||
tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
|
||||
yield messagePromise;
|
||||
is(NewTabWebChannel.numBrowsers, 1, "Correct number of targets");
|
||||
|
||||
messagePromise = newMessagePromise();
|
||||
Preferences.set("browser.newtabpage.remote.mode", "test2");
|
||||
tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL_2));
|
||||
yield unloadAllPromise;
|
||||
yield messagePromise;
|
||||
is(NewTabWebChannel.numBrowsers, 1, "Correct number of targets");
|
||||
|
||||
NewTabWebChannel.broadcast("respond", null);
|
||||
yield newReplyPromise();
|
||||
is(replyCount, 1, "only current channel is listened to for replies");
|
||||
|
||||
const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
|
||||
let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
|
||||
let newWhitelist = origWhitelist + " http://mochi.test:8888";
|
||||
Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
|
||||
try {
|
||||
NewTabWebChannel.broadcast("respond_object", null);
|
||||
yield newReplyPromise();
|
||||
} finally {
|
||||
Services.prefs.clearUserPref(webchannelWhitelistPref);
|
||||
}
|
||||
|
||||
for (let tab of tabs) {
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
|
||||
Cu.forceGC();
|
||||
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
|
||||
yield unloadPromise;
|
||||
cleanup();
|
||||
});
|
||||
|
||||
add_task(function* open_webchannel_reload() {
|
||||
setup();
|
||||
|
||||
let tabOptions = {
|
||||
gBrowser,
|
||||
url: TEST_URL
|
||||
};
|
||||
|
||||
let messagePromise = new Promise(resolve => {
|
||||
NewTabWebChannel.once("foo", function(name, msg) {
|
||||
is(name, "foo", "Correct message type sent: foo");
|
||||
is(msg.data, "bar", "Correct data sent: bar");
|
||||
resolve(msg.target);
|
||||
});
|
||||
});
|
||||
let unloadPromise = new Promise(resolve => {
|
||||
NewTabWebChannel.once("targetUnload", function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
|
||||
yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
|
||||
let target = yield messagePromise;
|
||||
is(NewTabWebChannel.numBrowsers, 1, "One target expected");
|
||||
is(target.browser, browser, "Same browser");
|
||||
|
||||
browser.reload();
|
||||
});
|
||||
|
||||
Cu.forceGC();
|
||||
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
|
||||
yield unloadPromise;
|
||||
cleanup();
|
||||
});
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/* globals Cu, XPCOMUtils, TestUtils, aboutNewTabService, ContentTask, content, is */
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
|
||||
"@mozilla.org/browser/aboutnewtab-service;1",
|
||||
"nsIAboutNewTabService");
|
||||
|
||||
const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/dummy_page.html";
|
||||
|
||||
/*
|
||||
* Tests opening a newtab page with a remote URL. Simulates a newtab open from chrome
|
||||
*/
|
||||
add_task(function* open_newtab() {
|
||||
let notificationPromise = nextChangeNotificationPromise(TEST_URL, "newtab page now points to test url");
|
||||
aboutNewTabService.newTabURL = TEST_URL;
|
||||
|
||||
yield notificationPromise;
|
||||
Assert.ok(aboutNewTabService.overridden, "url has been overridden");
|
||||
|
||||
/*
|
||||
* Simulate a newtab open as a user would.
|
||||
*
|
||||
* Bug 1240169 - We cannot set the URL to about:newtab because that would invoke the redirector.
|
||||
* The redirector always yields the loading of a default newtab URL. We expect the user to use
|
||||
* the browser UI to access overriding URLs, for istance by click on the "+" button in the tab
|
||||
* bar, or by using the new tab shortcut key.
|
||||
*/
|
||||
BrowserOpenTab(); // jshint ignore:line
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
yield ContentTask.spawn(browser, {url: TEST_URL}, function*(args) {
|
||||
Assert.equal(content.document.location.href, args.url,
|
||||
"document.location should match the external resource");
|
||||
Assert.equal(content.document.documentURI, args.url,
|
||||
"document.documentURI should match the external resource");
|
||||
Assert.equal(content.document.nodePrincipal.URI.spec, args.url,
|
||||
"nodePrincipal should match the external resource");
|
||||
});
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
||||
function nextChangeNotificationPromise(aNewURL, testMessage) {
|
||||
return TestUtils.topicObserved("newtab-url-changed", function observer(aSubject, aData) { // jshint unused:false
|
||||
Assert.equal(aData, aNewURL, testMessage);
|
||||
return true;
|
||||
}.bind(this));
|
||||
}
|
||||
10
browser/components/newtab/tests/browser/dummy_page.html
Normal file
10
browser/components/newtab/tests/browser/dummy_page.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<p>Dummy Page</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Newtab WebChannel test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.addEventListener("WebChannelMessageToContent", function(e) {
|
||||
if (e.detail.message) {
|
||||
let reply;
|
||||
switch (e.detail.message.type) {
|
||||
case "RECEIVE_FRECENT":
|
||||
reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: JSON.stringify({
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "numItemsAck", data: e.detail.message.data.length}),
|
||||
})
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
break;
|
||||
case "RECEIVE_PLACES_CHANGE":
|
||||
if (e.detail.message.data.type === "clearHistory") {
|
||||
reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: JSON.stringify({
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "clearHistoryAck", data: e.detail.message.data.type}),
|
||||
})
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
if (document.readyState === "complete") {
|
||||
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: JSON.stringify({
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_FRECENT"}),
|
||||
})
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Newtab WebChannel test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.addEventListener("WebChannelMessageToContent", function(e) {
|
||||
if (e.detail.message && e.detail.message.type === "RECEIVE_PREFS") {
|
||||
let reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: JSON.stringify({
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "responseAck"}),
|
||||
})
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
}
|
||||
}, true);
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: JSON.stringify({
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_PREFS"}),
|
||||
})
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Newtab WebChannel test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
let thumbURL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
|
||||
|
||||
window.addEventListener("WebChannelMessageToContent", function(e) {
|
||||
if (e.detail.message && e.detail.message.type === "RECEIVE_THUMB") {
|
||||
if (e.detail.message.data.imgData && e.detail.message.data.url === thumbURL) {
|
||||
let reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: JSON.stringify({
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "responseAck"}),
|
||||
})
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
if (document.readyState === "complete") {
|
||||
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: JSON.stringify({
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "REQUEST_THUMB", data: thumbURL}),
|
||||
})
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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,36 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Newtab WebChannel test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
document.onreadystatechange = function () {
|
||||
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: JSON.stringify({
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "foo", data: "bar"}),
|
||||
})
|
||||
});
|
||||
window.dispatchEvent(msg);
|
||||
};
|
||||
|
||||
window.addEventListener("WebChannelMessageToContent", function(e) {
|
||||
if (e.detail.message && e.detail.message.type.startsWith("respond")) {
|
||||
var detail = {
|
||||
id: "newtab",
|
||||
message: JSON.stringify({type: "reply", data: "quuz"}),
|
||||
};
|
||||
if (e.detail.message.type !== "respond_object") {
|
||||
detail = JSON.stringify(detail);
|
||||
}
|
||||
let reply = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: detail
|
||||
});
|
||||
window.dispatchEvent(reply);
|
||||
}
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
7
browser/components/newtab/tests/xpcshell/.eslintrc.js
Normal file
7
browser/components/newtab/tests/xpcshell/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/xpcshell/xpcshell.eslintrc.js"
|
||||
]
|
||||
};
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
/* globals Services, XPCOMUtils, NewTabPrefsProvider, Preferences, aboutNewTabService, do_register_cleanup */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
|
||||
"resource:///modules/NewTabPrefsProvider.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
|
||||
"@mozilla.org/browser/aboutnewtab-service;1",
|
||||
"nsIAboutNewTabService");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
|
||||
"resource://gre/modules/Locale.jsm");
|
||||
|
||||
const DEFAULT_HREF = aboutNewTabService.generateRemoteURL();
|
||||
const DEFAULT_CHROME_URL = "chrome://browser/content/newtab/newTab.xhtml";
|
||||
const DOWNLOADS_URL = "chrome://browser/content/downloads/contentAreaDownloadsView.xul";
|
||||
const DEFAULT_VERSION = aboutNewTabService.remoteVersion;
|
||||
|
||||
function cleanup() {
|
||||
Services.prefs.setBoolPref("browser.newtabpage.remote", false);
|
||||
Services.prefs.setCharPref("browser.newtabpage.remote.version", DEFAULT_VERSION);
|
||||
aboutNewTabService.resetNewTabURL();
|
||||
NewTabPrefsProvider.prefs.uninit();
|
||||
}
|
||||
|
||||
do_register_cleanup(cleanup);
|
||||
|
||||
/**
|
||||
* Test the overriding of the default URL
|
||||
*/
|
||||
add_task(function* test_override_remote_disabled() {
|
||||
NewTabPrefsProvider.prefs.init();
|
||||
let notificationPromise;
|
||||
Services.prefs.setBoolPref("browser.newtabpage.remote", false);
|
||||
|
||||
// tests default is the local newtab resource
|
||||
Assert.equal(aboutNewTabService.defaultURL, DEFAULT_CHROME_URL,
|
||||
`Default newtab URL should be ${DEFAULT_CHROME_URL}`);
|
||||
|
||||
// override with some remote URL
|
||||
let url = "http://example.com/";
|
||||
notificationPromise = nextChangeNotificationPromise(url);
|
||||
aboutNewTabService.newTabURL = url;
|
||||
yield notificationPromise;
|
||||
Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
|
||||
Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
|
||||
Assert.equal(aboutNewTabService.newTabURL, url, "Newtab URL should be the custom URL");
|
||||
|
||||
// test reset with remote disabled
|
||||
notificationPromise = nextChangeNotificationPromise("about:newtab");
|
||||
aboutNewTabService.resetNewTabURL();
|
||||
yield notificationPromise;
|
||||
Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
|
||||
Assert.equal(aboutNewTabService.newTabURL, "about:newtab", "Newtab URL should be the default");
|
||||
|
||||
// test override to a chrome URL
|
||||
notificationPromise = nextChangeNotificationPromise(DOWNLOADS_URL);
|
||||
aboutNewTabService.newTabURL = DOWNLOADS_URL;
|
||||
yield notificationPromise;
|
||||
Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
|
||||
Assert.equal(aboutNewTabService.newTabURL, DOWNLOADS_URL, "Newtab URL should be the custom URL");
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
add_task(function* test_override_remote_enabled() {
|
||||
NewTabPrefsProvider.prefs.init();
|
||||
let notificationPromise;
|
||||
// change newtab page to remote
|
||||
notificationPromise = nextChangeNotificationPromise("about:newtab");
|
||||
Services.prefs.setBoolPref("browser.newtabpage.remote", true);
|
||||
yield notificationPromise;
|
||||
let remoteHref = aboutNewTabService.generateRemoteURL();
|
||||
Assert.equal(aboutNewTabService.defaultURL, remoteHref, "Newtab URL should be the default remote URL");
|
||||
Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
|
||||
Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
|
||||
|
||||
// change to local newtab page while remote is enabled
|
||||
notificationPromise = nextChangeNotificationPromise(DEFAULT_CHROME_URL);
|
||||
aboutNewTabService.newTabURL = DEFAULT_CHROME_URL;
|
||||
yield notificationPromise;
|
||||
Assert.equal(aboutNewTabService.newTabURL, DEFAULT_CHROME_URL,
|
||||
"Newtab URL set to chrome url");
|
||||
Assert.equal(aboutNewTabService.defaultURL, DEFAULT_CHROME_URL,
|
||||
"Newtab URL defaultURL set to the default chrome URL");
|
||||
Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
|
||||
Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests reponse to updates to prefs
|
||||
*/
|
||||
add_task(function* test_updates() {
|
||||
/*
|
||||
* Simulates a "cold-boot" situation, with some pref already set before testing a series
|
||||
* of changes.
|
||||
*/
|
||||
Preferences.set("browser.newtabpage.remote", true);
|
||||
aboutNewTabService.resetNewTabURL(); // need to set manually because pref notifs are off
|
||||
let notificationPromise;
|
||||
let productionModeBaseUrl = "https://content.cdn.mozilla.net";
|
||||
let testModeBaseUrl = "https://example.com";
|
||||
let expectedPath = `/newtab` +
|
||||
`/v${aboutNewTabService.remoteVersion}` +
|
||||
`/${aboutNewTabService.remoteReleaseName}` +
|
||||
"/en-GB" +
|
||||
"/index.html";
|
||||
let expectedHref = productionModeBaseUrl + expectedPath;
|
||||
Preferences.set("intl.locale.matchOS", true);
|
||||
Preferences.set("general.useragent.locale", "en-GB");
|
||||
Preferences.set("browser.newtabpage.remote.mode", "production");
|
||||
NewTabPrefsProvider.prefs.init();
|
||||
|
||||
// test update checks for prefs
|
||||
notificationPromise = nextChangeNotificationPromise(
|
||||
expectedHref, "Remote href should be updated");
|
||||
Preferences.set("intl.locale.matchOS", false);
|
||||
yield notificationPromise;
|
||||
|
||||
notificationPromise = nextChangeNotificationPromise(
|
||||
DEFAULT_HREF, "Remote href changes back to default");
|
||||
Preferences.set("general.useragent.locale", "en-US");
|
||||
yield notificationPromise;
|
||||
|
||||
// test update fires when mode is changed
|
||||
expectedPath = expectedPath.replace("/en-GB/", "/en-US/");
|
||||
notificationPromise = nextChangeNotificationPromise(
|
||||
testModeBaseUrl + expectedPath, "Remote href changes back to origin of test mode");
|
||||
Preferences.set("browser.newtabpage.remote.mode", "test");
|
||||
yield notificationPromise;
|
||||
|
||||
// test invalid mode ends up pointing to production url
|
||||
notificationPromise = nextChangeNotificationPromise(
|
||||
DEFAULT_HREF, "Remote href changes back to production default");
|
||||
Preferences.set("browser.newtabpage.remote.mode", "invalid");
|
||||
yield notificationPromise;
|
||||
|
||||
// test update fires on override and reset
|
||||
let testURL = "https://example.com/";
|
||||
notificationPromise = nextChangeNotificationPromise(
|
||||
testURL, "a notification occurs on override");
|
||||
aboutNewTabService.newTabURL = testURL;
|
||||
yield notificationPromise;
|
||||
|
||||
// from overridden to default
|
||||
notificationPromise = nextChangeNotificationPromise(
|
||||
"about:newtab", "a notification occurs on reset");
|
||||
aboutNewTabService.resetNewTabURL();
|
||||
Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
|
||||
Assert.equal(aboutNewTabService.defaultURL, DEFAULT_HREF, "Default URL should be the remote page");
|
||||
yield notificationPromise;
|
||||
|
||||
// override to default URL from default URL
|
||||
notificationPromise = nextChangeNotificationPromise(
|
||||
testURL, "a notification only occurs for a change in overridden urls");
|
||||
aboutNewTabService.newTabURL = aboutNewTabService.generateRemoteURL();
|
||||
Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
|
||||
aboutNewTabService.newTabURL = testURL;
|
||||
yield notificationPromise;
|
||||
Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
|
||||
|
||||
// reset twice, only one notification for default URL
|
||||
notificationPromise = nextChangeNotificationPromise(
|
||||
"about:newtab", "reset occurs");
|
||||
aboutNewTabService.resetNewTabURL();
|
||||
yield notificationPromise;
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that releaseFromUpdateChannel
|
||||
* Returns the correct release names
|
||||
*/
|
||||
add_task(function* test_release_names() {
|
||||
let valid_channels = ["esr", "release", "beta", "aurora", "nightly"];
|
||||
let invalid_channels = new Set(["default", "invalid"]);
|
||||
|
||||
for (let channel of valid_channels) {
|
||||
Assert.equal(channel, aboutNewTabService.releaseFromUpdateChannel(channel),
|
||||
"release == channel name when valid");
|
||||
}
|
||||
|
||||
for (let channel of invalid_channels) {
|
||||
Assert.equal("nightly", aboutNewTabService.releaseFromUpdateChannel(channel),
|
||||
"release == nightly when invalid");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that remote version updates changes the remote newtab url
|
||||
*/
|
||||
add_task(function* test_version_update() {
|
||||
NewTabPrefsProvider.prefs.init();
|
||||
|
||||
Services.prefs.setBoolPref("browser.newtabpage.remote", true);
|
||||
Assert.ok(aboutNewTabService.remoteEnabled, "remote mode enabled");
|
||||
|
||||
let productionModeBaseUrl = "https://content.cdn.mozilla.net";
|
||||
let version_incr = String(parseInt(DEFAULT_VERSION) + 1);
|
||||
let expectedPath = `/newtab` +
|
||||
`/v${version_incr}` +
|
||||
`/${aboutNewTabService.remoteReleaseName}` +
|
||||
`/${Locale.getLocale()}` +
|
||||
`/index.html`;
|
||||
let expectedHref = productionModeBaseUrl + expectedPath;
|
||||
|
||||
let notificationPromise;
|
||||
notificationPromise = nextChangeNotificationPromise(expectedHref);
|
||||
Preferences.set("browser.newtabpage.remote.version", version_incr);
|
||||
yield notificationPromise;
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
function nextChangeNotificationPromise(aNewURL, testMessage) {
|
||||
return new Promise(resolve => {
|
||||
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint unused:false
|
||||
Services.obs.removeObserver(observer, aTopic);
|
||||
Assert.equal(aData, aNewURL, testMessage);
|
||||
resolve();
|
||||
}, "newtab-url-changed", false);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
"use strict";
|
||||
|
||||
/* global XPCOMUtils, equal, Preferences, NewTabPrefsProvider, run_next_test */
|
||||
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
|
||||
"resource:///modules/NewTabPrefsProvider.jsm");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_observe() {
|
||||
let prefsMap = NewTabPrefsProvider.prefs.prefsMap;
|
||||
for (let prefName of prefsMap.keys()) {
|
||||
let prefValueType = prefsMap.get(prefName);
|
||||
|
||||
let beforeVal;
|
||||
let afterVal;
|
||||
|
||||
switch (prefValueType) {
|
||||
case "bool":
|
||||
beforeVal = false;
|
||||
afterVal = true;
|
||||
Preferences.set(prefName, beforeVal);
|
||||
break;
|
||||
case "localized":
|
||||
case "str":
|
||||
beforeVal = "";
|
||||
afterVal = "someStr";
|
||||
Preferences.set(prefName, beforeVal);
|
||||
break;
|
||||
}
|
||||
NewTabPrefsProvider.prefs.init();
|
||||
let promise = new Promise(resolve => {
|
||||
NewTabPrefsProvider.prefs.once(prefName, (name, data) => { // jshint ignore:line
|
||||
resolve([name, data]);
|
||||
});
|
||||
});
|
||||
Preferences.set(prefName, afterVal);
|
||||
let [actualName, actualData] = yield promise;
|
||||
equal(prefName, actualName, `emitter sent the correct pref: ${prefName}`);
|
||||
equal(afterVal, actualData, `emitter collected correct pref data for ${prefName}`);
|
||||
NewTabPrefsProvider.prefs.uninit();
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
"use strict";
|
||||
|
||||
/* global XPCOMUtils, NewTabSearchProvider, run_next_test, ok, equal, do_check_true, do_get_profile, Services */
|
||||
/* 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();
|
||||
});
|
||||
52
browser/components/newtab/tests/xpcshell/test_NewTabURL.js
Normal file
52
browser/components/newtab/tests/xpcshell/test_NewTabURL.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
/* globals Services, NewTabURL, XPCOMUtils, aboutNewTabService, NewTabPrefsProvider */
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/NewTabURL.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
|
||||
"resource:///modules/NewTabPrefsProvider.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
|
||||
"@mozilla.org/browser/aboutnewtab-service;1",
|
||||
"nsIAboutNewTabService");
|
||||
|
||||
add_task(function*() {
|
||||
let defaultURL = aboutNewTabService.newTabURL;
|
||||
Services.prefs.setBoolPref("browser.newtabpage.remote", false);
|
||||
|
||||
Assert.equal(NewTabURL.get(), defaultURL, `Default newtab URL should be ${defaultURL}`);
|
||||
let url = "http://example.com/";
|
||||
let notificationPromise = promiseNewtabURLNotification(url);
|
||||
NewTabURL.override(url);
|
||||
yield notificationPromise;
|
||||
Assert.ok(NewTabURL.overridden, "Newtab URL should be overridden");
|
||||
Assert.equal(NewTabURL.get(), url, "Newtab URL should be the custom URL");
|
||||
|
||||
notificationPromise = promiseNewtabURLNotification(defaultURL);
|
||||
NewTabURL.reset();
|
||||
yield notificationPromise;
|
||||
Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
|
||||
Assert.equal(NewTabURL.get(), defaultURL, "Newtab URL should be the default");
|
||||
|
||||
// change newtab page to remote
|
||||
NewTabPrefsProvider.prefs.init();
|
||||
Services.prefs.setBoolPref("browser.newtabpage.remote", true);
|
||||
Assert.equal(NewTabURL.get(), "about:newtab", `Newtab URL should be about:newtab`);
|
||||
Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
|
||||
NewTabPrefsProvider.prefs.uninit();
|
||||
});
|
||||
|
||||
function promiseNewtabURLNotification(aNewURL) {
|
||||
return new Promise(resolve => {
|
||||
Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint ignore:line
|
||||
Services.obs.removeObserver(observer, aTopic);
|
||||
Assert.equal(aData, aNewURL, "Data for newtab-url-changed notification should be new URL.");
|
||||
resolve();
|
||||
}, "newtab-url-changed", false);
|
||||
});
|
||||
}
|
||||
358
browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
Normal file
358
browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
"use strict";
|
||||
|
||||
/* global XPCOMUtils, PlacesUtils, PlacesTestUtils, PlacesProvider, NetUtil */
|
||||
/* global do_get_profile, run_next_test, add_task */
|
||||
/* global equal, ok */
|
||||
|
||||
const {
|
||||
utils: Cu,
|
||||
interfaces: Ci,
|
||||
} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesProvider",
|
||||
"resource:///modules/PlacesProvider.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
|
||||
"resource://testing-common/PlacesTestUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
// ensure a profile exists
|
||||
do_get_profile();
|
||||
|
||||
function run_test() {
|
||||
PlacesProvider.links.init();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// url prefix for test history population
|
||||
const TEST_URL = "https://mozilla.com/";
|
||||
// time when the test starts execution
|
||||
const TIME_NOW = (new Date()).getTime();
|
||||
|
||||
// utility function to compute past timestap
|
||||
function timeDaysAgo(numDays) {
|
||||
return TIME_NOW - (numDays * 24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
// utility function to make a visit for insetion into places db
|
||||
function makeVisit(index, daysAgo, isTyped, domain=TEST_URL) {
|
||||
let {
|
||||
TRANSITION_TYPED,
|
||||
TRANSITION_LINK
|
||||
} = PlacesUtils.history;
|
||||
|
||||
return {
|
||||
uri: NetUtil.newURI(`${domain}${index}`),
|
||||
visitDate: timeDaysAgo(daysAgo),
|
||||
transition: (isTyped) ? TRANSITION_TYPED : TRANSITION_LINK,
|
||||
};
|
||||
}
|
||||
|
||||
/** Test LinkChecker **/
|
||||
|
||||
add_task(function test_LinkChecker_securityCheck() {
|
||||
|
||||
let urls = [
|
||||
{url: "javascript:alert('hello')", expected: false}, // jshint ignore:line
|
||||
{url: "data:image/png;base64,XXX", expected: false},
|
||||
{url: "about:newtab", expected: true},
|
||||
{url: "https://example.com", expected: true},
|
||||
{url: "ftp://example.com", expected: true},
|
||||
{url: "file://home/file/image.png", expected: true},
|
||||
{url: "resource:///modules/PlacesProvider.jsm", expected: true},
|
||||
];
|
||||
for (let {url, expected} of urls) {
|
||||
let observed = PlacesProvider.LinkChecker.checkLoadURI(url);
|
||||
equal(observed, expected, `can load "${url}"?`);
|
||||
}
|
||||
});
|
||||
|
||||
/** Test Provider **/
|
||||
|
||||
add_task(function* test_Links_getLinks() {
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
let provider = PlacesProvider.links;
|
||||
|
||||
let links = yield provider.getLinks();
|
||||
equal(links.length, 0, "empty history yields empty links");
|
||||
|
||||
// add a visit
|
||||
let testURI = NetUtil.newURI("http://mozilla.com");
|
||||
yield PlacesTestUtils.addVisits(testURI);
|
||||
|
||||
links = yield provider.getLinks();
|
||||
equal(links.length, 1, "adding a visit yields a link");
|
||||
equal(links[0].url, testURI.spec, "added visit corresponds to added url");
|
||||
});
|
||||
|
||||
add_task(function* test_Links_getLinks_Order() {
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
let provider = PlacesProvider.links;
|
||||
|
||||
// all four visits must come from different domains to avoid deduplication
|
||||
let visits = [
|
||||
makeVisit(0, 0, true, "http://bar.com/"), // frecency 200, today
|
||||
makeVisit(1, 0, true, "http://foo.com/"), // frecency 200, today
|
||||
makeVisit(2, 2, true, "http://buz.com/"), // frecency 200, 2 days ago
|
||||
makeVisit(3, 2, false, "http://aaa.com/"), // frecency 10, 2 days ago, transition
|
||||
];
|
||||
|
||||
let links = yield provider.getLinks();
|
||||
equal(links.length, 0, "empty history yields empty links");
|
||||
yield PlacesTestUtils.addVisits(visits);
|
||||
|
||||
links = yield provider.getLinks();
|
||||
equal(links.length, visits.length, "number of links added is the same as obtain by getLinks");
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
equal(links[i].url, visits[i].uri.spec, "links are obtained in the expected order");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_Links_getLinks_Deduplication() {
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
let provider = PlacesProvider.links;
|
||||
|
||||
// all for visits must come from different domains to avoid deduplication
|
||||
let visits = [
|
||||
makeVisit(0, 2, true, "http://bar.com/"), // frecency 200, 2 days ago
|
||||
makeVisit(1, 0, true, "http://bar.com/"), // frecency 200, today
|
||||
makeVisit(2, 0, false, "http://foo.com/"), // frecency 10, today
|
||||
makeVisit(3, 0, true, "http://foo.com/"), // frecency 200, today
|
||||
];
|
||||
|
||||
let links = yield provider.getLinks();
|
||||
equal(links.length, 0, "empty history yields empty links");
|
||||
yield PlacesTestUtils.addVisits(visits);
|
||||
|
||||
links = yield provider.getLinks();
|
||||
equal(links.length, 2, "only two links must be left after deduplication");
|
||||
equal(links[0].url, visits[1].uri.spec, "earliest link is present");
|
||||
equal(links[1].url, visits[3].uri.spec, "most fresent link is present");
|
||||
});
|
||||
|
||||
add_task(function* test_Links_onLinkChanged() {
|
||||
let provider = PlacesProvider.links;
|
||||
|
||||
let url = "https://example.com/onFrecencyChanged1";
|
||||
let linkChangedMsgCount = 0;
|
||||
|
||||
let linkChangedPromise = new Promise(resolve => {
|
||||
let handler = (_, link) => { // jshint ignore:line
|
||||
/* There are 3 linkChanged events:
|
||||
* 1. visit insertion (-1 frecency by default)
|
||||
* 2. frecency score update (after transition type calculation etc)
|
||||
* 3. title change
|
||||
*/
|
||||
if (link.url === url) {
|
||||
equal(link.url, url, `expected url on linkChanged event`);
|
||||
linkChangedMsgCount += 1;
|
||||
if (linkChangedMsgCount === 3) {
|
||||
ok(true, `all linkChanged events captured`);
|
||||
provider.off("linkChanged", this);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
provider.on("linkChanged", handler);
|
||||
});
|
||||
|
||||
// add a visit
|
||||
let testURI = NetUtil.newURI(url);
|
||||
yield PlacesTestUtils.addVisits(testURI);
|
||||
yield linkChangedPromise;
|
||||
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
});
|
||||
|
||||
add_task(function* test_Links_onClearHistory() {
|
||||
let provider = PlacesProvider.links;
|
||||
|
||||
let clearHistoryPromise = new Promise(resolve => {
|
||||
let handler = () => {
|
||||
ok(true, `clearHistory event captured`);
|
||||
provider.off("clearHistory", handler);
|
||||
resolve();
|
||||
};
|
||||
provider.on("clearHistory", handler);
|
||||
});
|
||||
|
||||
// add visits
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
let url = `https://example.com/onClearHistory${i}`;
|
||||
let testURI = NetUtil.newURI(url);
|
||||
yield PlacesTestUtils.addVisits(testURI);
|
||||
}
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
yield clearHistoryPromise;
|
||||
});
|
||||
|
||||
add_task(function* test_Links_onDeleteURI() {
|
||||
let provider = PlacesProvider.links;
|
||||
|
||||
let testURL = "https://example.com/toDelete";
|
||||
|
||||
let deleteURIPromise = new Promise(resolve => {
|
||||
let handler = (_, {url}) => { // jshint ignore:line
|
||||
equal(testURL, url, "deleted url and expected url are the same");
|
||||
provider.off("deleteURI", handler);
|
||||
resolve();
|
||||
};
|
||||
|
||||
provider.on("deleteURI", handler);
|
||||
});
|
||||
|
||||
let testURI = NetUtil.newURI(testURL);
|
||||
yield PlacesTestUtils.addVisits(testURI);
|
||||
yield PlacesUtils.history.remove(testURL);
|
||||
yield deleteURIPromise;
|
||||
});
|
||||
|
||||
add_task(function* test_Links_onManyLinksChanged() {
|
||||
let provider = PlacesProvider.links;
|
||||
|
||||
let promise = new Promise(resolve => {
|
||||
let handler = () => {
|
||||
ok(true);
|
||||
provider.off("manyLinksChanged", handler);
|
||||
resolve();
|
||||
};
|
||||
|
||||
provider.on("manyLinksChanged", handler);
|
||||
});
|
||||
|
||||
let testURL = "https://example.com/toDelete";
|
||||
let testURI = NetUtil.newURI(testURL);
|
||||
yield PlacesTestUtils.addVisits(testURI);
|
||||
|
||||
// trigger DecayFrecency
|
||||
PlacesUtils.history.QueryInterface(Ci.nsIObserver).
|
||||
observe(null, "idle-daily", "");
|
||||
|
||||
yield promise;
|
||||
});
|
||||
|
||||
add_task(function* test_Links_execute_query() {
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
let provider = PlacesProvider.links;
|
||||
|
||||
let visits = [
|
||||
makeVisit(0, 0, true), // frecency 200, today
|
||||
makeVisit(1, 0, true), // frecency 200, today
|
||||
makeVisit(2, 2, true), // frecency 200, 2 days ago
|
||||
makeVisit(3, 2, false), // frecency 10, 2 days ago, transition
|
||||
];
|
||||
|
||||
yield PlacesTestUtils.addVisits(visits);
|
||||
|
||||
function testItemValue(results, index, value) {
|
||||
equal(results[index][0], `${TEST_URL}${value}`, "raw url");
|
||||
equal(results[index][1], `test visit for ${TEST_URL}${value}`, "raw title");
|
||||
}
|
||||
|
||||
function testItemObject(results, index, columnValues) {
|
||||
Object.keys(columnValues).forEach(name => {
|
||||
equal(results[index][name], columnValues[name], "object name " + name);
|
||||
});
|
||||
}
|
||||
|
||||
// select all 4 records
|
||||
let results = yield provider.executePlacesQuery("select url, title from moz_places");
|
||||
equal(results.length, 4, "expect 4 items");
|
||||
// check for insert order sequence
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
testItemValue(results, i, i);
|
||||
}
|
||||
|
||||
// test parameter passing
|
||||
results = yield provider.executePlacesQuery(
|
||||
"select url, title from moz_places limit :limit",
|
||||
{params: {limit: 2}}
|
||||
);
|
||||
equal(results.length, 2, "expect 2 items");
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
testItemValue(results, i, i);
|
||||
}
|
||||
|
||||
// test extracting items by name
|
||||
results = yield provider.executePlacesQuery(
|
||||
"select url, title from moz_places limit :limit",
|
||||
{columns: ["url", "title"], params: {limit: 4}}
|
||||
);
|
||||
equal(results.length, 4, "expect 4 items");
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
testItemObject(results, i, {
|
||||
"url": `${TEST_URL}${i}`,
|
||||
"title": `test visit for ${TEST_URL}${i}`,
|
||||
});
|
||||
}
|
||||
|
||||
// test ordering
|
||||
results = yield provider.executePlacesQuery(
|
||||
"select url, title, last_visit_date, frecency from moz_places " +
|
||||
"order by frecency DESC, last_visit_date DESC, url DESC limit :limit",
|
||||
{columns: ["url", "title", "last_visit_date", "frecency"], params: {limit: 4}}
|
||||
);
|
||||
equal(results.length, 4, "expect 4 items");
|
||||
testItemObject(results, 0, {url: `${TEST_URL}1`});
|
||||
testItemObject(results, 1, {url: `${TEST_URL}0`});
|
||||
testItemObject(results, 2, {url: `${TEST_URL}2`});
|
||||
testItemObject(results, 3, {url: `${TEST_URL}3`});
|
||||
|
||||
// test callback passing
|
||||
results = [];
|
||||
function handleRow(aRow) {
|
||||
results.push({
|
||||
url: aRow.getResultByName("url"),
|
||||
title: aRow.getResultByName("title"),
|
||||
last_visit_date: aRow.getResultByName("last_visit_date"),
|
||||
frecency: aRow.getResultByName("frecency")
|
||||
});
|
||||
}
|
||||
yield provider.executePlacesQuery(
|
||||
"select url, title, last_visit_date, frecency from moz_places " +
|
||||
"order by frecency DESC, last_visit_date DESC, url DESC",
|
||||
{callback: handleRow}
|
||||
);
|
||||
equal(results.length, 4, "expect 4 items");
|
||||
testItemObject(results, 0, {url: `${TEST_URL}1`});
|
||||
testItemObject(results, 1, {url: `${TEST_URL}0`});
|
||||
testItemObject(results, 2, {url: `${TEST_URL}2`});
|
||||
testItemObject(results, 3, {url: `${TEST_URL}3`});
|
||||
|
||||
// negative test cases
|
||||
// bad sql
|
||||
try {
|
||||
yield provider.executePlacesQuery("select from moz");
|
||||
do_throw("bad sql should've thrown");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true("expected failure - bad sql");
|
||||
}
|
||||
// missing bindings
|
||||
try {
|
||||
yield provider.executePlacesQuery("select * from moz_places limit :limit");
|
||||
do_throw("bad sql should've thrown");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true("expected failure - missing bidning");
|
||||
}
|
||||
// non-existent column name
|
||||
try {
|
||||
yield provider.executePlacesQuery("select * from moz_places limit :limit",
|
||||
{columns: ["no-such-column"], params: {limit: 4}});
|
||||
do_throw("bad sql should've thrown");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true("expected failure - wrong column name");
|
||||
}
|
||||
|
||||
// cleanup
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
});
|
||||
11
browser/components/newtab/tests/xpcshell/xpcshell.ini
Normal file
11
browser/components/newtab/tests/xpcshell/xpcshell.ini
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_AboutNewTabService.js]
|
||||
[test_NewTabPrefsProvider.js]
|
||||
[test_NewTabSearchProvider.js]
|
||||
[test_NewTabURL.js]
|
||||
[test_PlacesProvider.js]
|
||||
Loading…
Add table
Add a link
Reference in a new issue