mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Merge m-c to inbound, a=merge
MozReview-Commit-ID: 2zwhun4JqPs
This commit is contained in:
commit
115c5a357a
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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).
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
@ -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. */
|
||||
|
||||
|
@ -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),
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
@ -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);
|
@ -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),
|
||||
|
@ -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');
|
||||
|
@ -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"
|
||||
|
@ -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());
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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--;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
@ -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',
|
||||
|
@ -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&utm_medium=notifications&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"
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.");
|
||||
|
@ -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" />
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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',
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
sdk=21
|
||||
constants=org.mozilla.gecko.BuildConfig
|
||||
packageName=org.mozilla.gecko
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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.
|
||||
|
@ -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");
|
||||
|
1866
services/common/kinto-http-client.js
Normal file
1866
services/common/kinto-http-client.js
Normal file
File diff suppressed because it is too large
Load Diff
1903
services/common/kinto-offline-client.js
Normal file
1903
services/common/kinto-offline-client.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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',
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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>
|
@ -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
|
||||
|
@ -5,6 +5,7 @@
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
|
||||
"FormLikeFactory",
|
||||
"UserAutoCompleteResult" ];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
@ -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>
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
---------------
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user