Merge m-c to inbound, a=merge

MozReview-Commit-ID: 2zwhun4JqPs
This commit is contained in:
Wes Kocher 2016-04-15 15:39:25 -07:00
commit 115c5a357a
81 changed files with 5054 additions and 5089 deletions

View File

@ -24,6 +24,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
"resource:///modules/ContentLinkHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
"resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
"resource://gre/modules/InsecurePasswordUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluginContent",
@ -61,10 +63,13 @@ addMessageListener("RemoteLogins:fillForm", function(message) {
});
addEventListener("DOMFormHasPassword", function(event) {
LoginManagerContent.onDOMFormHasPassword(event, content);
InsecurePasswordUtils.checkForInsecurePasswords(event.target);
let formLike = FormLikeFactory.createFromForm(event.target);
InsecurePasswordUtils.checkForInsecurePasswords(formLike);
});
addEventListener("DOMInputPasswordAdded", function(event) {
LoginManagerContent.onDOMInputPasswordAdded(event, content);
let formLike = FormLikeFactory.createFromField(event.target);
InsecurePasswordUtils.checkForInsecurePasswords(formLike);
});
addEventListener("pageshow", function(event) {
LoginManagerContent.onPageShow(event, content);

View File

@ -15,8 +15,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
@ -58,32 +56,35 @@ Sanitizer.prototype = {
* occurs, a message is reported to the console and all other items are still
* cleared before the promise is finally rejected.
*
* If the consumer specifies the (optional) array parameter, only those
* items get cleared (irrespective of the preference settings)
* @param [optional] itemsToClear
* Array of items to be cleared. if specified only those
* items get cleared, irrespectively of the preference settings.
* @param [optional] options
* Object whose properties are options for this sanitization.
* TODO (bug 1167238) document options here.
*/
sanitize: Task.async(function*(aItemsToClear = null) {
let progress = {};
let promise = this._sanitize(aItemsToClear, progress);
sanitize: Task.async(function*(itemsToClear = null, options = {}) {
let progress = options.progress || {};
let promise = this._sanitize(itemsToClear, progress);
//
// Depending on preferences, the sanitizer may perform asynchronous
// work before it starts cleaning up the Places database (e.g. closing
// windows). We need to make sure that the connection to that database
// hasn't been closed by the time we use it.
//
let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
.getService(Ci.nsPIPlacesDatabase)
.shutdownClient
.jsclient;
shutdownClient.addBlocker("sanitize.js: Sanitize",
promise,
{
fetchState: () => {
return { progress };
// Though, if this is a sanitize on shutdown, we already have a blocker.
if (!progress.isShutdown) {
let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
.getService(Ci.nsPIPlacesDatabase)
.shutdownClient
.jsclient;
shutdownClient.addBlocker("sanitize.js: Sanitize",
promise,
{
fetchState: () => ({ progress })
}
}
);
);
}
try {
yield promise;
} finally {
@ -833,25 +834,23 @@ Sanitizer.onStartup = Task.async(function*() {
Services.prefs.savePrefFile(null);
}
// Make sure that we are triggered during shutdown, at the right time,
// and only once.
let placesClient = Cc["@mozilla.org/browser/nav-history-service;1"]
.getService(Ci.nsPIPlacesDatabase)
.shutdownClient
.jsclient;
let deferredSanitization = PromiseUtils.defer();
let sanitizationInProgress = false;
let doSanitize = function() {
if (!sanitizationInProgress) {
sanitizationInProgress = true;
Sanitizer.onShutdown().catch(er => {Promise.reject(er) /* Do not return rejected promise */;}).then(() =>
deferredSanitization.resolve()
);
// Make sure that we are triggered during shutdown.
let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
.getService(Ci.nsPIPlacesDatabase)
.shutdownClient
.jsclient;
// We need to pass to sanitize() (through sanitizeOnShutdown) a state object
// that tracks the status of the shutdown blocker. This `progress` object
// will be updated during sanitization and reported with the crash in case of
// a shutdown timeout.
// We use the `options` argument to pass the `progress` object to sanitize().
let progress = { isShutdown: true };
shutdownClient.addBlocker("sanitize.js: Sanitize on shutdown",
() => sanitizeOnShutdown({ progress }),
{
fetchState: () => ({ progress })
}
return deferredSanitization.promise;
}
placesClient.addBlocker("sanitize.js: Sanitize on shutdown", doSanitize);
);
// Check if Firefox crashed during a sanitization.
let lastInterruptedSanitization = Preferences.get(Sanitizer.PREF_SANITIZE_IN_PROGRESS, "");
@ -864,18 +863,18 @@ Sanitizer.onStartup = Task.async(function*() {
// Otherwise, could be we were supposed to sanitize on shutdown but we
// didn't have a chance, due to an earlier crash.
// In such a case, just redo a shutdown sanitize now, during startup.
yield Sanitizer.onShutdown();
yield sanitizeOnShutdown();
}
});
Sanitizer.onShutdown = Task.async(function*() {
var sanitizeOnShutdown = Task.async(function*(options = {}) {
if (!Preferences.get(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN)) {
return;
}
// Need to sanitize upon shutdown
let s = new Sanitizer();
s.prefDomain = "privacy.clearOnShutdown.";
yield s.sanitize();
yield s.sanitize(null, options);
// We didn't crash during shutdown sanitization, so annotate it to avoid
// sanitizing again on startup.
Preferences.set(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN, true);

View File

@ -1360,8 +1360,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
var popupDirection = aElement.ownerDocument.defaultView.getComputedStyle(aElement).direction;
this.style.direction = popupDirection;
// Make the popup's starting margin negaxtive so that the start of the
// popup aligns with the window border.
// Make the popup's starting margin negative so that the leading edge
// of the popup aligns with the window border.
let elementRect = aElement.getBoundingClientRect();
if (popupDirection == "rtl") {
let offset = elementRect.right - rect.right
@ -1371,6 +1371,30 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.style.marginLeft = offset + "px";
}
// Keep the popup items' site icons aligned with the urlbar's identity
// icon if it's not too far from the edge of the window. If there are
// at most two toolbar buttons between the window edge and the urlbar,
// then consider that as "not too far." The forward button's
// visibility may have changed since the last time the popup was
// opened, so this needs to happen now. Do it *before* the popup
// opens because otherwise the items will visibly shift.
let nodes = [...document.getElementById("nav-bar-customization-target").childNodes];
let urlbarPosition = nodes.findIndex(n => n.id == "urlbar-container");
let alignSiteIcons = urlbarPosition <= 2 &&
nodes.slice(0, urlbarPosition)
.every(n => n.localName == "toolbarbutton");
if (alignSiteIcons) {
let identityRect =
document.getElementById("identity-icon").getBoundingClientRect();
this.siteIconStart = popupDirection == "rtl" ? identityRect.right
: identityRect.left;
}
else {
// Reset the alignment so that the site icons are positioned
// according to whatever's in the CSS.
this.siteIconStart = undefined;
}
// Position the popup below the navbar. To get the y-coordinate,
// which is an offset from the bottom of the input, subtract the
// bottom of the navbar from the buttom of the input.
@ -2528,8 +2552,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// otherwise just rely on the panel id for common arrowpanels.
let type = this._panel.firstChild.getAttribute("popupid") ||
this._panel.id;
if (type == "password")
return "passwords";
if (type == "editBookmarkPanel")
return "bookmarks";
if (type == "addon-install-complete" || type == "addon-install-restart") {
@ -2586,17 +2608,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
let viewsLeft = this._viewsLeft;
if (viewsLeft) {
let notification = this._panel.firstElementChild.notification;
if (this._notificationType == "passwords" && notification && notification.options &&
notification.options.displayURI instanceof Ci.nsIStandardURL) {
let fxAOrigin = new URL(Services.prefs.getCharPref("identity.fxaccounts.remote.signup.uri")).origin
if (notification.options.displayURI.prePath == fxAOrigin) {
// Somewhat gross hack - we don't want to show the sync promo while
// the user may be logging into Sync.
return;
}
}
if (Services.prefs.prefHasUserValue("services.sync.username") &&
this._notificationType != "addons-sync-disabled") {
// If the user has already setup Sync, don't show the notification.

View File

@ -151,7 +151,7 @@ Object.assign(SyncedTabsListStore.prototype, EventEmitter.prototype, {
if (parent <= -1) {
parentRow = 0;
} else if (parent >= maxParentRow) {
parentRow = maxParentRow - 1;
return;
}
let childRow = child;

View File

@ -95,6 +95,9 @@ TabListView.prototype = {
this._renderClient(client);
}
}
if (this.list.firstChild) {
this.list.firstChild.querySelector(".item.tab:first-child .item-title").setAttribute("tabindex", 2);
}
},
destroy() {

View File

@ -66,7 +66,7 @@
<template id="tabs-container-template">
<div class="tabs-container">
<div class="list" role="listbox" tabindex="1"></div>
<div class="list" role="listbox"></div>
</div>
</template>
@ -97,7 +97,7 @@
<div class="sidebar-search-container tabs-container sync-state">
<div class="search-box compact">
<div class="textbox-input-box">
<input type="text" class="tabsFilter textbox-input"/>
<input type="text" class="tabsFilter textbox-input" tabindex="1"/>
<div class="textbox-search-icons">
<a class="textbox-search-clear"></a>
<a class="textbox-search-icon"></a>
@ -107,7 +107,7 @@
</div>
</div>
<!-- the scrollable content area where our templates are inserted -->
<div id="template-container" class="content-scrollable">
<div id="template-container" class="content-scrollable" tabindex="-1">
</div>
</div>
</body>

View File

@ -445,10 +445,6 @@ browser.menu.showCharacterEncoding=false
# the add bookmark star panel. %S will be replaced by syncBrandShortName.
# The final space separates this text from the Learn More link.
syncPromoNotification.bookmarks.description=You can access your bookmarks on all your devices with %S.\u0020
# LOCALIZATION NOTE (syncPromoNotification.passwords.label): This appears in
# the remember password panel. %S will be replaced by syncBrandShortName.
# The final space separates this text from the Learn More link.
syncPromoNotification.passwords.description=You can access your passwords on all your devices with %S.\u0020
syncPromoNotification.learnMoreLinkText=Learn More
# LOCALIZATION NOTE (syncPromoNotification.addons.label): This appears in
# the add-on install complete panel when Sync isn't set.

View File

@ -1166,11 +1166,6 @@ toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
.searchbar-textbox:not(:-moz-lwtheme)[focused] {
box-shadow: 0 0 0 1px Highlight inset;
}
/* overlap the urlbar's border and inset box-shadow */
#PopupAutoCompleteRichResult:not(:-moz-lwtheme) {
margin-top: -2px;
}
}
@media not all and (-moz-os-version: windows-xp) {

View File

@ -10,12 +10,6 @@
const { ANIMATION_TYPES } = require("devtools/server/actors/animation");
add_task(function* () {
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["dom.animations-api.core.enabled", true]
]}, resolve);
});
yield addTab(URL_ROOT + "doc_multiple_animation_types.html");
let {panel} = yield openAnimationInspector();

View File

@ -12,12 +12,6 @@
// rate changed have a delay = delay/rate and a duration = duration/rate.
add_task(function* () {
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["dom.animations-api.core.enabled", true]
]}, resolve);
});
yield addTab(URL_ROOT + "doc_modify_playbackRate.html");
let {panel} = yield openAnimationInspector();

View File

@ -31,6 +31,16 @@ registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
});
// WebAnimations API is not enabled by default in all release channels yet, see
// Bug 1264101.
function enableWebAnimationsAPI() {
return new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["dom.animations-api.core.enabled", true]
]}, resolve);
});
}
/**
* Add a new test tab in the browser and load the given url.
* @param {String} url The url to be loaded in the new tab
@ -38,7 +48,7 @@ registerCleanupFunction(() => {
*/
var _addTab = addTab;
addTab = function(url) {
return _addTab(url).then(tab => {
return enableWebAnimationsAPI().then(() => _addTab(url)).then(tab => {
let browser = tab.linkedBrowser;
info("Loading the helper frame script " + FRAME_SCRIPT_URL);
browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);

View File

@ -45,7 +45,7 @@
<legend>&options.commonPrefs.label;</legend>
<label title="&options.enablePersistentLogs.tooltip;">
<input type="checkbox" data-pref="devtools.webconsole.persistlog" />
&options.enablePersistentLogs.label;
<span>&options.enablePersistentLogs.label;</span>
</label>
</fieldset>

View File

@ -1052,9 +1052,8 @@ InspectorPanel.prototype = {
* Create a new node as the last child of the current selection, expand the
* parent and select the new node.
*/
addNode: Task.async(function*() {
let root = this.selection.nodeFront;
if (!this.canAddHTMLChild(root)) {
addNode: Task.async(function* () {
if (!this.canAddHTMLChild()) {
return;
}
@ -1062,7 +1061,8 @@ InspectorPanel.prototype = {
// Insert the html and expect a childList markup mutation.
let onMutations = this.once("markupmutation");
let {nodes} = yield this.walker.insertAdjacentHTML(root, "beforeEnd", html);
let {nodes} = yield this.walker.insertAdjacentHTML(this.selection.nodeFront,
"beforeEnd", html);
yield onMutations;
// Select the new node (this will auto-expand its parent).

View File

@ -82,6 +82,9 @@ var WaterfallView = Heritage.extend(DetailsSubview, {
*/
render: function(interval={}) {
let recording = PerformanceController.getCurrentRecording();
if (recording.isRecording()) {
return;
}
let startTime = interval.startTime || 0;
let endTime = interval.endTime || recording.getDuration();
let markers = recording.getMarkers();

View File

@ -26,7 +26,6 @@ this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools", "BuiltinProvider",
var loaderModules = {
"Services": Object.create(Services),
"toolkit/loader": Loader,
promise,
PromiseDebugging,
ChromeUtils,
ThreadSafeChromeUtils,
@ -82,6 +81,24 @@ XPCOMUtils.defineLazyGetter(loaderModules, "URL", () => {
return sandbox.URL;
});
const loaderPaths = {
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"": "resource://gre/modules/commonjs/",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"devtools": "resource://devtools",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"gcli": "resource://devtools/shared/gcli/source/lib/gcli",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"acorn": "resource://devtools/acorn",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"acorn/util/walk": "resource://devtools/acorn/walk.js",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"source-map": "resource://devtools/shared/sourcemap/source-map.js",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
// Allow access to xpcshell test items from the loader.
"xpcshell-test": "resource://test",
};
var sharedGlobalBlocklist = ["sdk/indexed-db"];
/**
@ -91,27 +108,31 @@ var sharedGlobalBlocklist = ["sdk/indexed-db"];
function BuiltinProvider() {}
BuiltinProvider.prototype = {
load: function() {
// Copy generic paths and modules for this loader instance
let paths = {};
for (let path in loaderPaths) {
paths[path] = loaderPaths[path];
}
let modules = {};
for (let name in loaderModules) {
XPCOMUtils.defineLazyGetter(modules, name, (function (name) {
return loaderModules[name];
}).bind(null, name));
}
// When creating a Loader invisible to the Debugger, we have to ensure
// using only modules and not depend on any JSM. As everything that is
// not loaded with Loader isn't going to respect `invisibleToDebugger`.
// But we have to keep using Promise.jsm for other loader to prevent
// breaking unhandled promise rejection in tests.
if (this.invisibleToDebugger) {
paths["promise"] = "resource://gre/modules/Promise-backend.js";
} else {
modules["promise"] = promise;
}
this.loader = new Loader.Loader({
id: "fx-devtools",
modules: loaderModules,
paths: {
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"": "resource://gre/modules/commonjs/",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"devtools": "resource://devtools",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"gcli": "resource://devtools/shared/gcli/source/lib/gcli",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"acorn": "resource://devtools/acorn",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"acorn/util/walk": "resource://devtools/acorn/walk.js",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
"source-map": "resource://devtools/shared/sourcemap/source-map.js",
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
// Allow access to xpcshell test items from the loader.
"xpcshell-test": "resource://test"
// ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
},
modules,
paths,
globals: this.globals,
invisibleToDebugger: this.invisibleToDebugger,
sharedGlobal: true,

View File

@ -27,6 +27,11 @@ function visible_loader() {
} catch(e) {
do_throw("debugger could not add visible value");
}
// Check that for common loader used for tabs, promise modules is Promise.jsm
// Which is required to support unhandled promises rejection in mochitests
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
do_check_eq(loader.require("promise"), promise);
}
function invisible_loader() {
@ -43,4 +48,11 @@ function invisible_loader() {
} catch(e) {
do_check_true(true);
}
// But for browser toolbox loader, promise is loaded as a regular modules out
// of Promise-backend.js, that to be invisible to the debugger and not step
// into it.
const promise = loader.require("promise");
const promiseModule = loader._provider.loader.modules["resource://gre/modules/Promise-backend.js"];
do_check_eq(promise, promiseModule.exports);
}

View File

@ -1027,60 +1027,3 @@ function ArrayStaticConcat(arr, arg1) {
var args = callFunction(std_Array_slice, arguments, 1);
return callFunction(std_Function_apply, ArrayConcat, arr, args);
}
function ArrayStaticJoin(arr, separator) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.join');
return callFunction(std_Array_join, arr, separator);
}
function ArrayStaticReverse(arr) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.reverse');
return callFunction(std_Array_reverse, arr);
}
function ArrayStaticSort(arr, comparefn) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.sort');
return callFunction(std_Array_sort, arr, comparefn);
}
function ArrayStaticPush(arr, arg1) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.push');
var args = callFunction(std_Array_slice, arguments, 1);
return callFunction(std_Function_apply, std_Array_push, arr, args);
}
function ArrayStaticPop(arr) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.pop');
return callFunction(std_Array_pop, arr);
}
function ArrayStaticShift(arr) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.shift');
return callFunction(std_Array_shift, arr);
}
function ArrayStaticUnshift(arr, arg1) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.unshift');
var args = callFunction(std_Array_slice, arguments, 1);
return callFunction(std_Function_apply, std_Array_unshift, arr, args);
}
function ArrayStaticSplice(arr, start, deleteCount) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.splice');
var args = callFunction(std_Array_slice, arguments, 1);
return callFunction(std_Function_apply, std_Array_splice, arr, args);
}
function ArrayStaticSlice(arr, start, end) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.slice');
return callFunction(std_Array_slice, arr, start, end);
}

View File

@ -722,11 +722,7 @@ function String_static_localeCompare(str1, str2) {
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "String.localeCompare");
var locales = arguments.length > 2 ? arguments[2] : undefined;
var options = arguments.length > 3 ? arguments[3] : undefined;
#if EXPOSE_INTL_API
return callFunction(String_localeCompare, str1, str2, locales, options);
#else
return callFunction(std_String_localeCompare, str1, str2, locales, options);
#endif
}
// ES6 draft 2014-04-27 B.2.3.3
@ -828,108 +824,3 @@ function String_link(url) {
var S = ToString(this);
return '<a href="' + EscapeAttributeValue(url) + '">' + S + "</a>";
}
function String_static_toLowerCase(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLowerCase');
return callFunction(std_String_toLowerCase, string);
}
function String_static_toUpperCase(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toUpperCase');
return callFunction(std_String_toUpperCase, string);
}
function String_static_charAt(string, pos) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.charAt');
return callFunction(std_String_charAt, string, pos);
}
function String_static_charCodeAt(string, pos) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.charCodeAt');
return callFunction(std_String_charCodeAt, string, pos);
}
function String_static_includes(string, searchString) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.includes');
var position = arguments.length > 2 ? arguments[2] : undefined;
return callFunction(std_String_includes, string, searchString, position);
}
function String_static_indexOf(string, searchString) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.indexOf');
var position = arguments.length > 2 ? arguments[2] : undefined;
return callFunction(std_String_indexOf, string, searchString, position);
}
function String_static_lastIndexOf(string, searchString) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.lastIndexOf');
var position = arguments.length > 2 ? arguments[2] : undefined;
return callFunction(std_String_lastIndexOf, string, searchString, position);
}
function String_static_startsWith(string, searchString) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.startsWith');
var position = arguments.length > 2 ? arguments[2] : undefined;
return callFunction(std_String_startsWith, string, searchString, position);
}
function String_static_endsWith(string, searchString) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.endsWith');
var endPosition = arguments.length > 2 ? arguments[2] : undefined;
return callFunction(std_String_endsWith, string, searchString, endPosition);
}
function String_static_trim(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trim');
return callFunction(std_String_trim, string);
}
function String_static_trimLeft(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trimLeft');
return callFunction(std_String_trimLeft, string);
}
function String_static_trimRight(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trimRight');
return callFunction(std_String_trimRight, string);
}
function String_static_toLocaleLowerCase(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLocaleLowerCase');
return callFunction(std_String_toLocaleLowerCase, string);
}
function String_static_toLocaleUpperCase(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLocaleUpperCase');
return callFunction(std_String_toLocaleUpperCase, string);
}
#if EXPOSE_INTL_API
function String_static_normalize(string) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.normalize');
var form = arguments.length > 1 ? arguments[1] : undefined;
return callFunction(std_String_normalize, string, form);
}
#endif
function String_static_concat(string, arg1) {
if (arguments.length < 1)
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.concat');
var args = callFunction(std_Array_slice, arguments, 1);
return callFunction(std_Function_apply, std_String_concat, string, args);
}

View File

@ -1,16 +0,0 @@
if (!('oomTest' in this))
quit();
evalcx(`
eval('\
var appendToActual = function(s) {};\
gczeal = function() {};\
gcslice = function() {};\
selectforgc = function() {};\
if (!("verifyprebarriers" in this)) {\
verifyprebarriers = function() {};\
}\
');
oomTest(() => eval('Array(..."")'));
Intl.NumberFormat.prototype.format(0);
`, newGlobal());

View File

@ -837,7 +837,17 @@ class MOZ_STACK_CLASS SourceBufferHolder final
#define JSFUN_CONSTRUCTOR 0x400 /* native that can be called as a ctor */
// 0x800 /* Unused */
/*
* Specify a generic native prototype methods, i.e., methods of a class
* prototype that are exposed as static methods taking an extra leading
* argument: the generic |this| parameter.
*
* If you set this flag in a JSFunctionSpec struct's flags initializer, then
* that struct must live at least as long as the native static method object
* created due to this flag by JS_DefineFunctions or JS_InitClass. Typically
* JSFunctionSpec structs are allocated in static arrays.
*/
#define JSFUN_GENERIC_NATIVE 0x800
#define JSFUN_HAS_REST 0x1000 /* function has ...rest parameter. */

View File

@ -1390,8 +1390,8 @@ ArrayReverseDenseKernel(JSContext* cx, HandleObject obj, uint32_t length)
DefineBoxedOrUnboxedFunctor3(ArrayReverseDenseKernel,
JSContext*, HandleObject, uint32_t);
bool
js::array_reverse(JSContext* cx, unsigned argc, Value* vp)
static bool
array_reverse(JSContext* cx, unsigned argc, Value* vp)
{
AutoSPSEntry pseudoFrame(cx->runtime(), "Array.prototype.reverse");
CallArgs args = CallArgsFromVp(argc, vp);
@ -2381,8 +2381,8 @@ CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t co
}
/* ES 2016 draft Mar 25, 2016 22.1.3.26. */
bool
js::array_splice(JSContext* cx, unsigned argc, Value* vp)
static bool
array_splice(JSContext* cx, unsigned argc, Value* vp)
{
return array_splice_impl(cx, argc, vp, true);
}
@ -3082,6 +3082,8 @@ array_of(JSContext* cx, unsigned argc, Value* vp)
return true;
}
#define GENERIC JSFUN_GENERIC_NATIVE
static const JSFunctionSpec array_methods[] = {
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, array_toSource, 0,0),
@ -3090,18 +3092,18 @@ static const JSFunctionSpec array_methods[] = {
JS_FN(js_toLocaleString_str, array_toLocaleString, 0,0),
/* Perl-ish methods. */
JS_INLINABLE_FN("join", array_join, 1,0, ArrayJoin),
JS_FN("reverse", array_reverse, 0,0),
JS_FN("sort", array_sort, 1,0),
JS_INLINABLE_FN("push", array_push, 1,0, ArrayPush),
JS_INLINABLE_FN("pop", array_pop, 0,0, ArrayPop),
JS_INLINABLE_FN("shift", array_shift, 0,0, ArrayShift),
JS_FN("unshift", array_unshift, 1,0),
JS_INLINABLE_FN("splice", array_splice, 2,0, ArraySplice),
JS_INLINABLE_FN("join", array_join, 1,JSFUN_GENERIC_NATIVE, ArrayJoin),
JS_FN("reverse", array_reverse, 0,JSFUN_GENERIC_NATIVE),
JS_FN("sort", array_sort, 1,JSFUN_GENERIC_NATIVE),
JS_INLINABLE_FN("push", array_push, 1,JSFUN_GENERIC_NATIVE, ArrayPush),
JS_INLINABLE_FN("pop", array_pop, 0,JSFUN_GENERIC_NATIVE, ArrayPop),
JS_INLINABLE_FN("shift", array_shift, 0,JSFUN_GENERIC_NATIVE, ArrayShift),
JS_FN("unshift", array_unshift, 1,JSFUN_GENERIC_NATIVE),
JS_INLINABLE_FN("splice", array_splice, 2,JSFUN_GENERIC_NATIVE, ArraySplice),
/* Pythonic sequence methods. */
JS_SELF_HOSTED_FN("concat", "ArrayConcat", 1,0),
JS_INLINABLE_FN("slice", array_slice, 2,0, ArraySlice),
JS_INLINABLE_FN("slice", array_slice, 2,JSFUN_GENERIC_NATIVE, ArraySlice),
JS_SELF_HOSTED_FN("lastIndexOf", "ArrayLastIndexOf", 1,0),
JS_SELF_HOSTED_FN("indexOf", "ArrayIndexOf", 1,0),
@ -3142,15 +3144,6 @@ static const JSFunctionSpec array_static_methods[] = {
JS_SELF_HOSTED_FN("some", "ArrayStaticSome", 2,0),
JS_SELF_HOSTED_FN("reduce", "ArrayStaticReduce", 2,0),
JS_SELF_HOSTED_FN("reduceRight", "ArrayStaticReduceRight", 2,0),
JS_SELF_HOSTED_FN("join", "ArrayStaticJoin", 2,0),
JS_SELF_HOSTED_FN("reverse", "ArrayStaticReverse", 1,0),
JS_SELF_HOSTED_FN("sort", "ArrayStaticSort", 2,0),
JS_SELF_HOSTED_FN("push", "ArrayStaticPush", 2,0),
JS_SELF_HOSTED_FN("pop", "ArrayStaticPop", 1,0),
JS_SELF_HOSTED_FN("shift", "ArrayStaticShift", 1,0),
JS_SELF_HOSTED_FN("unshift", "ArrayStaticUnshift", 2,0),
JS_SELF_HOSTED_FN("splice", "ArrayStaticSplice", 3,0),
JS_SELF_HOSTED_FN("slice", "ArrayStaticSlice", 3,0),
JS_SELF_HOSTED_FN("from", "ArrayFrom", 3,0),
JS_FN("of", array_of, 0,0),

View File

@ -184,12 +184,6 @@ array_slice(JSContext* cx, unsigned argc, js::Value* vp);
extern JSObject*
array_slice_dense(JSContext* cx, HandleObject obj, int32_t begin, int32_t end, HandleObject result);
extern bool
array_reverse(JSContext* cx, unsigned argc, js::Value* vp);
extern bool
array_splice(JSContext* cx, unsigned argc, js::Value* vp);
/*
* Append the given (non-hole) value to the end of an array. The array must be
* a newborn array -- that is, one which has not been exposed to script for

View File

@ -2899,6 +2899,34 @@ js::HasDataProperty(JSContext* cx, NativeObject* obj, jsid id, Value* vp)
return false;
}
static bool
GenericNativeMethodDispatcher(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const JSFunctionSpec* fs = (JSFunctionSpec*)
args.callee().as<JSFunction>().getExtendedSlot(0).toPrivate();
MOZ_ASSERT((fs->flags & JSFUN_GENERIC_NATIVE) != 0);
if (argc < 1) {
ReportMissingArg(cx, args.calleev(), 0);
return false;
}
/*
* Copy all actual (argc) arguments down over our |this| parameter, vp[1],
* which is almost always the class constructor object, e.g. Array. Then
* call the corresponding prototype native method with our first argument
* passed as |this|.
*/
memmove(vp + 1, vp + 2, argc * sizeof(Value));
/* Clear the last parameter in case too few arguments were passed. */
vp[2 + --argc].setUndefined();
return fs->call.op(cx, argc, vp);
}
extern bool
PropertySpecNameToId(JSContext* cx, const char* name, MutableHandleId id,
js::PinningBehavior pin = js::DoNotPinAtom);
@ -2926,6 +2954,27 @@ DefineFunctionFromSpec(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs
if (!PropertySpecNameToId(cx, fs->name, &id))
return false;
// Define a generic arity N+1 static method for the arity N prototype
// method if flags contains JSFUN_GENERIC_NATIVE.
if (flags & JSFUN_GENERIC_NATIVE) {
// We require that any consumers using JSFUN_GENERIC_NATIVE stash
// the prototype and constructor in the global slots before invoking
// JS_DefineFunctions on the proto.
JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass());
MOZ_ASSERT(obj == &obj->global().getPrototype(key).toObject());
RootedObject ctor(cx, &obj->global().getConstructor(key).toObject());
flags &= ~JSFUN_GENERIC_NATIVE;
JSFunction* fun = DefineFunction(cx, ctor, id,
GenericNativeMethodDispatcher,
fs->nargs + 1, flags,
gc::AllocKind::FUNCTION_EXTENDED);
if (!fun)
return false;
fun->setExtendedSlot(0, PrivateValue(const_cast<JSFunctionSpec*>(fs)));
}
JSFunction* fun = NewFunctionFromSpec(cx, fs, id);
if (!fun)
return false;

View File

@ -682,8 +682,8 @@ js::str_toLowerCase(JSContext* cx, unsigned argc, Value* vp)
return ToLowerCaseHelper(cx, CallArgsFromVp(argc, vp));
}
bool
js::str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
static bool
str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@ -833,8 +833,8 @@ js::str_toUpperCase(JSContext* cx, unsigned argc, Value* vp)
return ToUpperCaseHelper(cx, CallArgsFromVp(argc, vp));
}
bool
js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
static bool
str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@ -859,8 +859,8 @@ js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
}
#if !EXPOSE_INTL_API
bool
js::str_localeCompare(JSContext* cx, unsigned argc, Value* vp)
static bool
str_localeCompare(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedString str(cx, ThisToStringForStringProto(cx, args));
@ -891,8 +891,8 @@ js::str_localeCompare(JSContext* cx, unsigned argc, Value* vp)
#if EXPOSE_INTL_API
/* ES6 20140210 draft 21.1.3.12. */
bool
js::str_normalize(JSContext* cx, unsigned argc, Value* vp)
static bool
str_normalize(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@ -1820,8 +1820,8 @@ js::str_startsWith(JSContext* cx, unsigned argc, Value* vp)
}
/* ES6 draft rc3 21.1.3.6. */
bool
js::str_endsWith(JSContext* cx, unsigned argc, Value* vp)
static bool
str_endsWith(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@ -1939,20 +1939,20 @@ TrimString(JSContext* cx, Value* vp, bool trimLeft, bool trimRight)
return true;
}
bool
js::str_trim(JSContext* cx, unsigned argc, Value* vp)
static bool
str_trim(JSContext* cx, unsigned argc, Value* vp)
{
return TrimString(cx, vp, true, true);
}
bool
js::str_trimLeft(JSContext* cx, unsigned argc, Value* vp)
static bool
str_trimLeft(JSContext* cx, unsigned argc, Value* vp)
{
return TrimString(cx, vp, true, false);
}
bool
js::str_trimRight(JSContext* cx, unsigned argc, Value* vp)
static bool
str_trimRight(JSContext* cx, unsigned argc, Value* vp)
{
return TrimString(cx, vp, false, true);
}
@ -2486,8 +2486,8 @@ js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, H
/*
* Python-esque sequence operations.
*/
bool
js::str_concat(JSContext* cx, unsigned argc, Value* vp)
static bool
str_concat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JSString* str = ThisToStringForStringProto(cx, args);
@ -2527,32 +2527,32 @@ static const JSFunctionSpec string_methods[] = {
/* Java-like methods. */
JS_FN(js_toString_str, str_toString, 0,0),
JS_FN(js_valueOf_str, str_toString, 0,0),
JS_FN("toLowerCase", str_toLowerCase, 0,0),
JS_FN("toUpperCase", str_toUpperCase, 0,0),
JS_INLINABLE_FN("charAt", str_charAt, 1,0, StringCharAt),
JS_INLINABLE_FN("charCodeAt", str_charCodeAt, 1,0, StringCharCodeAt),
JS_FN("toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE),
JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE),
JS_INLINABLE_FN("charAt", str_charAt, 1,JSFUN_GENERIC_NATIVE, StringCharAt),
JS_INLINABLE_FN("charCodeAt", str_charCodeAt, 1,JSFUN_GENERIC_NATIVE, StringCharCodeAt),
JS_SELF_HOSTED_FN("substring", "String_substring", 2,0),
JS_SELF_HOSTED_FN("padStart", "String_pad_start", 2,0),
JS_SELF_HOSTED_FN("padEnd", "String_pad_end", 2,0),
JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0),
JS_FN("includes", str_includes, 1,0),
JS_FN("indexOf", str_indexOf, 1,0),
JS_FN("lastIndexOf", str_lastIndexOf, 1,0),
JS_FN("startsWith", str_startsWith, 1,0),
JS_FN("endsWith", str_endsWith, 1,0),
JS_FN("trim", str_trim, 0,0),
JS_FN("trimLeft", str_trimLeft, 0,0),
JS_FN("trimRight", str_trimRight, 0,0),
JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,0),
JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,0),
JS_FN("includes", str_includes, 1,JSFUN_GENERIC_NATIVE),
JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE),
JS_FN("lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE),
JS_FN("startsWith", str_startsWith, 1,JSFUN_GENERIC_NATIVE),
JS_FN("endsWith", str_endsWith, 1,JSFUN_GENERIC_NATIVE),
JS_FN("trim", str_trim, 0,JSFUN_GENERIC_NATIVE),
JS_FN("trimLeft", str_trimLeft, 0,JSFUN_GENERIC_NATIVE),
JS_FN("trimRight", str_trimRight, 0,JSFUN_GENERIC_NATIVE),
JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,JSFUN_GENERIC_NATIVE),
JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,JSFUN_GENERIC_NATIVE),
#if EXPOSE_INTL_API
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
#else
JS_FN("localeCompare", str_localeCompare, 1,0),
JS_FN("localeCompare", str_localeCompare, 1,JSFUN_GENERIC_NATIVE),
#endif
JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
#if EXPOSE_INTL_API
JS_FN("normalize", str_normalize, 0,0),
JS_FN("normalize", str_normalize, 0,JSFUN_GENERIC_NATIVE),
#endif
/* Perl-ish methods (search is actually Python-esque). */
@ -2563,7 +2563,7 @@ static const JSFunctionSpec string_methods[] = {
JS_SELF_HOSTED_FN("substr", "String_substr", 2,0),
/* Python-esque sequence methods. */
JS_FN("concat", str_concat, 1,0),
JS_FN("concat", str_concat, 1,JSFUN_GENERIC_NATIVE),
JS_SELF_HOSTED_FN("slice", "String_slice", 2,0),
/* HTML string methods. */
@ -2716,26 +2716,11 @@ static const JSFunctionSpec string_static_methods[] = {
JS_SELF_HOSTED_FN("search", "String_generic_search", 2,0),
JS_SELF_HOSTED_FN("split", "String_generic_split", 3,0),
JS_SELF_HOSTED_FN("toLowerCase", "String_static_toLowerCase", 1,0),
JS_SELF_HOSTED_FN("toUpperCase", "String_static_toUpperCase", 1,0),
JS_SELF_HOSTED_FN("charAt", "String_static_charAt", 2,0),
JS_SELF_HOSTED_FN("charCodeAt", "String_static_charCodeAt", 2,0),
JS_SELF_HOSTED_FN("includes", "String_static_includes", 2,0),
JS_SELF_HOSTED_FN("indexOf", "String_static_indexOf", 2,0),
JS_SELF_HOSTED_FN("lastIndexOf", "String_static_lastIndexOf", 2,0),
JS_SELF_HOSTED_FN("startsWith", "String_static_startsWith", 2,0),
JS_SELF_HOSTED_FN("endsWith", "String_static_endsWith", 2,0),
JS_SELF_HOSTED_FN("trim", "String_static_trim", 1,0),
JS_SELF_HOSTED_FN("trimLeft", "String_static_trimLeft", 1,0),
JS_SELF_HOSTED_FN("trimRight", "String_static_trimRight", 1,0),
JS_SELF_HOSTED_FN("toLocaleLowerCase","String_static_toLocaleLowerCase",1,0),
JS_SELF_HOSTED_FN("toLocaleUpperCase","String_static_toLocaleUpperCase",1,0),
// This must be at the end because of bug 853075: functions listed after
// self-hosted methods aren't available in self-hosted code.
#if EXPOSE_INTL_API
JS_SELF_HOSTED_FN("normalize", "String_static_normalize", 1,0),
#endif
JS_SELF_HOSTED_FN("concat", "String_static_concat", 2,0),
JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0),
#endif
JS_FS_END
};
@ -2767,6 +2752,9 @@ js::InitStringClass(JSContext* cx, HandleObject obj)
if (!ctor)
return nullptr;
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_String, ctor, proto))
return nullptr;
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -2783,9 +2771,6 @@ js::InitStringClass(JSContext* cx, HandleObject obj)
if (!JS_DefineFunctions(cx, global, string_functions))
return nullptr;
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_String, ctor, proto))
return nullptr;
return proto;
}

View File

@ -338,39 +338,6 @@ str_charCodeAt_impl(JSContext* cx, HandleString string, HandleValue index, Mutab
extern bool
str_charCodeAt(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_contains(JSContext *cx, unsigned argc, Value *vp);
extern bool
str_endsWith(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_trim(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_trimLeft(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_trimRight(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp);
#if !EXPOSE_INTL_API
extern bool
str_localeCompare(JSContext* cx, unsigned argc, Value* vp);
#else
extern bool
str_normalize(JSContext* cx, unsigned argc, Value* vp);
#endif
extern bool
str_concat(JSContext* cx, unsigned argc, Value* vp);
/*
* Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
* least 4 bytes long. Return the number of UTF-8 bytes of data written.

View File

@ -1,331 +0,0 @@
var BUGNUMBER = 1263558;
var summary = "Self-host all Array generics.";
print(BUGNUMBER + ": " + summary);
var arr, arrLike, tmp, f;
function reset() {
arr = [5, 7, 13];
arrLike = {
length: 3,
0: 5,
1: 7,
2: 13,
toString() {
return "arrLike";
}
};
tmp = [];
}
function toString() {
return "G";
}
// Array.join (test this first to use it in remaining tests).
reset();
assertThrowsInstanceOf(() => Array.join(), TypeError);
assertEq(Array.join(arr), "5,7,13");
assertEq(Array.join(arr, "-"), "5-7-13");
assertEq(Array.join(arrLike), "5,7,13");
assertEq(Array.join(arrLike, "-"), "5-7-13");
// Array.concat.
reset();
assertThrowsInstanceOf(() => Array.concat(), TypeError);
assertEq(Array.join(Array.concat(arr), ","), "5,7,13");
assertEq(Array.join(Array.concat(arr, 11), ","), "5,7,13,11");
assertEq(Array.join(Array.concat(arr, 11, 17), ","), "5,7,13,11,17");
assertEq(Array.join(Array.concat(arrLike), ","), "arrLike");
assertEq(Array.join(Array.concat(arrLike, 11), ","), "arrLike,11");
assertEq(Array.join(Array.concat(arrLike, 11, 17), ","), "arrLike,11,17");
// Array.lastIndexOf.
reset();
assertThrowsInstanceOf(() => Array.lastIndexOf(), TypeError);
assertEq(Array.lastIndexOf(arr), -1);
assertEq(Array.lastIndexOf(arr, 1), -1);
assertEq(Array.lastIndexOf(arr, 5), 0);
assertEq(Array.lastIndexOf(arr, 7), 1);
assertEq(Array.lastIndexOf(arr, 13, 1), -1);
assertEq(Array.lastIndexOf(arrLike), -1);
assertEq(Array.lastIndexOf(arrLike, 1), -1);
assertEq(Array.lastIndexOf(arrLike, 5), 0);
assertEq(Array.lastIndexOf(arrLike, 7), 1);
assertEq(Array.lastIndexOf(arrLike, 13, 1), -1);
// Array.indexOf.
reset();
assertThrowsInstanceOf(() => Array.indexOf(), TypeError);
assertEq(Array.indexOf(arr), -1);
assertEq(Array.indexOf(arr, 1), -1);
assertEq(Array.indexOf(arr, 5), 0);
assertEq(Array.indexOf(arr, 7), 1);
assertEq(Array.indexOf(arr, 1, 5), -1);
assertEq(Array.indexOf(arrLike), -1);
assertEq(Array.indexOf(arrLike, 1), -1);
assertEq(Array.indexOf(arrLike, 5), 0);
assertEq(Array.indexOf(arrLike, 7), 1);
assertEq(Array.indexOf(arrLike, 1, 5), -1);
// Array.forEach.
reset();
assertThrowsInstanceOf(() => Array.forEach(), TypeError);
assertThrowsInstanceOf(() => Array.forEach(arr), TypeError);
assertThrowsInstanceOf(() => Array.forEach(arrLike), TypeError);
f = function(...args) {
tmp.push(this, ...args);
};
tmp = [];
Array.forEach(arr, f);
assertEq(tmp.join(","), "G,5,0,5,7,13," + "G,7,1,5,7,13," + "G,13,2,5,7,13");
tmp = [];
Array.forEach(arr, f, "T");
assertEq(tmp.join(","), "T,5,0,5,7,13," + "T,7,1,5,7,13," + "T,13,2,5,7,13");
tmp = [];
Array.forEach(arrLike, f);
assertEq(tmp.join(","), "G,5,0,arrLike," + "G,7,1,arrLike," + "G,13,2,arrLike");
tmp = [];
Array.forEach(arrLike, f, "T");
assertEq(tmp.join(","), "T,5,0,arrLike," + "T,7,1,arrLike," + "T,13,2,arrLike");
// Array.map.
reset();
assertThrowsInstanceOf(() => Array.map(), TypeError);
assertThrowsInstanceOf(() => Array.map(arr), TypeError);
assertThrowsInstanceOf(() => Array.map(arrLike), TypeError);
f = function(...args) {
tmp.push(this, ...args);
return args[0] * 2;
}
tmp = [];
assertEq(Array.join(Array.map(arr, f), ","), "10,14,26");
assertEq(tmp.join(","), "G,5,0,5,7,13," + "G,7,1,5,7,13," + "G,13,2,5,7,13");
tmp = [];
assertEq(Array.join(Array.map(arr, f, "T"), ","), "10,14,26");
assertEq(tmp.join(","), "T,5,0,5,7,13," + "T,7,1,5,7,13," + "T,13,2,5,7,13");
tmp = [];
assertEq(Array.join(Array.map(arrLike, f), ","), "10,14,26");
assertEq(tmp.join(","), "G,5,0,arrLike," + "G,7,1,arrLike," + "G,13,2,arrLike");
tmp = [];
assertEq(Array.join(Array.map(arrLike, f, "T"), ","), "10,14,26");
assertEq(tmp.join(","), "T,5,0,arrLike," + "T,7,1,arrLike," + "T,13,2,arrLike");
// Array.filter.
reset();
assertThrowsInstanceOf(() => Array.filter(), TypeError);
assertThrowsInstanceOf(() => Array.filter(arr), TypeError);
assertThrowsInstanceOf(() => Array.filter(arrLike), TypeError);
f = function(...args) {
tmp.push(this, ...args);
return args[0] < 10;
}
tmp = [];
assertEq(Array.join(Array.filter(arr, f), ","), "5,7");
assertEq(tmp.join(","), "G,5,0,5,7,13," + "G,7,1,5,7,13," + "G,13,2,5,7,13");
tmp = [];
assertEq(Array.join(Array.filter(arr, f, "T"), ","), "5,7");
assertEq(tmp.join(","), "T,5,0,5,7,13," + "T,7,1,5,7,13," + "T,13,2,5,7,13");
tmp = [];
assertEq(Array.join(Array.filter(arrLike, f), ","), "5,7");
assertEq(tmp.join(","), "G,5,0,arrLike," + "G,7,1,arrLike," + "G,13,2,arrLike");
tmp = [];
assertEq(Array.join(Array.filter(arrLike, f, "T"), ","), "5,7");
assertEq(tmp.join(","), "T,5,0,arrLike," + "T,7,1,arrLike," + "T,13,2,arrLike");
// Array.every.
reset();
assertThrowsInstanceOf(() => Array.every(), TypeError);
assertThrowsInstanceOf(() => Array.every(arr), TypeError);
assertThrowsInstanceOf(() => Array.every(arrLike), TypeError);
f = function(...args) {
tmp.push(this, ...args);
return args[0] < 6;
}
tmp = [];
assertEq(Array.every(arr, f), false);
assertEq(tmp.join(","), "G,5,0,5,7,13," + "G,7,1,5,7,13");
tmp = [];
assertEq(Array.every(arr, f, "T"), false);
assertEq(tmp.join(","), "T,5,0,5,7,13," + "T,7,1,5,7,13");
tmp = [];
assertEq(Array.every(arrLike, f), false);
assertEq(tmp.join(","), "G,5,0,arrLike," + "G,7,1,arrLike");
tmp = [];
assertEq(Array.every(arrLike, f, "T"), false);
assertEq(tmp.join(","), "T,5,0,arrLike," + "T,7,1,arrLike");
// Array.some.
reset();
assertThrowsInstanceOf(() => Array.some(), TypeError);
assertThrowsInstanceOf(() => Array.some(arr), TypeError);
assertThrowsInstanceOf(() => Array.some(arrLike), TypeError);
f = function(...args) {
tmp.push(this, ...args);
return args[0] == 7;
}
tmp = [];
assertEq(Array.some(arr, f), true);
assertEq(tmp.join(","), "G,5,0,5,7,13," + "G,7,1,5,7,13");
tmp = [];
assertEq(Array.some(arr, f, "T"), true);
assertEq(tmp.join(","), "T,5,0,5,7,13," + "T,7,1,5,7,13");
tmp = [];
assertEq(Array.some(arrLike, f), true);
assertEq(tmp.join(","), "G,5,0,arrLike," + "G,7,1,arrLike");
tmp = [];
assertEq(Array.some(arrLike, f, "T"), true);
assertEq(tmp.join(","), "T,5,0,arrLike," + "T,7,1,arrLike");
// Array.reduce.
reset();
assertThrowsInstanceOf(() => Array.reduce(), TypeError);
assertThrowsInstanceOf(() => Array.reduce(arr), TypeError);
assertThrowsInstanceOf(() => Array.reduce(arrLike), TypeError);
f = function(...args) {
tmp.push(...args);
return args[0] + args[1];
}
tmp = [];
assertEq(Array.reduce(arr, f), 25);
assertEq(tmp.join(","), "5,7,1,5,7,13," + "12,13,2,5,7,13");
tmp = [];
assertEq(Array.reduce(arr, f, 17), 42);
assertEq(tmp.join(","), "17,5,0,5,7,13," + "22,7,1,5,7,13," + "29,13,2,5,7,13");
tmp = [];
assertEq(Array.reduce(arrLike, f), 25);
assertEq(tmp.join(","), "5,7,1,arrLike," + "12,13,2,arrLike");
tmp = [];
assertEq(Array.reduce(arrLike, f, 17), 42);
assertEq(tmp.join(","), "17,5,0,arrLike," + "22,7,1,arrLike," + "29,13,2,arrLike");
// Array.reduceRight.
reset();
assertThrowsInstanceOf(() => Array.reduceRight(), TypeError);
assertThrowsInstanceOf(() => Array.reduceRight(arr), TypeError);
assertThrowsInstanceOf(() => Array.reduceRight(arrLike), TypeError);
f = function(...args) {
tmp.push(...args);
return args[0] + args[1];
}
tmp = [];
assertEq(Array.reduceRight(arr, f), 25);
assertEq(tmp.join(","), "13,7,1,5,7,13," + "20,5,0,5,7,13");
tmp = [];
assertEq(Array.reduceRight(arr, f, 17), 42);
assertEq(tmp.join(","), "17,13,2,5,7,13," + "30,7,1,5,7,13," + "37,5,0,5,7,13");
tmp = [];
assertEq(Array.reduceRight(arrLike, f), 25);
assertEq(tmp.join(","), "13,7,1,arrLike," + "20,5,0,arrLike");
tmp = [];
assertEq(Array.reduceRight(arrLike, f, 17), 42);
assertEq(tmp.join(","), "17,13,2,arrLike," + "30,7,1,arrLike," + "37,5,0,arrLike");
// Array.reverse.
reset();
assertThrowsInstanceOf(() => Array.reverse(), TypeError);
assertEq(Array.join(Array.reverse(arr), ","), "13,7,5");
assertEq(Array.join(arr, ","), "13,7,5");
assertEq(Array.join(Array.reverse(arrLike), ","), "13,7,5");
assertEq(Array.join(arrLike, ","), "13,7,5");
// Array.sort.
reset();
assertThrowsInstanceOf(() => Array.sort(), TypeError);
f = function(x, y) {
return y - x;
}
assertEq(Array.join(Array.sort(arr), ","), "13,5,7");
assertEq(Array.join(Array.sort(arr, f), ","), "13,7,5");
assertEq(Array.join(Array.sort(arrLike), ","), "13,5,7");
assertEq(Array.join(Array.sort(arrLike, f), ","), "13,7,5");
// Array.push.
reset();
assertThrowsInstanceOf(() => Array.push(), TypeError);
assertEq(Array.push(arr), 3);
assertEq(Array.join(arr), "5,7,13");
assertEq(Array.push(arr, 17), 4);
assertEq(Array.join(arr), "5,7,13,17");
assertEq(Array.push(arr, 19, 21), 6);
assertEq(Array.join(arr), "5,7,13,17,19,21");
assertEq(Array.push(arrLike), 3);
assertEq(Array.join(arrLike), "5,7,13");
assertEq(Array.push(arrLike, 17), 4);
assertEq(Array.join(arrLike), "5,7,13,17");
assertEq(Array.push(arrLike, 19, 21), 6);
assertEq(Array.join(arrLike), "5,7,13,17,19,21");
// Array.pop.
reset();
assertThrowsInstanceOf(() => Array.pop(), TypeError);
assertEq(Array.pop(arr), 13);
assertEq(Array.join(arr), "5,7");
assertEq(Array.pop(arr), 7);
assertEq(Array.join(arr), "5");
assertEq(Array.pop(arrLike), 13);
assertEq(Array.join(arrLike), "5,7");
assertEq(Array.pop(arrLike), 7);
assertEq(Array.join(arrLike), "5");
// Array.shift.
reset();
assertThrowsInstanceOf(() => Array.shift(), TypeError);
assertEq(Array.shift(arr), 5);
assertEq(Array.join(arr), "7,13");
assertEq(Array.shift(arr), 7);
assertEq(Array.join(arr), "13");
assertEq(Array.shift(arrLike), 5);
assertEq(Array.join(arrLike), "7,13");
assertEq(Array.shift(arrLike), 7);
assertEq(Array.join(arrLike), "13");
// Array.unshift.
reset();
assertThrowsInstanceOf(() => Array.unshift(), TypeError);
assertEq(Array.unshift(arr), 3);
assertEq(Array.join(arr), "5,7,13");
assertEq(Array.unshift(arr, 17), 4);
assertEq(Array.join(arr), "17,5,7,13");
assertEq(Array.unshift(arr, 19, 21), 6);
assertEq(Array.join(arr), "19,21,17,5,7,13");
assertEq(Array.unshift(arrLike), 3);
assertEq(Array.join(arrLike), "5,7,13");
assertEq(Array.unshift(arrLike, 17), 4);
assertEq(Array.join(arrLike), "17,5,7,13");
assertEq(Array.unshift(arrLike, 19, 21), 6);
assertEq(Array.join(arrLike), "19,21,17,5,7,13");
// Array.splice.
reset();
assertThrowsInstanceOf(() => Array.splice(), TypeError);
assertEq(Array.join(Array.splice(arr)), "");
assertEq(Array.join(arr), "5,7,13");
assertEq(Array.join(Array.splice(arr, 1)), "7,13");
assertEq(Array.join(arr), "5");
reset();
assertEq(Array.join(Array.splice(arr, 1, 1)), "7");
assertEq(Array.join(arr), "5,13");
reset();
assertEq(Array.join(Array.splice(arrLike)), "");
assertEq(Array.join(arrLike), "5,7,13");
assertEq(Array.join(Array.splice(arrLike, 1)), "7,13");
assertEq(Array.join(arrLike), "5");
reset();
assertEq(Array.join(Array.splice(arrLike, 1, 1)), "7");
assertEq(Array.join(arrLike), "5,13");
// Array.slice.
reset();
assertThrowsInstanceOf(() => Array.slice(), TypeError);
assertEq(Array.join(Array.slice(arr)), "5,7,13");
assertEq(Array.join(Array.slice(arr, 1)), "7,13");
assertEq(Array.join(Array.slice(arr, 1, 1)), "");
assertEq(Array.join(Array.slice(arr, 1, 2)), "7");
assertEq(Array.join(Array.slice(arrLike)), "5,7,13");
assertEq(Array.join(Array.slice(arrLike, 1)), "7,13");
assertEq(Array.join(Array.slice(arrLike, 1, 1)), "");
assertEq(Array.join(Array.slice(arrLike, 1, 2)), "7");
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -1,228 +0,0 @@
var BUGNUMBER = 1263558;
var summary = "Self-host all String generics.";
print(BUGNUMBER + ": " + summary);
var result;
var str = "ABCde";
var strObj = {
toString() {
return "ABCde";
}
};
// String.substring.
assertThrowsInstanceOf(() => String.substring(), TypeError);
assertEq(String.substring(str), "ABCde");
assertEq(String.substring(str, 1), "BCde");
assertEq(String.substring(str, 1, 3), "BC");
assertEq(String.substring(strObj), "ABCde");
assertEq(String.substring(strObj, 1), "BCde");
assertEq(String.substring(strObj, 1, 3), "BC");
// String.substr.
assertThrowsInstanceOf(() => String.substr(), TypeError);
assertEq(String.substr(str), "ABCde");
assertEq(String.substr(str, 1), "BCde");
assertEq(String.substr(str, 1, 3), "BCd");
assertEq(String.substr(strObj), "ABCde");
assertEq(String.substr(strObj, 1), "BCde");
assertEq(String.substr(strObj, 1, 3), "BCd");
// String.slice.
assertThrowsInstanceOf(() => String.slice(), TypeError);
assertEq(String.slice(str), "ABCde");
assertEq(String.slice(str, 1), "BCde");
assertEq(String.slice(str, 1, 3), "BC");
assertEq(String.slice(strObj), "ABCde");
assertEq(String.slice(strObj, 1), "BCde");
assertEq(String.slice(strObj, 1, 3), "BC");
// String.match.
assertThrowsInstanceOf(() => String.match(), TypeError);
result = String.match(str);
assertEq(result.index, 0);
assertEq(result.length, 1);
assertEq(result[0], "");
result = String.match(str, /c/i);
assertEq(result.index, 2);
assertEq(result.length, 1);
assertEq(result[0], "C");
result = String.match(strObj);
assertEq(result.index, 0);
assertEq(result.length, 1);
assertEq(result[0], "");
result = String.match(strObj, /c/i);
assertEq(result.index, 2);
assertEq(result.length, 1);
assertEq(result[0], "C");
// String.replace.
assertThrowsInstanceOf(() => String.replace(), TypeError);
assertEq(String.replace(str), "ABCde");
assertEq(String.replace(str, /c/i), "ABundefinedde");
assertEq(String.replace(str, /c/i, "x"), "ABxde");
assertEq(String.replace(strObj), "ABCde");
assertEq(String.replace(strObj, /c/i), "ABundefinedde");
assertEq(String.replace(strObj, /c/i, "x"), "ABxde");
// String.search.
assertThrowsInstanceOf(() => String.search(), TypeError);
assertEq(String.search(str), 0);
assertEq(String.search(str, /c/i), 2);
assertEq(String.search(strObj), 0);
assertEq(String.search(strObj, /c/i), 2);
// String.split.
assertThrowsInstanceOf(() => String.split(), TypeError);
assertEq(String.split(str).join(","), "ABCde");
assertEq(String.split(str, /[bd]/i).join(","), "A,C,e");
assertEq(String.split(str, /[bd]/i, 2).join(","), "A,C");
assertEq(String.split(strObj).join(","), "ABCde");
assertEq(String.split(strObj, /[bd]/i).join(","), "A,C,e");
assertEq(String.split(strObj, /[bd]/i, 2).join(","), "A,C");
// String.toLowerCase.
assertThrowsInstanceOf(() => String.toLowerCase(), TypeError);
assertEq(String.toLowerCase(str), "abcde");
assertEq(String.toLowerCase(strObj), "abcde");
// String.toUpperCase.
assertThrowsInstanceOf(() => String.toUpperCase(), TypeError);
assertEq(String.toUpperCase(str), "ABCDE");
assertEq(String.toUpperCase(strObj), "ABCDE");
// String.charAt.
assertThrowsInstanceOf(() => String.charAt(), TypeError);
assertEq(String.charAt(str), "A");
assertEq(String.charAt(str, 2), "C");
assertEq(String.charAt(strObj), "A");
assertEq(String.charAt(strObj, 2), "C");
// String.charCodeAt.
assertThrowsInstanceOf(() => String.charCodeAt(), TypeError);
assertEq(String.charCodeAt(str), 65);
assertEq(String.charCodeAt(str, 2), 67);
assertEq(String.charCodeAt(strObj), 65);
assertEq(String.charCodeAt(strObj, 2), 67);
// String.includes.
assertThrowsInstanceOf(() => String.includes(), TypeError);
assertEq(String.includes(str), false);
assertEq(String.includes(str, "C"), true);
assertEq(String.includes(str, "C", 2), true);
assertEq(String.includes(str, "C", 3), false);
assertEq(String.includes(strObj), false);
assertEq(String.includes(strObj, "C"), true);
assertEq(String.includes(strObj, "C", 2), true);
assertEq(String.includes(strObj, "C", 3), false);
// String.indexOf.
assertThrowsInstanceOf(() => String.indexOf(), TypeError);
assertEq(String.indexOf(str), -1);
assertEq(String.indexOf(str, "C"), 2);
assertEq(String.indexOf(str, "C", 2), 2);
assertEq(String.indexOf(str, "C", 3), -1);
assertEq(String.indexOf(strObj), -1);
assertEq(String.indexOf(strObj, "C"), 2);
assertEq(String.indexOf(strObj, "C", 2), 2);
assertEq(String.indexOf(strObj, "C", 3), -1);
// String.lastIndexOf.
assertThrowsInstanceOf(() => String.lastIndexOf(), TypeError);
assertEq(String.lastIndexOf(str), -1);
assertEq(String.lastIndexOf(str, "C"), 2);
assertEq(String.lastIndexOf(str, "C", 2), 2);
assertEq(String.lastIndexOf(str, "C", 1), -1);
assertEq(String.lastIndexOf(strObj), -1);
assertEq(String.lastIndexOf(strObj, "C"), 2);
assertEq(String.lastIndexOf(strObj, "C", 2), 2);
assertEq(String.lastIndexOf(strObj, "C", 1), -1);
// String.startsWith.
assertThrowsInstanceOf(() => String.startsWith(), TypeError);
assertEq(String.startsWith(str), false);
assertEq(String.startsWith(str, "A"), true);
assertEq(String.startsWith(str, "B", 0), false);
assertEq(String.startsWith(str, "B", 1), true);
assertEq(String.startsWith(strObj), false);
assertEq(String.startsWith(strObj, "A"), true);
assertEq(String.startsWith(strObj, "B", 0), false);
assertEq(String.startsWith(strObj, "B", 1), true);
// String.endsWith.
assertThrowsInstanceOf(() => String.endsWith(), TypeError);
assertEq(String.endsWith(str), false);
assertEq(String.endsWith(str, "e"), true);
assertEq(String.endsWith(str, "B", 0), false);
assertEq(String.endsWith(str, "B", 2), true);
assertEq(String.endsWith(strObj), false);
assertEq(String.endsWith(strObj, "e"), true);
assertEq(String.endsWith(strObj, "B", 0), false);
assertEq(String.endsWith(strObj, "B", 2), true);
// String.trim.
var str2 = " ABCde ";
var strObj2 = {
toString() {
return " ABCde ";
}
};
assertThrowsInstanceOf(() => String.trim(), TypeError);
assertEq(String.trim(str2), "ABCde");
assertEq(String.trim(strObj2), "ABCde");
// String.trimLeft.
assertThrowsInstanceOf(() => String.trimLeft(), TypeError);
assertEq(String.trimLeft(str2), "ABCde ");
assertEq(String.trimLeft(strObj2), "ABCde ");
// String.trimRight.
assertThrowsInstanceOf(() => String.trimRight(), TypeError);
assertEq(String.trimRight(str2), " ABCde");
assertEq(String.trimRight(strObj2), " ABCde");
// String.toLocaleLowerCase.
assertThrowsInstanceOf(() => String.toLocaleLowerCase(), TypeError);
assertEq(String.toLocaleLowerCase(str), str.toLocaleLowerCase());
assertEq(String.toLocaleLowerCase(strObj), str.toLocaleLowerCase());
// String.toLocaleUpperCase.
assertThrowsInstanceOf(() => String.toLocaleUpperCase(), TypeError);
assertEq(String.toLocaleUpperCase(str), str.toLocaleUpperCase());
assertEq(String.toLocaleUpperCase(strObj), str.toLocaleUpperCase());
// String.localeCompare.
assertThrowsInstanceOf(() => String.localeCompare(), TypeError);
assertEq(String.localeCompare(str), str.localeCompare());
assertEq(String.localeCompare(str, "abcde"), str.localeCompare("abcde"));
assertEq(String.localeCompare(strObj), str.localeCompare());
assertEq(String.localeCompare(strObj, "abcde"), str.localeCompare("abcde"));
// String.normalize.
if ("normalize" in String.prototype) {
var str3 = "\u3082\u3058\u3089 \u3082\u3057\u3099\u3089";
var strObj3 = {
toString() {
return "\u3082\u3058\u3089 \u3082\u3057\u3099\u3089";
}
};
assertThrowsInstanceOf(() => String.normalize(), TypeError);
assertEq(String.normalize(str3), "\u3082\u3058\u3089 \u3082\u3058\u3089");
assertEq(String.normalize(str3, "NFD"), "\u3082\u3057\u3099\u3089 \u3082\u3057\u3099\u3089");
assertEq(String.normalize(strObj3), "\u3082\u3058\u3089 \u3082\u3058\u3089");
assertEq(String.normalize(strObj3, "NFD"), "\u3082\u3057\u3099\u3089 \u3082\u3057\u3099\u3089");
}
// String.concat.
assertThrowsInstanceOf(() => String.concat(), TypeError);
assertEq(String.concat(str), "ABCde");
assertEq(String.concat(str, "f"), "ABCdef");
assertEq(String.concat(str, "f", "g"), "ABCdefg");
assertEq(String.concat(strObj), "ABCde");
assertEq(String.concat(strObj, "f"), "ABCdef");
assertEq(String.concat(strObj, "f", "g"), "ABCdefg");
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -2248,8 +2248,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("std_Array_unshift", array_unshift, 1,0),
JS_INLINABLE_FN("std_Array_slice", array_slice, 2,0, ArraySlice),
JS_FN("std_Array_sort", array_sort, 1,0),
JS_FN("std_Array_reverse", array_reverse, 0,0),
JS_INLINABLE_FN("std_Array_splice", array_splice, 2,0, ArraySplice),
JS_FN("std_Date_now", date_now, 0,0),
JS_FN("std_Date_valueOf", date_valueOf, 0,0),
@ -2292,21 +2290,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("std_String_toLowerCase", str_toLowerCase, 0,0),
JS_FN("std_String_toUpperCase", str_toUpperCase, 0,0),
JS_INLINABLE_FN("std_String_charAt", str_charAt, 1,0, StringCharAt),
JS_FN("std_String_endsWith", str_endsWith, 1,0),
JS_FN("std_String_trim", str_trim, 0,0),
JS_FN("std_String_trimLeft", str_trimLeft, 0,0),
JS_FN("std_String_trimRight", str_trimRight, 0,0),
JS_FN("std_String_toLocaleLowerCase", str_toLocaleLowerCase, 0,0),
JS_FN("std_String_toLocaleUpperCase", str_toLocaleUpperCase, 0,0),
#if !EXPOSE_INTL_API
JS_FN("std_String_localeCompare", str_localeCompare, 1,0),
#else
JS_FN("std_String_normalize", str_normalize, 0,0),
#endif
JS_FN("std_String_concat", str_concat, 1,0),
JS_FN("std_WeakMap_has", WeakMap_has, 1,0),
JS_FN("std_WeakMap_get", WeakMap_get, 2,0),
JS_FN("std_WeakMap_set", WeakMap_set, 2,0),

View File

@ -539,7 +539,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681
// low value.
let propsToSkip = ['length', Symbol.unscopables];
testXray('Array', new iwin.Array(20), new iwin.Array(), propsToSkip);
// On the constructor, we want to skip all the non-standard "generic"
// functions. We're trying to remove them anyway; no point doing extra work
// to expose them over Xrays.
let ctorPropsToSkip = ["join", "reverse", "sort", "push", "pop", "shift",
"unshift", "splice", "slice"];
testXray('Array', new iwin.Array(20), new iwin.Array(), propsToSkip,
ctorPropsToSkip);
let symbolProps = '';
uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol');

View File

@ -53,7 +53,7 @@
android:label="@string/moz_app_displayname"
android:taskAffinity="@ANDROID_PACKAGE_NAME@.BROWSER"
android:alwaysRetainTaskState="true"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
android:windowSoftInputMode="stateUnspecified|adjustResize"
android:launchMode="singleTask"
android:exported="true"

View File

@ -7,16 +7,16 @@ package org.mozilla.gecko;
import android.Manifest;
import android.app.DownloadManager;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import org.json.JSONArray;
import org.mozilla.gecko.adjust.AdjustHelperInterface;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
import org.mozilla.gecko.Tabs.TabEvents;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
@ -25,10 +25,8 @@ import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.SuggestedSites;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.distribution.Distribution.DistributionDescriptor;
import org.mozilla.gecko.distribution.DistributionStoreCallback;
import org.mozilla.gecko.dlc.DownloadContentService;
import org.mozilla.gecko.dlc.catalog.DownloadContent;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
@ -86,6 +84,7 @@ import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.DrawableUtil;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.Experiments;
import org.mozilla.gecko.util.FloatUtils;
@ -130,6 +129,7 @@ import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MenuItemCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
@ -363,7 +363,15 @@ public class BrowserApp extends GeckoApp
tab.loadFavicon();
break;
case BOOKMARK_ADDED:
showBookmarkAddedSnackbar();
// We always show the special offline snackbar whenever we bookmark a reader page.
// It's possible that the page is already stored offline, however this is highly
// unlikely, and even so it is probably nicer to show the same offline notification
// every time we bookmark an about:reader page.
if (!AboutPages.isAboutReader(tab.getURL())) {
showBookmarkAddedSnackbar();
} else {
showReaderModeBookmarkAddedSnackbar();
}
break;
case BOOKMARK_REMOVED:
showBookmarkRemovedSnackbar();
@ -442,6 +450,26 @@ public class BrowserApp extends GeckoApp
callback);
}
private void showReaderModeBookmarkAddedSnackbar() {
final Drawable iconDownloaded = DrawableUtil.tintDrawable(getContext(), R.drawable.status_icon_readercache, Color.WHITE);
final SnackbarHelper.SnackbarCallback callback = new SnackbarHelper.SnackbarCallback() {
@Override
public void onClick(View v) {
openUrlAndStopEditing("about:home?panel=" + HomeConfig.getIdForBuiltinPanelType(PanelType.BOOKMARKS));
}
};
SnackbarHelper.showSnackbarWithActionAndColors(this,
getResources().getString(R.string.reader_saved_offline),
Snackbar.LENGTH_LONG,
getResources().getString(R.string.reader_switch_to_bookmarks),
callback,
iconDownloaded,
ContextCompat.getColor(this, R.color.link_blue),
Color.WHITE);
}
private void showBookmarkRemovedSnackbar() {
SnackbarHelper.showSnackbar(this, getResources().getString(R.string.bookmark_removed), Snackbar.LENGTH_LONG);
}
@ -3191,6 +3219,8 @@ public class BrowserApp extends GeckoApp
new HashSet<String>()).isEmpty();
aMenu.findItem(R.id.quit).setVisible(visible);
// If tab data is unavailable we disable most of the context menu and related items and
// return early.
if (tab == null || tab.getURL() == null) {
bookmark.setEnabled(false);
back.setEnabled(false);
@ -3214,8 +3244,14 @@ public class BrowserApp extends GeckoApp
return true;
}
// If tab data IS available we need to manually enable items as necessary. They may have
// been disabled if returning early above, hence every item must be toggled, even if it's
// always expected to be enabled (e.g. the bookmark star is always enabled, except when
// we don't have tab data).
final boolean inGuestMode = GeckoProfile.get(this).inGuestMode();
bookmark.setEnabled(true); // Might have been disabled above, ensure it's reenabled
bookmark.setVisible(!inGuestMode);
bookmark.setCheckable(true);
bookmark.setChecked(tab.isBookmark());

View File

@ -14,6 +14,7 @@ import android.content.Context;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AppCompatActivity;
/**
* This is a helper class to do typical locale switching operations without
@ -47,6 +48,14 @@ public class Locales {
}
}
public static class LocaleAwareAppCompatActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Locales.initializeLocale(getApplicationContext());
super.onCreate(savedInstanceState);
}
}
public static class LocaleAwareFragmentActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {

View File

@ -9,10 +9,14 @@ import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeJSObject;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
import java.lang.ref.WeakReference;
@ -98,15 +102,46 @@ public class SnackbarHelper {
* @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed.
*/
public static void showSnackbarWithAction(Activity activity, String message, int duration, String action, SnackbarCallback callback) {
showSnackbarWithActionAndColors(activity, message, duration, action, callback, null, null, null);
}
public static void showSnackbarWithActionAndColors(Activity activity,
String message,
int duration,
String action,
SnackbarCallback callback,
Drawable icon,
Integer backgroundColor,
Integer actionColor) {
final View parentView = findBestParentView(activity);
final Snackbar snackbar = Snackbar.make(parentView, message, duration);
if (callback != null && !TextUtils.isEmpty(action)) {
snackbar.setAction(action, callback);
snackbar.setActionTextColor(ContextCompat.getColor(activity, R.color.fennec_ui_orange));
if (actionColor == null) {
ContextCompat.getColor(activity, R.color.fennec_ui_orange);
} else {
snackbar.setActionTextColor(actionColor);
}
snackbar.setCallback(callback);
}
if (icon != null) {
int leftPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, activity.getResources().getDisplayMetrics());
final InsetDrawable paddedIcon = new InsetDrawable(icon, 0, 0, leftPadding, 0);
paddedIcon.setBounds(0, 0, leftPadding + icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
TextView textView = (TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text);
textView.setCompoundDrawables(paddedIcon, null, null, null);
}
if (backgroundColor != null) {
snackbar.getView().setBackgroundColor(backgroundColor);
}
snackbar.show();
synchronized (currentSnackbarLock) {

View File

@ -38,7 +38,7 @@ public abstract class SharedBrowserDatabaseProvider extends AbstractPerProfileDa
return databases;
}
// Can't mark as @Override. Added in API 11.
@Override
public void shutdown() {
synchronized (SharedBrowserDatabaseProvider.class) {
databases.shutdown();

View File

@ -35,7 +35,7 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
private static final String LOGTAG = "GeckoHomeConfigBackend";
// Increment this to trigger a migration.
private static final int VERSION = 4;
private static final int VERSION = 5;
// This key was originally used to store only an array of panel configs.
public static final String PREFS_CONFIG_KEY_OLD = "home_panels";
@ -186,6 +186,7 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
PanelConfig newPanel;
int replaceIndex;
int removeIndex;
if (historyFlags.contains(PanelConfig.Flags.DISABLED_PANEL) && !syncFlags.contains(PanelConfig.Flags.DISABLED_PANEL)) {
// Replace the Sync panel if it's visible and the History panel is disabled.
replaceIndex = syncIndex;
@ -213,6 +214,25 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
return newArray;
}
private static void ensureDefaultPanelForV5(Context context, JSONArray jsonPanels) throws JSONException {
int historyIndex = -1;
for (int i = 0; i < jsonPanels.length(); i++) {
final PanelConfig panelConfig = new PanelConfig(jsonPanels.getJSONObject(i));
if (panelConfig.isDefault()) {
return;
}
if (panelConfig.getType() == PanelType.COMBINED_HISTORY) {
historyIndex = i;
}
}
// Make the History panel default. We can't modify existing PanelConfigs, so make a new one.
final PanelConfig historyPanelConfig = createBuiltinPanelConfig(context, PanelType.COMBINED_HISTORY, EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL));
jsonPanels.put(historyIndex, historyPanelConfig.toJSON());
}
/**
* Checks to see if the reading list panel already exists.
*
@ -316,6 +336,11 @@ public class HomeConfigPrefsBackend implements HomeConfigBackend {
// the Sync panel if that's visible.
jsonPanels = combineHistoryAndSyncPanels(context, jsonPanels);
break;
case 5:
// This is the fix for bug 1264136 where we lost track of the default panel during some migrations.
ensureDefaultPanelForV5(context, jsonPanels);
break;
}
}

View File

@ -145,7 +145,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
mUserEnteredView.setOnClickListener(mClickListener);
mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text);
mSearchHistorySuggestionIcon = DrawableUtil.tintDrawable(getContext(), R.drawable.icon_most_recent_empty, R.color.tabs_tray_icon_grey);
mSearchHistorySuggestionIcon = DrawableUtil.tintDrawableWithColorRes(getContext(), R.drawable.icon_most_recent_empty, R.color.tabs_tray_icon_grey);
// Suggestion limits
mMaxSavedSuggestions = getResources().getInteger(R.integer.max_saved_suggestions);

View File

@ -156,6 +156,7 @@ OnSharedPreferenceChangeListener
private static final String PREFS_FAQ_LINK = NON_PREF_PREFIX + "faq.link";
private static final String PREFS_FEEDBACK_LINK = NON_PREF_PREFIX + "feedback.link";
public static final String PREFS_NOTIFICATIONS_CONTENT = NON_PREF_PREFIX + "notifications.content";
public static final String PREFS_NOTIFICATIONS_CONTENT_LEARN_MORE = NON_PREF_PREFIX + "notifications.content.learn_more";
public static final String PREFS_NOTIFICATIONS_WHATS_NEW = NON_PREF_PREFIX + "notifications.whats_new";
private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
@ -881,7 +882,8 @@ OnSharedPreferenceChangeListener
i--;
continue;
}
} else if (PREFS_NOTIFICATIONS_CONTENT.equals(key)) {
} else if (PREFS_NOTIFICATIONS_CONTENT.equals(key) ||
PREFS_NOTIFICATIONS_CONTENT_LEARN_MORE.equals(key)) {
if (!FeedService.isInExperiment(this)) {
preferences.removePreference(pref);
i--;

View File

@ -150,10 +150,10 @@ public class ToolbarEditLayout extends ThemedLinearLayout {
final int searchDrawableId = R.drawable.search_icon_active;
final Drawable searchDrawable;
if (!isActive) {
searchDrawable = DrawableUtil.tintDrawable(getContext(), searchDrawableId, R.color.placeholder_grey);
searchDrawable = DrawableUtil.tintDrawableWithColorRes(getContext(), searchDrawableId, R.color.placeholder_grey);
} else {
if (isPrivateMode()) {
searchDrawable = DrawableUtil.tintDrawable(getContext(), searchDrawableId, R.color.tabs_tray_icon_grey);
searchDrawable = DrawableUtil.tintDrawableWithColorRes(getContext(), searchDrawableId, R.color.tabs_tray_icon_grey);
} else {
searchDrawable = getResources().getDrawable(searchDrawableId);
}

View File

@ -7,8 +7,10 @@ package org.mozilla.gecko.util;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.support.annotation.CheckResult;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
@ -21,14 +23,25 @@ public class DrawableUtil {
* Tints the given drawable with the given color and returns it.
*/
@CheckResult
public static Drawable tintDrawable(@NonNull final Context context, @DrawableRes final int drawableID,
@ColorRes final int colorID) {
public static Drawable tintDrawable(@NonNull final Context context,
@DrawableRes final int drawableID,
@ColorInt final int color) {
final Drawable icon = DrawableCompat.wrap(
ContextCompat.getDrawable(context, drawableID).mutate());
DrawableCompat.setTint(icon, ContextCompat.getColor(context, colorID));
DrawableCompat.setTint(icon, color);
return icon;
}
/**
* Tints the given drawable with the given color and returns it.
*/
@CheckResult
public static Drawable tintDrawableWithColorRes(@NonNull final Context context,
@DrawableRes final int drawableID,
@ColorRes final int colorID) {
return tintDrawable(context, drawableID, ContextCompat.getColor(context, colorID));
}
/**
* Tints the given drawable with the given tint list and returns it. Note that you
* should no longer use the argument Drawable because the argument is not mutated

View File

@ -70,6 +70,12 @@
<!ENTITY screenshot_folder_label_in_bookmarks "Screenshots">
<!ENTITY readinglist_smartfolder_label_in_bookmarks "Reading List">
<!ENTITY reader_saved_offline "Saved offline">
<!-- Localization note (reader_switch_to_bookmarks) : This
string is used as an action in a snackbar - it lets you
"switch" to the bookmarks (saved items) panel. -->
<!ENTITY reader_switch_to_bookmarks "Switch">
<!ENTITY history_today_section "Today">
<!ENTITY history_yesterday_section "Yesterday">
<!ENTITY history_week_section3 "Last 7 days">

View File

@ -148,6 +148,7 @@ services_jar.extra_jars = [
CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
CONFIG['ANDROID_APPCOMPAT_V7_AAR_LIB'],
'constants.jar',
'gecko-R.jar',
'gecko-mozglue.jar',

View File

@ -4,6 +4,11 @@
android:title="@string/pref_content_notifications"
android:summary="@string/pref_content_notifications_summary"
android:defaultValue="true" />
<org.mozilla.gecko.preferences.AlignRightLinkPreference
android:key="android.not_a_preference.notifications.content.learn_more"
android:title="@string/pref_learn_more"
android:persistent="false"
url="https://support.mozilla.org/kb/notifications-firefox-android?utm_source=inproduct&amp;utm_medium=notifications&amp;utm_campaign=mobileandroid" />
<SwitchPreference android:key="android.not_a_preference.notifications.whats_new"
android:title="@string/pref_whats_new_notification"
android:summary="@string/pref_whats_new_notification_summary"

View File

@ -92,6 +92,9 @@
<string name="screenshot_folder_label_in_bookmarks">&screenshot_folder_label_in_bookmarks;</string>
<string name="readinglist_smartfolder_label_in_bookmarks">&readinglist_smartfolder_label_in_bookmarks;</string>
<string name="reader_saved_offline">&reader_saved_offline;</string>
<string name="reader_switch_to_bookmarks">&reader_switch_to_bookmarks;</string>
<string name="history_today_section">&history_today_section;</string>
<string name="history_yesterday_section">&history_yesterday_section;</string>
<string name="history_week_section">&history_week_section3;</string>

View File

@ -509,7 +509,7 @@ var ActionBarHandler = {
SEARCH_ADD: {
id: "search_add_action",
label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine2"),
label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine3"),
icon: "drawable://ab_add_search_engine",
order: 0,
floatingOrder: 8,

View File

@ -794,7 +794,7 @@ var SelectionHandler = {
SEARCH_ADD: {
id: "search_add_action",
label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine2"),
label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine3"),
icon: "drawable://ab_add_search_engine",
selector: {

View File

@ -4086,9 +4086,15 @@ Tab.prototype = {
break;
}
case "DOMAudioPlaybackStarted":
case "DOMAudioPlaybackStopped":
case "TabPreZombify": {
if (!this.playingAudio) {
return;
}
// Fall through to the DOMAudioPlayback events, so the
// audio playback indicator gets reset upon zombification.
}
case "DOMAudioPlaybackStarted":
case "DOMAudioPlaybackStopped": {
if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
!aEvent.isTrusted) {
return;
@ -6672,7 +6678,7 @@ var SearchEngines = {
}
// prompt user for name of search engine
let promptTitle = Strings.browser.GetStringFromName("contextmenu.addSearchEngine2");
let promptTitle = Strings.browser.GetStringFromName("contextmenu.addSearchEngine3");
let title = { value: (aElement.ownerDocument.title || docURI.host) };
if (!Services.prompt.prompt(null, promptTitle, null, title, null, {}))
return;

View File

@ -262,7 +262,13 @@ contextmenu.search=%S Search
contextmenu.saveImage=Save Image
contextmenu.showImage=Show Image
contextmenu.setImageAs=Set Image As
contextmenu.addSearchEngine2=Add as Search Engine
# LOCALIZATION NOTE (contextmenu.addSearchEngine3): This string should be rather short. If it is
# significantly longer than the translation for the "Paste" action then this might trigger an
# Android bug positioning the floating text selection partially off the screen. This issue heavily
# depends on the screen size and the specific translations. For English "Paste" / "Add search engine"
# is working while "Paste" / "Add as search engine" triggers the bug. See bug 1262098 for more details.
# Manual testing the scenario described in bug 1262098 is highly recommended.
contextmenu.addSearchEngine3=Add Search Engine
contextmenu.playMedia=Play
contextmenu.pauseMedia=Pause
contextmenu.shareMedia=Share Video

View File

@ -10,7 +10,6 @@ import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -18,13 +17,14 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Locales.LocaleAwareFragmentActivity;
import org.mozilla.gecko.Locales.LocaleAwareAppCompatActivity;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
@ -36,7 +36,7 @@ import org.mozilla.gecko.sync.Utils;
/**
* Activity which displays account status.
*/
public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
public class FxAccountStatusActivity extends LocaleAwareAppCompatActivity {
private static final String LOG_TAG = FxAccountStatusActivity.class.getSimpleName();
protected FxAccountStatusFragment statusFragment;
@ -67,10 +67,11 @@ public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
Logger.debug(LOG_TAG, "Not enabling home button; version too low.");
return;
}
final ActionBar actionBar = getActionBar();
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
Logger.debug(LOG_TAG, "Enabling home button.");
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
return;
}
Logger.debug(LOG_TAG, "Not enabling home button.");

View File

@ -77,12 +77,14 @@
android:layout_alignParentRight="true"
android:layout_below="@id/host"
android:layout_marginBottom="20dp"
android:layout_marginLeft="20dp"
android:layout_marginLeft="100dp"
android:layout_marginRight="30dp"
android:background="@drawable/button_background_action_orange_round"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="@string/promotion_add_to_homescreen"
android:maxLines="2"
android:ellipsize="end"
android:textColor="@android:color/white"
android:textSize="16sp" />

View File

@ -12,11 +12,9 @@
<item name="android:displayOptions">showHome|homeAsUp|showTitle</item>
</style>
<style name="FxAccountTheme" parent="@style/Gecko" />
<style name="FxAccountTheme" parent="Gecko.Preferences" />
<style name="FxAccountTheme.FxAccountStatusActivity" parent="Gecko.Preferences">
<item name="android:windowActionBar">true</item>
<item name="android:windowNoTitle">false</item>
<item name="android:actionBarStyle">@style/ActionBar.FxAccountStatusActivity</item>
</style>

View File

@ -7,7 +7,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="FxAccountTheme" parent="@style/Gecko" />
<style name="FxAccountTheme" parent="Gecko.Preferences" />
<style name="FxAccountTheme.FxAccountStatusActivity" parent="@style/FxAccountTheme">
<item name="android:windowNoTitle">false</item>

View File

@ -18,7 +18,6 @@ background_junit3_sources = [
'src/org/mozilla/gecko/background/db/TestClientsDatabase.java',
'src/org/mozilla/gecko/background/db/TestClientsDatabaseAccessor.java',
'src/org/mozilla/gecko/background/db/TestFennecTabsRepositorySession.java',
'src/org/mozilla/gecko/background/db/TestFennecTabsStorage.java',
'src/org/mozilla/gecko/background/db/TestFormHistoryRepositorySession.java',
'src/org/mozilla/gecko/background/db/TestPasswordsRepository.java',
'src/org/mozilla/gecko/background/db/TestTopSites.java',
@ -66,7 +65,6 @@ background_junit3_sources = [
'src/org/mozilla/gecko/background/sync/TestResetting.java',
'src/org/mozilla/gecko/background/sync/TestStoreTracking.java',
'src/org/mozilla/gecko/background/sync/TestSyncConfiguration.java',
'src/org/mozilla/gecko/background/sync/TestTabsRecord.java',
'src/org/mozilla/gecko/background/sync/TestWebURLFinder.java',
'src/org/mozilla/gecko/background/testhelpers/BaseMockServerSyncStage.java',
'src/org/mozilla/gecko/background/testhelpers/CommandHelpers.java',

View File

@ -13,7 +13,6 @@ subsuite = background
[src/org/mozilla/gecko/background/db/TestClientsDatabase.java]
[src/org/mozilla/gecko/background/db/TestClientsDatabaseAccessor.java]
[src/org/mozilla/gecko/background/db/TestFennecTabsRepositorySession.java]
[src/org/mozilla/gecko/background/db/TestFennecTabsStorage.java]
[src/org/mozilla/gecko/background/db/TestFormHistoryRepositorySession.java]
[src/org/mozilla/gecko/background/db/TestPasswordsRepository.java]
[src/org/mozilla/gecko/background/fxa/TestBrowserIDKeyPairGeneration.java]
@ -23,5 +22,4 @@ subsuite = background
[src/org/mozilla/gecko/background/sync/TestResetting.java]
[src/org/mozilla/gecko/background/sync/TestStoreTracking.java]
[src/org/mozilla/gecko/background/sync/TestSyncConfiguration.java]
[src/org/mozilla/gecko/background/sync/TestTabsRecord.java]
[src/org/mozilla/gecko/background/sync/TestWebURLFinder.java]

View File

@ -1,237 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.db;
import org.json.simple.JSONArray;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import android.app.Activity;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.test.ActivityInstrumentationTestCase2;
/**
* Exercise Fennec's tabs provider.
*
* @author rnewman
*
*/
public class TestFennecTabsStorage extends ActivityInstrumentationTestCase2<Activity> {
public static final String TEST_CLIENT_GUID = "test guid"; // Real GUIDs never contain spaces.
public static final String TEST_CLIENT_NAME = "test client name";
public static final String CLIENTS_GUID_IS = BrowserContract.Clients.GUID + " = ?";
public static final String TABS_CLIENT_GUID_IS = BrowserContract.Tabs.CLIENT_GUID + " = ?";
protected Tab testTab1;
protected Tab testTab2;
protected Tab testTab3;
public TestFennecTabsStorage() {
super(Activity.class);
}
protected ContentProviderClient getClientsClient() {
final ContentResolver cr = getInstrumentation().getTargetContext().getApplicationContext().getContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
}
protected ContentProviderClient getTabsClient() {
final ContentResolver cr = getInstrumentation().getTargetContext().getApplicationContext().getContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.TABS_CONTENT_URI);
}
protected int deleteTestClient(final ContentProviderClient clientsClient) throws RemoteException {
if (clientsClient == null) {
return -1;
}
return clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, CLIENTS_GUID_IS, new String[] { TEST_CLIENT_GUID });
}
protected int deleteAllTestTabs(final ContentProviderClient tabsClient) throws RemoteException {
if (tabsClient == null) {
return -1;
}
return tabsClient.delete(BrowserContractHelpers.TABS_CONTENT_URI, TABS_CLIENT_GUID_IS, new String[]{TEST_CLIENT_GUID});
}
@Override
protected void tearDown() throws Exception {
deleteAllTestTabs(getTabsClient());
}
protected void insertTestClient(final ContentProviderClient clientsClient) throws RemoteException {
ContentValues cv = new ContentValues();
cv.put(BrowserContract.Clients.GUID, TEST_CLIENT_GUID);
cv.put(BrowserContract.Clients.NAME, TEST_CLIENT_NAME);
clientsClient.insert(BrowserContractHelpers.CLIENTS_CONTENT_URI, cv);
}
@SuppressWarnings("unchecked")
protected void insertSomeTestTabs(ContentProviderClient tabsClient) throws RemoteException {
final JSONArray history1 = new JSONArray();
history1.add("http://test.com/test1.html");
testTab1 = new Tab("test title 1", "http://test.com/test1.png", history1, 1000);
final JSONArray history2 = new JSONArray();
history2.add("http://test.com/test2.html#1");
history2.add("http://test.com/test2.html#2");
history2.add("http://test.com/test2.html#3");
testTab2 = new Tab("test title 2", "http://test.com/test2.png", history2, 2000);
final JSONArray history3 = new JSONArray();
history3.add("http://test.com/test3.html#1");
history3.add("http://test.com/test3.html#2");
testTab3 = new Tab("test title 3", "http://test.com/test3.png", history3, 3000);
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab1.toContentValues(TEST_CLIENT_GUID, 0));
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab2.toContentValues(TEST_CLIENT_GUID, 1));
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab3.toContentValues(TEST_CLIENT_GUID, 2));
}
// Sanity.
public void testObtainCP() {
final ContentProviderClient clientsClient = getClientsClient();
assertNotNull(clientsClient);
clientsClient.release();
final ContentProviderClient tabsClient = getTabsClient();
assertNotNull(tabsClient);
tabsClient.release();
}
public void testWipeClients() throws RemoteException {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient clientsClient = getClientsClient();
// Have to ensure that it's empty
clientsClient.delete(uri, null, null);
int deleted = clientsClient.delete(uri, null, null);
assertEquals(0, deleted);
}
public void testWipeTabs() throws RemoteException {
final ContentProviderClient tabsClient = getTabsClient();
// Have to ensure that it's empty
deleteAllTestTabs(tabsClient);
int deleted = deleteAllTestTabs(tabsClient);
assertEquals(0, deleted);
}
public void testStoreAndRetrieveClients() throws RemoteException {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient clientsClient = getClientsClient();
// Have to ensure that it's empty
clientsClient.delete(uri, null, null);
final long now = System.currentTimeMillis();
final ContentValues first = new ContentValues();
final ContentValues second = new ContentValues();
first.put(BrowserContract.Clients.GUID, "abcdefghijkl");
first.put(BrowserContract.Clients.NAME, "Frist Psot");
first.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
second.put(BrowserContract.Clients.GUID, "mnopqrstuvwx");
second.put(BrowserContract.Clients.NAME, "Second!!1!");
second.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
ContentValues[] values = new ContentValues[] { first, second };
final int inserted = clientsClient.bulkInsert(uri, values);
assertEquals(2, inserted);
final String since = BrowserContract.Clients.LAST_MODIFIED + " >= ?";
final String[] nowArg = new String[] { String.valueOf(now) };
final String guidAscending = BrowserContract.Clients.GUID + " ASC";
Cursor cursor = clientsClient.query(uri, null, since, nowArg, guidAscending);
assertNotNull(cursor);
try {
assertTrue(cursor.moveToFirst());
assertEquals(2, cursor.getCount());
final String g1 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.GUID));
final String n1 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.NAME));
final long m1 = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Clients.LAST_MODIFIED));
assertEquals(first.get(BrowserContract.Clients.GUID), g1);
assertEquals(first.get(BrowserContract.Clients.NAME), n1);
assertEquals(now + 1, m1);
assertTrue(cursor.moveToNext());
final String g2 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.GUID));
final String n2 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.NAME));
final long m2 = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Clients.LAST_MODIFIED));
assertEquals(second.get(BrowserContract.Clients.GUID), g2);
assertEquals(second.get(BrowserContract.Clients.NAME), n2);
assertEquals(now + 2, m2);
assertFalse(cursor.moveToNext());
} finally {
cursor.close();
}
int deleted = clientsClient.delete(uri, null, null);
assertEquals(2, deleted);
}
public void testTabFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final ContentProviderClient clientsClient = getClientsClient();
deleteAllTestTabs(tabsClient);
deleteTestClient(clientsClient);
insertTestClient(clientsClient);
insertSomeTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
assertEquals(3, cursor.getCount());
cursor.moveToFirst();
final Tab parsed1 = Tab.fromCursor(cursor);
assertEquals(testTab1, parsed1);
cursor.moveToNext();
final Tab parsed2 = Tab.fromCursor(cursor);
assertEquals(testTab2, parsed2);
cursor.moveToPosition(2);
final Tab parsed3 = Tab.fromCursor(cursor);
assertEquals(testTab3, parsed3);
} finally {
cursor.close();
}
}
public void testDeletingClientDeletesTabs() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final ContentProviderClient clientsClient = getClientsClient();
deleteAllTestTabs(tabsClient);
deleteTestClient(clientsClient);
insertTestClient(clientsClient);
insertSomeTestTabs(tabsClient);
// Delete just the client...
clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, CLIENTS_GUID_IS, new String [] { TEST_CLIENT_GUID });
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, null);
// ... and all that client's tabs should be removed.
assertEquals(0, cursor.getCount());
} finally {
cursor.close();
}
}
}

View File

@ -1,100 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.sync;
import org.mozilla.gecko.background.db.CursorDumper;
import org.mozilla.gecko.background.db.TestFennecTabsStorage;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
import android.content.ContentProviderClient;
import android.database.Cursor;
public class TestTabsRecord extends TestFennecTabsStorage {
public void testTabsRecordFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
deleteAllTestTabs(tabsClient);
insertTestClient(getClientsClient());
insertSomeTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
assertEquals(3, cursor.getCount());
cursor.moveToPosition(1);
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
// Make sure we clean up after ourselves.
assertEquals(1, cursor.getPosition());
assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
assertEquals(3, tabsRecord.tabs.size());
assertEquals(testTab1, tabsRecord.tabs.get(0));
assertEquals(testTab2, tabsRecord.tabs.get(1));
assertEquals(testTab3, tabsRecord.tabs.get(2));
assertEquals(Math.max(Math.max(testTab1.lastUsed, testTab2.lastUsed), testTab3.lastUsed), tabsRecord.lastModified);
} finally {
cursor.close();
}
}
// Verify that we can fetch a record when there are no local tabs at all.
public void testEmptyTabsRecordFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
deleteAllTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
assertEquals(0, cursor.getCount());
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
assertNotNull(tabsRecord.tabs);
assertEquals(0, tabsRecord.tabs.size());
assertEquals(0, tabsRecord.lastModified);
} finally {
cursor.close();
}
}
// Not much of a test, but verifies the tabs record at least agrees with the
// disk data and doubles as a database inspector.
public void testLocalTabs() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
// Keep this in sync with the Fennec schema.
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, BrowserContract.Tabs.CLIENT_GUID + " IS NULL", null, positionAscending);
CursorDumper.dumpCursor(cursor);
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
assertNotNull(tabsRecord.tabs);
assertEquals(cursor.getCount(), tabsRecord.tabs.size());
} finally {
cursor.close();
}
}
}

View File

@ -1,2 +1,3 @@
sdk=21
constants=org.mozilla.gecko.BuildConfig
packageName=org.mozilla.gecko

View File

@ -0,0 +1,86 @@
/* 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/. */
package org.mozilla.gecko.background.db;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import org.mozilla.gecko.db.BrowserContract;
import java.util.ArrayList;
/**
* Wrap a ContentProvider, appending &test=1 to all queries.
*/
public class DelegatingTestContentProvider extends ContentProvider {
protected final ContentProvider mTargetProvider;
protected static Uri appendUriParam(Uri uri, String param, String value) {
return uri.buildUpon().appendQueryParameter(param, value).build();
}
public DelegatingTestContentProvider(ContentProvider targetProvider) {
super();
mTargetProvider = targetProvider;
}
private Uri appendTestParam(Uri uri) {
return appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
}
@Override
public boolean onCreate() {
return mTargetProvider.onCreate();
}
@Override
public String getType(Uri uri) {
return mTargetProvider.getType(uri);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return mTargetProvider.delete(appendTestParam(uri), selection, selectionArgs);
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return mTargetProvider.insert(appendTestParam(uri), values);
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return mTargetProvider.update(appendTestParam(uri), values,
selection, selectionArgs);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return mTargetProvider.query(appendTestParam(uri), projection, selection,
selectionArgs, sortOrder);
}
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
return mTargetProvider.applyBatch(operations);
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
return mTargetProvider.bulkInsert(appendTestParam(uri), values);
}
public ContentProvider getTargetProvider() {
return mTargetProvider;
}
}

View File

@ -0,0 +1,338 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.db;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import org.json.simple.JSONArray;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.TabsProvider;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
import org.robolectric.shadows.ShadowContentResolver;
@RunWith(TestRunner.class)
public class TestTabsProvider {
public static final String TEST_CLIENT_GUID = "test guid"; // Real GUIDs never contain spaces.
public static final String TEST_CLIENT_NAME = "test client name";
public static final String CLIENTS_GUID_IS = BrowserContract.Clients.GUID + " = ?";
public static final String TABS_CLIENT_GUID_IS = BrowserContract.Tabs.CLIENT_GUID + " = ?";
protected Tab testTab1;
protected Tab testTab2;
protected Tab testTab3;
protected TabsProvider provider;
@Before
public void setUp() {
provider = new TabsProvider();
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.TABS_AUTHORITY, new DelegatingTestContentProvider(provider));
}
@After
public void tearDown() throws Exception {
provider.shutdown();
provider = null;
}
protected ContentProviderClient getClientsClient() {
final ShadowContentResolver cr = new ShadowContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
}
protected ContentProviderClient getTabsClient() {
final ShadowContentResolver cr = new ShadowContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.TABS_CONTENT_URI);
}
protected int deleteTestClient(final ContentProviderClient clientsClient) throws RemoteException {
if (clientsClient == null) {
throw new IllegalStateException("Provided ContentProviderClient is null");
}
return clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, CLIENTS_GUID_IS, new String[] { TEST_CLIENT_GUID });
}
protected int deleteAllTestTabs(final ContentProviderClient tabsClient) throws RemoteException {
if (tabsClient == null) {
throw new IllegalStateException("Provided ContentProviderClient is null");
}
return tabsClient.delete(BrowserContractHelpers.TABS_CONTENT_URI, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID });
}
protected void insertTestClient(final ContentProviderClient clientsClient) throws RemoteException {
ContentValues cv = new ContentValues();
cv.put(BrowserContract.Clients.GUID, TEST_CLIENT_GUID);
cv.put(BrowserContract.Clients.NAME, TEST_CLIENT_NAME);
clientsClient.insert(BrowserContractHelpers.CLIENTS_CONTENT_URI, cv);
}
@SuppressWarnings("unchecked")
protected void insertSomeTestTabs(ContentProviderClient tabsClient) throws RemoteException {
final JSONArray history1 = new JSONArray();
history1.add("http://test.com/test1.html");
testTab1 = new Tab("test title 1", "http://test.com/test1.png", history1, 1000);
final JSONArray history2 = new JSONArray();
history2.add("http://test.com/test2.html#1");
history2.add("http://test.com/test2.html#2");
history2.add("http://test.com/test2.html#3");
testTab2 = new Tab("test title 2", "http://test.com/test2.png", history2, 2000);
final JSONArray history3 = new JSONArray();
history3.add("http://test.com/test3.html#1");
history3.add("http://test.com/test3.html#2");
testTab3 = new Tab("test title 3", "http://test.com/test3.png", history3, 3000);
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab1.toContentValues(TEST_CLIENT_GUID, 0));
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab2.toContentValues(TEST_CLIENT_GUID, 1));
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab3.toContentValues(TEST_CLIENT_GUID, 2));
}
// Sanity.
@Test
public void testObtainCP() {
final ContentProviderClient clientsClient = getClientsClient();
Assert.assertNotNull(clientsClient);
clientsClient.release();
final ContentProviderClient tabsClient = getTabsClient();
Assert.assertNotNull(tabsClient);
tabsClient.release();
}
@Test
public void testDeleteEmptyClients() throws RemoteException {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient clientsClient = getClientsClient();
// Have to ensure that it's empty
clientsClient.delete(uri, null, null);
int deleted = clientsClient.delete(uri, null, null);
Assert.assertEquals(0, deleted);
}
@Test
public void testDeleteEmptyTabs() throws RemoteException {
final ContentProviderClient tabsClient = getTabsClient();
// Have to ensure that it's empty
deleteAllTestTabs(tabsClient);
int deleted = deleteAllTestTabs(tabsClient);
Assert.assertEquals(0, deleted);
}
@Test
public void testStoreAndRetrieveClients() throws RemoteException {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient clientsClient = getClientsClient();
// Have to ensure that it's empty
clientsClient.delete(uri, null, null);
final long now = System.currentTimeMillis();
final ContentValues first = new ContentValues();
final ContentValues second = new ContentValues();
first.put(BrowserContract.Clients.GUID, "abcdefghijkl");
first.put(BrowserContract.Clients.NAME, "Frist Psot");
first.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
second.put(BrowserContract.Clients.GUID, "mnopqrstuvwx");
second.put(BrowserContract.Clients.NAME, "Second!!1!");
second.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
ContentValues[] values = new ContentValues[] { first, second };
final int inserted = clientsClient.bulkInsert(uri, values);
Assert.assertEquals(2, inserted);
final String since = BrowserContract.Clients.LAST_MODIFIED + " >= ?";
final String[] nowArg = new String[] { String.valueOf(now) };
final String guidAscending = BrowserContract.Clients.GUID + " ASC";
Cursor cursor = clientsClient.query(uri, null, since, nowArg, guidAscending);
Assert.assertNotNull(cursor);
try {
Assert.assertTrue(cursor.moveToFirst());
Assert.assertEquals(2, cursor.getCount());
final String g1 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.GUID));
final String n1 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.NAME));
final long m1 = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Clients.LAST_MODIFIED));
Assert.assertEquals(first.get(BrowserContract.Clients.GUID), g1);
Assert.assertEquals(first.get(BrowserContract.Clients.NAME), n1);
Assert.assertEquals(now + 1, m1);
Assert.assertTrue(cursor.moveToNext());
final String g2 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.GUID));
final String n2 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.NAME));
final long m2 = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Clients.LAST_MODIFIED));
Assert.assertEquals(second.get(BrowserContract.Clients.GUID), g2);
Assert.assertEquals(second.get(BrowserContract.Clients.NAME), n2);
Assert.assertEquals(now + 2, m2);
Assert.assertFalse(cursor.moveToNext());
} finally {
cursor.close();
}
int deleted = clientsClient.delete(uri, null, null);
Assert.assertEquals(2, deleted);
}
@Test
public void testTabFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final ContentProviderClient clientsClient = getClientsClient();
deleteAllTestTabs(tabsClient);
deleteTestClient(clientsClient);
insertTestClient(clientsClient);
insertSomeTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
Assert.assertEquals(3, cursor.getCount());
cursor.moveToFirst();
final Tab parsed1 = Tab.fromCursor(cursor);
Assert.assertEquals(testTab1, parsed1);
cursor.moveToNext();
final Tab parsed2 = Tab.fromCursor(cursor);
Assert.assertEquals(testTab2, parsed2);
cursor.moveToPosition(2);
final Tab parsed3 = Tab.fromCursor(cursor);
Assert.assertEquals(testTab3, parsed3);
} finally {
cursor.close();
}
}
@Test
public void testDeletingClientDeletesTabs() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final ContentProviderClient clientsClient = getClientsClient();
deleteAllTestTabs(tabsClient);
deleteTestClient(clientsClient);
insertTestClient(clientsClient);
insertSomeTestTabs(tabsClient);
// Delete just the client...
clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, CLIENTS_GUID_IS, new String [] { TEST_CLIENT_GUID });
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, null);
// ... and all that client's tabs should be removed.
Assert.assertEquals(0, cursor.getCount());
} finally {
cursor.close();
}
}
@Test
public void testTabsRecordFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
deleteAllTestTabs(tabsClient);
insertTestClient(getClientsClient());
insertSomeTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
Assert.assertEquals(3, cursor.getCount());
cursor.moveToPosition(1);
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
// Make sure we clean up after ourselves.
Assert.assertEquals(1, cursor.getPosition());
Assert.assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
Assert.assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
Assert.assertEquals(3, tabsRecord.tabs.size());
Assert.assertEquals(testTab1, tabsRecord.tabs.get(0));
Assert.assertEquals(testTab2, tabsRecord.tabs.get(1));
Assert.assertEquals(testTab3, tabsRecord.tabs.get(2));
Assert.assertEquals(Math.max(Math.max(testTab1.lastUsed, testTab2.lastUsed), testTab3.lastUsed), tabsRecord.lastModified);
} finally {
cursor.close();
}
}
// Verify that we can fetch a record when there are no local tabs at all.
@Test
public void testEmptyTabsRecordFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
deleteAllTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
Assert.assertEquals(0, cursor.getCount());
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
Assert.assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
Assert.assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
Assert.assertNotNull(tabsRecord.tabs);
Assert.assertEquals(0, tabsRecord.tabs.size());
Assert.assertEquals(0, tabsRecord.lastModified);
} finally {
cursor.close();
}
}
// Not much of a test, but verifies the tabs record at least agrees with the
// disk data and doubles as a database inspector.
@Test
public void testLocalTabs() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
// Keep this in sync with the Fennec schema.
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, BrowserContract.Tabs.CLIENT_GUID + " IS NULL", null, positionAscending);
CursorDumper.dumpCursor(cursor);
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
Assert.assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
Assert.assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
Assert.assertNotNull(tabsRecord.tabs);
Assert.assertEquals(cursor.getCount(), tabsRecord.tabs.size());
} finally {
cursor.close();
}
}
}

View File

@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.tests.browser.junit3;
package org.mozilla.gecko.background.db;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
@ -9,26 +9,55 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.test.InstrumentationTestCase;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.background.db.CursorDumper;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.LocalTabsAccessor;
import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.db.TabsProvider;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.internal.runtime.RuntimeAdapter;
import org.robolectric.shadows.ShadowContentResolver;
import java.util.List;
public class TestRemoteTabs extends InstrumentationTestCase {
@RunWith(TestRunner.class)
public class TestTabsProviderRemoteTabs {
private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
private static final long ONE_WEEK_IN_MILLISECONDS = 7 * ONE_DAY_IN_MILLISECONDS;
private static final long THREE_WEEKS_IN_MILLISECONDS = 3 * ONE_WEEK_IN_MILLISECONDS;
protected TabsProvider provider;
@Before
public void setUp() {
provider = new TabsProvider();
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.TABS_AUTHORITY, new DelegatingTestContentProvider(provider));
}
@After
public void tearDown() throws Exception {
provider.shutdown();
provider = null;
}
protected ContentProviderClient getClientsClient() {
final ShadowContentResolver cr = new ShadowContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
}
@Test
public void testGetClientsWithoutTabsByRecencyFromCursor() throws Exception {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentResolver cr = getInstrumentation().getTargetContext().getContentResolver();
final ContentProviderClient cpc = cr.acquireContentProviderClient(uri);
final ContentProviderClient cpc = getClientsClient();
final LocalTabsAccessor accessor = new LocalTabsAccessor("test"); // The profile name given doesn't matter.
try {
@ -36,7 +65,7 @@ public class TestRemoteTabs extends InstrumentationTestCase {
cpc.delete(uri, null, null);
Cursor allClients = cpc.query(uri, null, null, null, null);
try {
assertEquals(0, allClients.getCount());
Assert.assertEquals(0, allClients.getCount());
} finally {
allClients.close();
}
@ -60,24 +89,24 @@ public class TestRemoteTabs extends InstrumentationTestCase {
ContentValues[] values = new ContentValues[]{local, remote1, remote2};
int inserted = cpc.bulkInsert(uri, values);
assertEquals(3, inserted);
Assert.assertEquals(3, inserted);
allClients = cpc.query(BrowserContract.Clients.CONTENT_RECENCY_URI, null, null, null, null);
try {
CursorDumper.dumpCursor(allClients);
// The local client is not ignored.
assertEquals(3, allClients.getCount());
Assert.assertEquals(3, allClients.getCount());
final List<RemoteClient> clients = accessor.getClientsWithoutTabsByRecencyFromCursor(allClients);
assertEquals(3, clients.size());
Assert.assertEquals(3, clients.size());
for (RemoteClient client : clients) {
// Each client should not have any tabs.
assertNotNull(client.tabs);
assertEquals(0, client.tabs.size());
Assert.assertNotNull(client.tabs);
Assert.assertEquals(0, client.tabs.size());
}
// Since there are no tabs, the order should be based on last_modified.
assertEquals("guid2", clients.get(0).guid);
assertEquals("guid1", clients.get(1).guid);
assertEquals(null, clients.get(2).guid);
Assert.assertEquals("guid2", clients.get(0).guid);
Assert.assertEquals("guid1", clients.get(1).guid);
Assert.assertEquals(null, clients.get(2).guid);
} finally {
allClients.close();
}
@ -102,25 +131,25 @@ public class TestRemoteTabs extends InstrumentationTestCase {
values = new ContentValues[]{remoteTab1, remoteTab2};
inserted = cpc.bulkInsert(BrowserContract.Tabs.CONTENT_URI, values);
assertEquals(2, inserted);
Assert.assertEquals(2, inserted);
allClients = cpc.query(BrowserContract.Clients.CONTENT_RECENCY_URI, null, BrowserContract.Clients.GUID + " IS NOT NULL", null, null);
try {
CursorDumper.dumpCursor(allClients);
// The local client is ignored.
assertEquals(2, allClients.getCount());
Assert.assertEquals(2, allClients.getCount());
final List<RemoteClient> clients = accessor.getClientsWithoutTabsByRecencyFromCursor(allClients);
assertEquals(2, clients.size());
Assert.assertEquals(2, clients.size());
for (RemoteClient client : clients) {
// Each client should be remote and should not have any tabs.
assertNotNull(client.guid);
assertNotNull(client.tabs);
assertEquals(0, client.tabs.size());
Assert.assertNotNull(client.guid);
Assert.assertNotNull(client.tabs);
Assert.assertEquals(0, client.tabs.size());
}
// Since now there is a tab attached to the remote2 client more recent than the
// remote1 client modified time, it should be first.
assertEquals("guid1", clients.get(0).guid);
assertEquals("guid2", clients.get(1).guid);
Assert.assertEquals("guid1", clients.get(0).guid);
Assert.assertEquals("guid2", clients.get(1).guid);
} finally {
allClients.close();
}
@ -129,20 +158,19 @@ public class TestRemoteTabs extends InstrumentationTestCase {
}
}
@Test
public void testGetRecentRemoteClientsUpToOneWeekOld() throws Exception {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final Context context = getInstrumentation().getTargetContext();
final String profileName = GeckoProfile.get(context).getName();
final ContentResolver cr = context.getContentResolver();
final ContentProviderClient cpc = cr.acquireContentProviderClient(uri);
final LocalTabsAccessor accessor = new LocalTabsAccessor(profileName);
final ContentProviderClient cpc = getClientsClient();
final LocalTabsAccessor accessor = new LocalTabsAccessor("test"); // The profile name given doesn't matter.
final Context context = RuntimeEnvironment.application.getApplicationContext();
try {
// Start Clean
cpc.delete(uri, null, null);
final Cursor allClients = cpc.query(uri, null, null, null, null);
try {
assertEquals(0, allClients.getCount());
Assert.assertEquals(0, allClients.getCount());
} finally {
allClients.close();
}
@ -185,7 +213,7 @@ public class TestRemoteTabs extends InstrumentationTestCase {
ContentValues[] values = new ContentValues[]{local, remote1, remote2, remote3, remote4, remote5};
int inserted = cpc.bulkInsert(uri, values);
assertEquals(values.length, inserted);
Assert.assertEquals(values.length, inserted);
final Cursor remoteClients =
accessor.getRemoteClientsByRecencyCursor(context);
@ -194,18 +222,18 @@ public class TestRemoteTabs extends InstrumentationTestCase {
CursorDumper.dumpCursor(remoteClients);
// Local client is not included.
// (remote1, guid1), (remote2, guid2), (remote3, guid3) are expected.
assertEquals(3, remoteClients.getCount());
Assert.assertEquals(3, remoteClients.getCount());
// Check the inner data, according to recency.
List<RemoteClient> recentRemoteClientsList =
accessor.getClientsWithoutTabsByRecencyFromCursor(remoteClients);
assertEquals(3, recentRemoteClientsList.size());
assertEquals("remote1", recentRemoteClientsList.get(0).name);
assertEquals("guid1", recentRemoteClientsList.get(0).guid);
assertEquals("remote2", recentRemoteClientsList.get(1).name);
assertEquals("guid2", recentRemoteClientsList.get(1).guid);
assertEquals("remote3", recentRemoteClientsList.get(2).name);
assertEquals("guid3", recentRemoteClientsList.get(2).guid);
Assert.assertEquals(3, recentRemoteClientsList.size());
Assert.assertEquals("remote1", recentRemoteClientsList.get(0).name);
Assert.assertEquals("guid1", recentRemoteClientsList.get(0).guid);
Assert.assertEquals("remote2", recentRemoteClientsList.get(1).name);
Assert.assertEquals("guid2", recentRemoteClientsList.get(1).guid);
Assert.assertEquals("remote3", recentRemoteClientsList.get(2).name);
Assert.assertEquals("guid3", recentRemoteClientsList.get(2).guid);
} finally {
remoteClients.close();
}

View File

@ -21,7 +21,6 @@ jar.sources += [
'src/org/mozilla/tests/browser/junit3/TestImageDownloader.java',
'src/org/mozilla/tests/browser/junit3/TestJarReader.java',
'src/org/mozilla/tests/browser/junit3/TestRawResource.java',
'src/org/mozilla/tests/browser/junit3/TestRemoteTabs.java',
'src/org/mozilla/tests/browser/junit3/TestSuggestedSites.java',
]
jar.generated_sources = [] # None yet -- try to keep it this way.

View File

@ -8,7 +8,7 @@ this.EXPORTED_SYMBOLS = ["OneCRLClient"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://services-common/moz-kinto-client.js");
Cu.import("resource://services-common/kinto-offline-client.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,10 +15,11 @@ EXTRA_COMPONENTS += [
EXTRA_JS_MODULES['services-common'] += [
'async.js',
'kinto-http-client.js',
'kinto-offline-client.js',
'kinto-updater.js',
'KintoCertificateBlocklist.js',
'logmanager.js',
'moz-kinto-client.js',
'observers.js',
'rest.js',
'stringbundle.js',

View File

@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-common/moz-kinto-client.js")
Cu.import("resource://services-common/kinto-offline-client.js");
Cu.import("resource://testing-common/httpd.js");
const BinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1",
@ -359,7 +359,7 @@ function getSampleResponse(req, port) {
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"settings":{"cliquet.batch_max_requests":25}, "url":`http://localhost:${port}/v1/`, "documentation":"https://kinto.readthedocs.org/", "version":"1.5.1", "commit":"cbc6f58", "hello":"kinto"})
},
"GET:/v1/buckets/default/collections/test_collection/records?": {
"GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -370,7 +370,7 @@ function getSampleResponse(req, port) {
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data":[{"last_modified":1445606341071, "done":false, "id":"68db8313-686e-4fff-835e-07d78ad6f2af", "title":"New test"}]})
},
"GET:/v1/buckets/default/collections/test_collection/records?_since=1445606341071": {
"GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified&_since=1445606341071": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",

View File

@ -4,7 +4,7 @@
const { Constructor: CC } = Components;
Cu.import("resource://services-common/KintoCertificateBlocklist.js");
Cu.import("resource://services-common/moz-kinto-client.js")
Cu.import("resource://services-common/kinto-offline-client.js");
Cu.import("resource://testing-common/httpd.js");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
@ -145,7 +145,7 @@ function getSampleResponse(req, port) {
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"settings":{"cliquet.batch_max_requests":25}, "url":`http://localhost:${port}/v1/`, "documentation":"https://kinto.readthedocs.org/", "version":"1.5.1", "commit":"cbc6f58", "hello":"kinto"})
},
"GET:/v1/buckets/blocklists/collections/certificates/records?": {
"GET:/v1/buckets/blocklists/collections/certificates/records?_sort=-last_modified": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -161,7 +161,7 @@ function getSampleResponse(req, port) {
"last_modified":3000
}]})
},
"GET:/v1/buckets/blocklists/collections/certificates/records?_since=3000": {
"GET:/v1/buckets/blocklists/collections/certificates/records?_sort=-last_modified&_since=3000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",

View File

@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-common/moz-kinto-client.js");
Cu.import("resource://services-common/kinto-offline-client.js");
// set up what we need to make storage adapters
const Kinto = loadKinto();

View File

@ -3,6 +3,8 @@
var {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
// WeakMap[Extension -> BackgroundPage]
var backgroundPagesMap = new WeakMap();
@ -65,6 +67,11 @@ BackgroundPage.prototype = {
this.contentWindow = window;
this.context.contentWindow = window;
if (this.extension.addonData.instanceID) {
AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
.then(addon => addon.setDebugGlobal(window));
}
// TODO: Right now we run onStartup after the background page
// finishes. See if this is what Chrome does.
let loadListener = event => {
@ -99,6 +106,11 @@ BackgroundPage.prototype = {
this.chromeWebNav.loadURI("about:blank", 0, null, null, null);
this.chromeWebNav.close();
this.chromeWebNav = null;
if (this.extension.addonData.instanceID) {
AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
.then(addon => addon.setDebugGlobal(null));
}
},
};

View File

@ -5,6 +5,8 @@ support-files =
interruptible.sjs
file_sample.html
[test_chrome_ext_background_debug_global.html]
skip-if = (os == 'android') # android doesn't have devtools
[test_chrome_ext_downloads_download.html]
[test_chrome_ext_downloads_misc.html]
[test_chrome_ext_downloads_search.html]

View File

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebExtension test</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
const {
utils: Cu,
} = Components;
Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm");
/**
* This test is asserting that ext-backgroundPage.js successfully sets its
* debug global in the AddonWrapper provided by XPIProvider.jsm
*
* It does _not_ test any functionality in devtools and does not guarantee
* debugging is actually working correctly end-to-end.
*/
function backgroundScript() {
window.testThing = "test!";
browser.test.notifyPass("background script ran");
}
let extensionData = {
useAddonManager: true,
background: "(" + backgroundScript.toString() + ")()",
manifest: {},
files: {},
};
add_task(function* () {
let extension = ExtensionTestUtils.loadExtension(extensionData);
yield extension.startup();
info("extension loaded");
yield extension.awaitFinish("background script ran");
yield new Promise(function(resolve) {
window.BrowserToolboxProcess.emit("connectionchange", "opened", {
setAddonOptions(id, options) {
if (id === extension.id) {
let context = Cu.waiveXrays(options.global);
ok(context.chrome, "global context has a chrome object");
ok(context.browser, "global context has a browser object");
is("test!", context.testThing, "global context is the background script context");
resolve();
}
},
});
});
yield extension.unload();
info("extension unloaded");
});
</script>
</body>
</html>

View File

@ -22,6 +22,7 @@ XPCOMUtils.defineLazyGetter(this, "l10n", () => {
});
this.InsecurePasswordUtils = {
_formRootsWarned: new WeakMap(),
_sendWebConsoleMessage(messageTag, domDoc) {
let windowId = WebConsoleUtils.getInnerWindowId(domDoc.defaultView);
let category = "Insecure Password Field";
@ -69,30 +70,40 @@ this.InsecurePasswordUtils = {
* @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
*/
checkForInsecurePasswords(aForm) {
if (this._formRootsWarned.has(aForm.rootElement) ||
this._formRootsWarned.get(aForm.rootElement)) {
return;
}
let domDoc = aForm.ownerDocument;
let topDocument = domDoc.defaultView.top.document;
let isSafePage = LoginManagerContent.isDocumentSecure(topDocument);
if (!isSafePage) {
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
this._formRootsWarned.set(aForm.rootElement, true);
}
// Check if we are on an iframe with insecure src, or inside another
// insecure iframe or document.
if (this._checkForInsecureNestedDocuments(domDoc)) {
this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
this._formRootsWarned.set(aForm.rootElement, true);
isSafePage = false;
}
let isFormSubmitHTTP = false, isFormSubmitHTTPS = false;
// Note that aForm.action can be a relative path (e.g. "", "/login", "//example.com", etc.)
// but we don't warn about those since we would have already warned about the form's document
// not being safe above.
if (aForm.action.match(/^http:\/\//)) {
this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
isFormSubmitHTTP = true;
} else if (aForm.action.match(/^https:\/\//)) {
isFormSubmitHTTPS = true;
if (aForm.rootElement instanceof Ci.nsIDOMHTMLFormElement) {
// Note that aForm.action can be a relative path (e.g. "", "/login", "//example.com", etc.)
// but we don't warn about those since we would have already warned about the form's document
// not being safe above.
if (aForm.action.match(/^http:\/\//)) {
this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
this._formRootsWarned.set(aForm.rootElement, true);
isFormSubmitHTTP = true;
} else if (aForm.action.match(/^https:\/\//)) {
isFormSubmitHTTPS = true;
}
}
// The safety of a password field determined by the form action and the page protocol

View File

@ -5,6 +5,7 @@
"use strict";
this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
"FormLikeFactory",
"UserAutoCompleteResult" ];
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

View File

@ -15,6 +15,7 @@ support-files =
../subtst_notifications_change_p.html
authenticate.sjs
form_basic.html
formless_basic.html
head.js
insecure_test.html
insecure_test_subframe.html
@ -29,6 +30,7 @@ skip-if = e10s # bug 1263760
[browser_filldoorhanger.js]
[browser_hasInsecureLoginForms.js]
[browser_hasInsecureLoginForms_streamConverter.js]
[browser_insecurePasswordWarning.js]
[browser_notifications.js]
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
[browser_passwordmgr_editing.js]

View File

@ -0,0 +1,84 @@
"use strict";
const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
const WARNING_PATTERN = [{
key: "INSECURE_FORM_ACTION",
msg: 'JavaScript Warning: "Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen."'
}, {
key: "INSECURE_PAGE",
msg: 'JavaScript Warning: "Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen."'
}];
add_task(function* testInsecurePasswordWarning() {
let warningPatternHandler;
function messageHandler(msg) {
function findWarningPattern(msg) {
return WARNING_PATTERN.find(patternPair => {
return msg.indexOf(patternPair.msg) !== -1;
});
}
let warning = findWarningPattern(msg.message);
// Only handle the insecure password related warning messages.
if (warning) {
// Prevent any unexpected or redundant matched warning message coming after
// the test case is ended.
ok(warningPatternHandler, "Invoke a valid warning message handler");
warningPatternHandler(warning, msg.message);
}
}
Services.console.registerListener(messageHandler);
registerCleanupFunction(function() {
Services.console.unregisterListener(messageHandler);
});
for (let [origin, testFile, expectWarnings] of [
// Form action at 127.0.0.1/localhost is considered as a secure case.
// There should be no INSECURE_FORM_ACTION warning at 127.0.0.1/localhost.
// This will be fixed at Bug 1261234.
["http://127.0.0.1", "form_basic.html", ["INSECURE_FORM_ACTION"]],
["http://127.0.0.1", "formless_basic.html", []],
["http://example.com", "form_basic.html", ["INSECURE_FORM_ACTION", "INSECURE_PAGE"]],
["http://example.com", "formless_basic.html", ["INSECURE_PAGE"]],
["https://example.com", "form_basic.html", []],
["https://example.com", "formless_basic.html", []],
]) {
let testUrlPath = origin + TEST_URL_PATH + testFile;
var promiseConsoleMessages = new Promise(resolve => {
warningPatternHandler = function (warning, originMessage) {
ok(warning, "Handling a warning pattern");
let fullMessage = `[${warning.msg} {file: "${testUrlPath}" line: 0 column: 0 source: "0"}]`;
is(originMessage, fullMessage, "Message full matched:" + originMessage);
let index = expectWarnings.indexOf(warning.key);
isnot(index, -1, "Found warning: " + warning.key + " for URL:" + testUrlPath);
if (index !== -1) {
// Remove the shown message.
expectWarnings.splice(index, 1);
}
if (expectWarnings.length === 0) {
info("All warnings are shown. URL:" + testUrlPath);
resolve();
}
}
});
yield BrowserTestUtils.withNewTab({
gBrowser,
url: testUrlPath
}, function*() {
if (expectWarnings.length === 0) {
info("All warnings are shown. URL:" + testUrlPath);
return Promise.resolve();
}
return promiseConsoleMessages;
});
// Remove warningPatternHandler to stop handling the matched warning pattern
// and the task should not get any warning anymore.
warningPatternHandler = null;
}
});

View File

@ -0,0 +1,18 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Simplest form with username and password fields. -->
<input id="form-basic-username" name="username">
<input id="form-basic-password" name="password" type="password">
<input id="form-basic-submit" type="submit">
<button id="add">Add input[type=password]</button>
<script>
document.getElementById("add").addEventListener("click", function () {
var node = document.createElement("input");
node.setAttribute("type", "password");
document.querySelector("body").appendChild(node);
});
</script>
</body></html>

View File

@ -961,7 +961,6 @@ var Impl = {
this._log.trace("getKeyedHistograms - Skipping test histogram: " + id);
continue;
}
ret[id] = {};
let keyed = Telemetry.getKeyedHistogramById(id);
let snapshot = null;
if (subsession) {
@ -970,7 +969,15 @@ var Impl = {
} else {
snapshot = keyed.snapshot();
}
for (let key of Object.keys(snapshot)) {
let keys = Object.keys(snapshot);
if (keys.length == 0) {
// Skip empty keyed histogram.
continue;
}
ret[id] = {};
for (let key of keys) {
ret[id][key] = this.packHistogram(snapshot[key]);
}
}

View File

@ -178,7 +178,9 @@ This section contains the histograms that are valid for the current platform. ``
keyedHistograms
---------------
This section contains the keyed histograms available for the current platform. Unlike the ``histograms`` section, this section always reports all the keyed histograms, even though they contain no data.
This section contains the keyed histograms available for the current platform.
As of Firefox 48, this section does not contain empty keyed histograms anymore.
threadHangStats
---------------

View File

@ -365,11 +365,9 @@ function checkPayload(payload, reason, successfulPings, savedPings) {
Assert.ok("keyedHistograms" in payload);
let keyedHistograms = payload.keyedHistograms;
Assert.ok(TELEMETRY_TEST_KEYED_FLAG in keyedHistograms);
Assert.ok(!(TELEMETRY_TEST_KEYED_FLAG in keyedHistograms));
Assert.ok(TELEMETRY_TEST_KEYED_COUNT in keyedHistograms);
Assert.deepEqual({}, keyedHistograms[TELEMETRY_TEST_KEYED_FLAG]);
const expected_keyed_count = {
"a": {
range: [1, 2],
@ -643,10 +641,8 @@ add_task(function* test_checkSubsessionHistograms() {
Assert.equal(subsession.info.reason, "environment-change");
Assert.ok(!(COUNT_ID in classic.histograms));
Assert.ok(!(COUNT_ID in subsession.histograms));
Assert.ok(KEYED_ID in classic.keyedHistograms);
Assert.ok(KEYED_ID in subsession.keyedHistograms);
Assert.deepEqual(classic.keyedHistograms[KEYED_ID], {});
Assert.deepEqual(subsession.keyedHistograms[KEYED_ID], {});
Assert.ok(!(KEYED_ID in classic.keyedHistograms));
Assert.ok(!(KEYED_ID in subsession.keyedHistograms));
checkHistograms(classic.histograms, subsession.histograms);
checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms);
@ -677,9 +673,8 @@ add_task(function* test_checkSubsessionHistograms() {
Assert.ok(!(COUNT_ID in classic.histograms));
Assert.ok(!(COUNT_ID in subsession.histograms));
Assert.ok(KEYED_ID in classic.keyedHistograms);
Assert.ok(KEYED_ID in subsession.keyedHistograms);
Assert.deepEqual(classic.keyedHistograms[KEYED_ID], {});
Assert.ok(!(KEYED_ID in classic.keyedHistograms));
Assert.ok(!(KEYED_ID in subsession.keyedHistograms));
checkHistograms(classic.histograms, subsession.histograms);
checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms);
@ -725,10 +720,9 @@ add_task(function* test_checkSubsessionHistograms() {
Assert.equal(subsession.histograms[COUNT_ID].sum, 0);
Assert.ok(KEYED_ID in classic.keyedHistograms);
Assert.ok(KEYED_ID in subsession.keyedHistograms);
Assert.ok(!(KEYED_ID in subsession.keyedHistograms));
Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 1);
Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 1);
Assert.deepEqual(subsession.keyedHistograms[KEYED_ID], {});
// Adding values should get picked up in both again.
count.add(1);
@ -872,7 +866,7 @@ add_task(function* test_dailyCollection() {
Assert.equal(subsessionStartDate.toISOString(), expectedDate.toISOString());
Assert.equal(ping.payload.histograms[COUNT_ID].sum, 0);
Assert.deepEqual(ping.payload.keyedHistograms[KEYED_ID], {});
Assert.ok(!(KEYED_ID in ping.payload.keyedHistograms));
// Trigger and collect another daily ping, with the histograms being set again.
count.add(1);
@ -1079,7 +1073,7 @@ add_task(function* test_environmentChange() {
Assert.equal(subsessionStartDate.toISOString(), startDay.toISOString());
Assert.equal(ping.payload.histograms[COUNT_ID].sum, 0);
Assert.deepEqual(ping.payload.keyedHistograms[KEYED_ID], {});
Assert.ok(!(KEYED_ID in ping.payload.keyedHistograms));
});
add_task(function* test_savedPingsOnShutdown() {

View File

@ -1239,6 +1239,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
if (this._currentIndex < existingItemsCount) {
// re-use the existing item
item = this.richlistbox.childNodes[this._currentIndex];
item.setAttribute("dir", this.style.direction);
// Completely reuse the existing richlistitem for invalidation
// due to new results, but only when: the item is the same, *OR*
@ -1249,8 +1250,13 @@ extends="chrome://global/content/bindings/popup.xml#popup">
invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
(item.getAttribute("url") == url ||
this.richlistbox.mouseSelectedIndex === this._currentIndex)) {
item.handleOverUnderflow();
item.collapsed = false;
// Call adjustSiteIconStart only after setting collapsed=false.
// The calculations it does may be wrong otherwise.
item.adjustSiteIconStart(this._siteIconStart);
// The popup may have changed size between now and the last time
// the item was shown, so always handle over/underflow.
item.handleOverUnderflow();
this._currentIndex++;
continue;
}
@ -1258,6 +1264,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
else {
// need to create a new item
item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
item.setAttribute("dir", this.style.direction);
}
// set these attributes before we set the class
@ -1281,6 +1288,11 @@ extends="chrome://global/content/bindings/popup.xml#popup">
this.richlistbox.appendChild(item);
}
let changed = item.adjustSiteIconStart(this._siteIconStart);
if (changed) {
item.handleOverUnderflow();
}
this._currentIndex++;
}
@ -1296,6 +1308,26 @@ extends="chrome://global/content/bindings/popup.xml#popup">
</body>
</method>
<!-- The x-coordinate relative to the leading edge of the window of the
items' site icons (favicons). -->
<property name="siteIconStart"
onget="return this._siteIconStart;">
<setter>
<![CDATA[
if (val != this._siteIconStart) {
this._siteIconStart = val;
for (let item of this.richlistbox.childNodes) {
let changed = item.adjustSiteIconStart(val);
if (changed) {
item.handleOverUnderflow();
}
}
}
return val;
]]>
</setter>
</property>
<method name="selectBy">
<parameter name="aReverse"/>
<parameter name="aPage"/>
@ -1997,6 +2029,37 @@ extends="chrome://global/content/bindings/popup.xml#popup">
</body>
</method>
<!-- Sets the x-coordinate of the leading edge of the site icon (favicon)
relative the the leading edge of the window.
@param newStart The new x-coordinate, relative to the leading edge of
the window. Pass undefined to reset the icon's position to
whatever is specified in CSS.
@return True if the icon's position changed, false if not. -->
<method name="adjustSiteIconStart">
<parameter name="newStart"/>
<body>
<![CDATA[
if (typeof(newStart) != "number") {
this._typeIcon.style.removeProperty("-moz-margin-start");
return true;
}
let rect = this._siteIcon.getBoundingClientRect();
let dir = this.getAttribute("dir");
let delta = dir == "rtl" ? rect.right - newStart
: newStart - rect.left;
let px = this._typeIcon.style.MozMarginStart;
if (!px) {
// Allow -moz-margin-start not to be specified in CSS initially.
let style = window.getComputedStyle(this._typeIcon);
px = dir == "rtl" ? style.marginRight : style.marginLeft;
}
let typeIconStart = Number(px.substr(0, px.length - 2));
this._typeIcon.style.MozMarginStart = (typeIconStart + delta) + "px";
return delta > 0;
]]>
</body>
</method>
<!-- This method truncates the displayed strings as necessary. -->
<method name="_handleOverflow">
<body><![CDATA[
@ -2014,8 +2077,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
// This extra padding amount is basically arbitrary but keeps the text
// from getting too close to the popup's edge.
let extraPadding = 30;
let dir =
this.ownerDocument.defaultView.getComputedStyle(this).direction;
let dir = this.getAttribute("dir");
let titleStart = dir == "rtl" ? itemRect.right - titleRect.right
: titleRect.left - itemRect.left;
let itemWidth = itemRect.width - titleStart - extraPadding;

View File

@ -3931,7 +3931,16 @@ this.XPIProvider = {
for (let [id, val] of this.activeAddons) {
if (aInstanceID == val.instanceID) {
return new Promise(resolve => this.getAddonByID(id, resolve));
if (val.safeWrapper) {
return Promise.resolve(val.safeWrapper);
}
return new Promise(resolve => {
this.getAddonByID(id, function(addon) {
val.safeWrapper = new PrivateWrapper(addon);
resolve(val.safeWrapper);
});
});
}
}
@ -4218,7 +4227,7 @@ this.XPIProvider = {
for (let [id, val] of this.activeAddons) {
aConnection.setAddonOptions(
id, { global: val.bootstrapScope });
id, { global: val.debugGlobal || val.bootstrapScope });
}
},
@ -4507,6 +4516,8 @@ this.XPIProvider = {
this.addAddonsToCrashReporter();
this.activeAddons.set(aId, {
debugGlobal: null,
safeWrapper: null,
bootstrapScope: null,
// a Symbol passed to this add-on, which it can use to identify itself
instanceID: Symbol(aId),
@ -7339,6 +7350,32 @@ AddonWrapper.prototype = {
}
};
/**
* The PrivateWrapper is used to expose certain functionality only when being
* called with the add-on instanceID, disallowing other add-ons to access it.
*/
function PrivateWrapper(aAddon) {
AddonWrapper.call(this, aAddon);
}
PrivateWrapper.prototype = Object.create(AddonWrapper.prototype);
Object.assign(PrivateWrapper.prototype, {
/**
* Defines a global context to be used in the console
* of the add-on debugging window.
*
* @param global
* The object to set as global context. Must be a window object.
*/
setDebugGlobal(global) {
let activeAddon;
if (activeAddon = XPIProvider.activeAddons.get(this.id)) {
activeAddon.debugGlobal = global;
}
}
});
function chooseValue(aAddon, aObj, aProp) {
let repositoryAddon = aAddon._repositoryAddon;
let objValue = aObj[aProp];