зеркало из https://github.com/mozilla/gecko-dev.git
Merge latest green b2g-inbound changeset and mozilla-central
This commit is contained in:
Коммит
65824f4564
|
@ -108,7 +108,7 @@ AccGroupInfo::AccGroupInfo(Accessible* aItem, role aRole) :
|
|||
return;
|
||||
|
||||
roles::Role parentRole = parent->Role();
|
||||
if (IsConceptualParent(aRole, parentRole))
|
||||
if (ShouldReportRelations(aRole, parentRole))
|
||||
mParent = parent;
|
||||
|
||||
// ARIA tree and list can be arranged by using ARIA groups to organize levels.
|
||||
|
@ -171,9 +171,9 @@ AccGroupInfo::FirstItemOf(Accessible* aContainer)
|
|||
}
|
||||
}
|
||||
|
||||
// Otherwise it can be a direct child.
|
||||
// Otherwise, it can be a direct child if the container is a list or tree.
|
||||
item = aContainer->FirstChild();
|
||||
if (IsConceptualParent(BaseRole(item->Role()), containerRole))
|
||||
if (ShouldReportRelations(item->Role(), containerRole))
|
||||
return item;
|
||||
|
||||
return nullptr;
|
||||
|
@ -201,29 +201,21 @@ AccGroupInfo::NextItemTo(Accessible* aItem)
|
|||
}
|
||||
}
|
||||
|
||||
NS_NOTREACHED("Item in the midle of the group but there's no next item!");
|
||||
NS_NOTREACHED("Item in the middle of the group but there's no next item!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
AccGroupInfo::IsConceptualParent(role aRole, role aParentRole)
|
||||
AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole)
|
||||
{
|
||||
// We only want to report hierarchy-based node relations for items in tree or
|
||||
// list form. ARIA level/owns relations are always reported.
|
||||
if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM)
|
||||
return true;
|
||||
if ((aParentRole == roles::TABLE || aParentRole == roles::TREE_TABLE) &&
|
||||
aRole == roles::ROW)
|
||||
if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW)
|
||||
return true;
|
||||
if (aParentRole == roles::LIST && aRole == roles::LISTITEM)
|
||||
return true;
|
||||
if (aParentRole == roles::COMBOBOX_LIST && aRole == roles::COMBOBOX_OPTION)
|
||||
return true;
|
||||
if (aParentRole == roles::LISTBOX && aRole == roles::OPTION)
|
||||
return true;
|
||||
if (aParentRole == roles::PAGETABLIST && aRole == roles::PAGETAB)
|
||||
return true;
|
||||
if ((aParentRole == roles::POPUP_MENU || aParentRole == roles::MENUPOPUP) &&
|
||||
aRole == roles::MENUITEM)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -91,10 +91,10 @@ private:
|
|||
}
|
||||
|
||||
/**
|
||||
* Return true if the given parent role is conceptual parent of the given
|
||||
* role.
|
||||
* Return true if the given parent and child roles should have their node
|
||||
* relations reported.
|
||||
*/
|
||||
static bool IsConceptualParent(a11y::role aRole, a11y::role aParentRole);
|
||||
static bool ShouldReportRelations(a11y::role aRole, a11y::role aParentRole);
|
||||
|
||||
uint32_t mPosInSet;
|
||||
uint32_t mSetSize;
|
||||
|
|
|
@ -96,6 +96,61 @@ function testRelation(aIdentifier, aRelType, aRelatedIdentifiers)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the given accessible relations don't exist.
|
||||
*
|
||||
* @param aIdentifier [in] identifier to get an accessible, may be ID
|
||||
* attribute or DOM element or accessible object
|
||||
* @param aRelType [in] relation type (see constants above)
|
||||
* @param aUnrelatedIdentifiers [in] identifier or array of identifiers of
|
||||
* accessibles that shouldn't exist for this
|
||||
* relation.
|
||||
*/
|
||||
function testAbsentRelation(aIdentifier, aRelType, aUnrelatedIdentifiers)
|
||||
{
|
||||
var relation = getRelationByType(aIdentifier, aRelType);
|
||||
|
||||
var relDescr = getRelationErrorMsg(aIdentifier, aRelType);
|
||||
var relDescrStart = getRelationErrorMsg(aIdentifier, aRelType, true);
|
||||
|
||||
if (!aUnrelatedIdentifiers) {
|
||||
ok(false, "No identifiers given for unrelated accessibles.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!relation || !relation.targetsCount) {
|
||||
ok(true, "No relations exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
var relatedIds = (aUnrelatedIdentifiers instanceof Array) ?
|
||||
aUnrelatedIdentifiers : [aUnrelatedIdentifiers];
|
||||
|
||||
var targets = [];
|
||||
for (var idx = 0; idx < relatedIds.length; idx++)
|
||||
targets.push(getAccessible(relatedIds[idx]));
|
||||
|
||||
if (targets.length != relatedIds.length)
|
||||
return;
|
||||
|
||||
var actualTargets = relation.getTargets();
|
||||
|
||||
// Any found targets that match given accessibles should be called out.
|
||||
for (var idx = 0; idx < targets.length; idx++) {
|
||||
var notFound = true;
|
||||
var enumerate = actualTargets.enumerate();
|
||||
while (enumerate.hasMoreElements()) {
|
||||
var relatedAcc = enumerate.getNext().QueryInterface(nsIAccessible);
|
||||
if (targets[idx] == relatedAcc) {
|
||||
notFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ok(notFound, prettyName(relatedIds[idx]) + " is a target of " + relDescr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return related accessible for the given relation type.
|
||||
*
|
||||
|
|
|
@ -84,6 +84,18 @@
|
|||
testRelation("tree2_ti1a", RELATION_NODE_CHILD_OF, "tree2_ti1");
|
||||
testRelation("tree2_ti1b", RELATION_NODE_CHILD_OF, "tree2_ti1");
|
||||
|
||||
// 'node child of' relation for row role in grid.
|
||||
// Relation for row associated using aria-owns should exist.
|
||||
testRelation("simplegrid-ownrow", RELATION_NODE_CHILD_OF, "simplegrid");
|
||||
// Relation for row associated using aria-level should exist.
|
||||
testRelation("simplegrid-row3", RELATION_NODE_CHILD_OF,
|
||||
"simplegrid-row2");
|
||||
// Relations for hierarchical children elements shouldn't exist.
|
||||
testAbsentRelation("simplegrid-row1", RELATION_NODE_CHILD_OF,
|
||||
"simplegrid");
|
||||
testAbsentRelation("simplegrid-row2", RELATION_NODE_CHILD_OF,
|
||||
"simplegrid");
|
||||
|
||||
// 'node child of' relation for row role of treegrid
|
||||
testRelation("treegridrow1", RELATION_NODE_CHILD_OF, "treegrid");
|
||||
testRelation("treegridrow2", RELATION_NODE_CHILD_OF, "treegrid");
|
||||
|
@ -118,6 +130,14 @@
|
|||
testRelation("treegrid", RELATION_NODE_PARENT_OF,
|
||||
["treegridrow1", "treegridrow2"]);
|
||||
|
||||
// 'node parent of' relation on ARIA grid.
|
||||
// Should only have relation to child added through aria-owns.
|
||||
testRelation("simplegrid", RELATION_NODE_PARENT_OF, "simplegrid-ownrow");
|
||||
// 'node parent of' relation on ARIA grid's row.
|
||||
// Should only have relation to child through aria-level.
|
||||
testRelation("simplegrid-row2", RELATION_NODE_PARENT_OF,
|
||||
"simplegrid-row3");
|
||||
|
||||
// 'node parent of' relation on ARIA list structured by groups
|
||||
testRelation("list", RELATION_NODE_PARENT_OF,
|
||||
"listitem1");
|
||||
|
@ -290,6 +310,22 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-owns="simplegrid-ownrow" role="grid" id="simplegrid">
|
||||
<div role="row" id="simplegrid-row1" aria-level="1">
|
||||
<div role="gridcell">cell 1,1</div>
|
||||
<div role="gridcell">cell 1,2</div>
|
||||
</div>
|
||||
<div role="row" id="simplegrid-row2" aria-level="1">
|
||||
<div role="gridcell">cell 2,1</div>
|
||||
<div role="gridcell">cell 2,2</div>
|
||||
</div>
|
||||
<div role="row" id="simplegrid-row3" aria-level="2">
|
||||
<div role="gridcell">cell 3,1</div>
|
||||
<div role="gridcell">cell 3,2</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="row" id="simplegrid-ownrow"></div>
|
||||
|
||||
<ul role="tree" id="tree2">
|
||||
<li role="treeitem" id="tree2_ti1">Item 1
|
||||
<ul role="group">
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Math"];
|
||||
|
||||
this.Math = {
|
||||
square: function (x) { return x * x; }
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
Math.jsm
|
||||
math.js
|
||||
data.json
|
||||
invalid.json
|
||||
[browser_sdk_loader_sdk_modules.js]
|
||||
[browser_sdk_loader_sdk_gui_modules.js]
|
||||
[browser_sdk_loader_jsm_modules.js]
|
||||
[browser_sdk_loader_js_modules.js]
|
||||
[browser_sdk_loader_json.js]
|
||||
[browser_sdk_loader_chrome.js]
|
||||
[browser_sdk_loader_chrome_in_sdk.js]
|
|
@ -0,0 +1,24 @@
|
|||
function test () {
|
||||
let loader = makeLoader();
|
||||
let module = Module("./main", gTestPath);
|
||||
let require = Require(loader, module);
|
||||
|
||||
const { Ci, Cc, Cu, components } = require("chrome");
|
||||
|
||||
let { generateUUID } = Cc["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Ci.nsIUUIDGenerator);
|
||||
ok(isUUID(generateUUID()), "chrome.Cc and chrome.Ci works");
|
||||
|
||||
let { ID: parseUUID } = components;
|
||||
let uuidString = "00001111-2222-3333-4444-555566667777";
|
||||
let parsed = parseUUID(uuidString);
|
||||
is(parsed, "{" + uuidString + "}", "chrome.components works");
|
||||
|
||||
const { defer } = Cu.import("resource://gre/modules/Promise.jsm").Promise;
|
||||
let { promise, resolve } = defer();
|
||||
resolve(5);
|
||||
promise.then(val => {
|
||||
is(val, 5, "chrome.Cu works");
|
||||
finish();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
function test () {
|
||||
let loader = makeLoader();
|
||||
let module = Module("./main", gTestPath);
|
||||
let require = Require(loader, module);
|
||||
|
||||
// sdk/util/uuid uses Cc, Ci, components
|
||||
const { uuid } = require("sdk/util/uuid");
|
||||
|
||||
ok(isUUID(uuid()), "chrome.Cc and chrome.Ci works in SDK includes");
|
||||
|
||||
let uuidString = '00001111-2222-3333-4444-555566667777';
|
||||
let parsed = uuid(uuidString);
|
||||
is(parsed, '{' + uuidString + '}', "chrome.components works in SDK includes");
|
||||
|
||||
// sdk/base64 uses Cu
|
||||
const { encode } = require("sdk/base64");
|
||||
is(encode("hello"), "aGVsbG8=", "chrome.Cu works in SDK includes");
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
function test () {
|
||||
let loader = makeLoader();
|
||||
let module = Module("./main", gTestPath);
|
||||
let require = Require(loader, module);
|
||||
|
||||
try {
|
||||
let Model = require("./cant-find-me");
|
||||
ok(false, "requiring a JS module that doesn't exist should throw");
|
||||
}
|
||||
catch (e) {
|
||||
ok(e, "requiring a JS module that doesn't exist should throw");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Relative resource:// URI of JS
|
||||
*/
|
||||
|
||||
let { square } = require("./math");
|
||||
is(square(5), 25, "loads relative URI of JS");
|
||||
|
||||
/*
|
||||
* Absolute resource:// URI of JS
|
||||
*/
|
||||
|
||||
let { has } = require("resource://gre/modules/commonjs/sdk/util/array");
|
||||
let testArray = ['rock', 'paper', 'scissors'];
|
||||
|
||||
ok(has(testArray, 'rock'), "loads absolute resource:// URI of JS");
|
||||
ok(!has(testArray, 'dragon'), "loads absolute resource:// URI of JS");
|
||||
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
function test () {
|
||||
let loader = makeLoader();
|
||||
let module = Module("./main", gTestPath);
|
||||
let require = Require(loader, module);
|
||||
|
||||
try {
|
||||
let Model = require("resource://gre/modules/BlinkTag.jsm");
|
||||
ok(false, "requiring a JS module that doesn't exist should throw");
|
||||
}
|
||||
catch (e) {
|
||||
ok(e, "requiring a JS module that doesn't exist should throw");
|
||||
}
|
||||
|
||||
/*
|
||||
* Relative resource:// URI of JSM
|
||||
*/
|
||||
|
||||
let { square } = require("./Math.jsm").Math;
|
||||
is(square(5), 25, "loads relative URI of JSM");
|
||||
|
||||
/*
|
||||
* Absolute resource:// URI of JSM
|
||||
*/
|
||||
let { defer } = require("resource://gre/modules/Promise.jsm").Promise;
|
||||
let { resolve, promise } = defer();
|
||||
resolve(5);
|
||||
promise.then(val => {
|
||||
is(val, 5, "loads absolute resource:// URI of JSM");
|
||||
}).then(finish);
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
function test () {
|
||||
let loader = makeLoader();
|
||||
let module = Module("./main", gTestPath);
|
||||
let require = Require(loader, module);
|
||||
|
||||
/*
|
||||
* Relative resource:// URI of json
|
||||
*/
|
||||
|
||||
let data = require("./data.json");
|
||||
is(data.title, "jetpack mochitests", "loads relative JSON");
|
||||
is(data.dependencies.underscore, "1.0.0", "loads relative JSON");
|
||||
|
||||
try {
|
||||
let data = require("./invalid.json");
|
||||
ok(false, "parsing an invalid JSON should throw");
|
||||
}
|
||||
catch (e) {
|
||||
ok(e, "parsing an invalid JSON should throw");
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
function test () {
|
||||
// Load `constructor` as global since tabs uses `traits`
|
||||
// that use this module
|
||||
let loader = makeLoader({ globals: constructor });
|
||||
let module = Module("./main", "scratchpad://");
|
||||
let require = Require(loader, module);
|
||||
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
tabs.open({
|
||||
url: "about:blank",
|
||||
onReady: function (tab) {
|
||||
is(tab.url, "about:blank", "correct uri for tab");
|
||||
is(tabs.activeTab, tab, "correctly active tab");
|
||||
tab.close(finish);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
function test () {
|
||||
let loader = makeLoader();
|
||||
let module = Module("./main", "scratchpad://");
|
||||
let require = Require(loader, module);
|
||||
|
||||
let { has } = require("sdk/util/array");
|
||||
let { extend } = require("sdk/util/object");
|
||||
let testArray = [1, 'hello', 2, 'hi'];
|
||||
|
||||
ok(has(testArray, 1), 'loader loading simple array utils');
|
||||
ok(has(testArray, 'hello'), 'loader loading simple array utils');
|
||||
ok(!has(testArray, 4), 'loader loading simple array utils');
|
||||
|
||||
let testObj1 = { strings: 6, prop: [] };
|
||||
let testObj2 = { name: 'Tosin Abasi', strings: 8 };
|
||||
let extended = extend(testObj1, testObj2);
|
||||
|
||||
is(extended.name, 'Tosin Abasi', 'loader loading simple object utils');
|
||||
is(extended.strings, 8, 'loader loading simple object utils');
|
||||
is(extended.prop, testObj1.prop, 'loader loading simple object utils');
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"title": "jetpack mochitests",
|
||||
"dependencies": {
|
||||
"underscore": "1.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* 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/. */
|
||||
|
||||
const { utils: Cu } = Components;
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
const LoaderModule = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}).Loader;
|
||||
const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
let {
|
||||
Loader, main, Module, Require, unload
|
||||
} = LoaderModule;
|
||||
|
||||
let CURRENT_DIR = gTestPath.replace(/\/[^\/]*\.js$/,'/');
|
||||
let loaders = [];
|
||||
|
||||
// All tests are asynchronous.
|
||||
waitForExplicitFinish();
|
||||
|
||||
let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
info("finish() was called, cleaning up...");
|
||||
loaders.forEach(unload);
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
|
||||
});
|
||||
|
||||
function makePaths (root) {
|
||||
return {
|
||||
'./': CURRENT_DIR,
|
||||
'': 'resource://gre/modules/commonjs/'
|
||||
};
|
||||
}
|
||||
|
||||
function makeLoader (options) {
|
||||
let { paths, globals } = options || {};
|
||||
|
||||
// We have to have `console` as a global, otherwise
|
||||
// many SDK modules will fail
|
||||
// bug 961252
|
||||
let globalDefaults = {
|
||||
console: console
|
||||
};
|
||||
|
||||
let loader = Loader({
|
||||
paths: paths || makePaths(),
|
||||
globals: extend({}, globalDefaults, globals) || null,
|
||||
modules: {
|
||||
// Needed because sdk/ modules reference utilities in
|
||||
// `toolkit/loader`, until Bug 961194 is completed
|
||||
'toolkit/loader': LoaderModule
|
||||
},
|
||||
// We need rootURI due to `sdk/self` (or are using native loader)
|
||||
// which overloads with pseudo modules
|
||||
// bug 961245
|
||||
rootURI: CURRENT_DIR,
|
||||
// We also need metadata dummy object
|
||||
// bug 961245
|
||||
metadata: {}
|
||||
});
|
||||
|
||||
loaders.push(loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
function isUUID (string) {
|
||||
return /^\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}$/.test(string);
|
||||
}
|
||||
|
||||
function extend (...objs) {
|
||||
if (objs.length === 0 || objs.length === 1)
|
||||
return objs[0] || {};
|
||||
|
||||
for (let i = objs.length; i > 1; i--) {
|
||||
for (var prop in objs[i - 1])
|
||||
objs[0][prop] = objs[i - 1][prop];
|
||||
}
|
||||
return objs[0];
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
this isnt json
|
|
@ -0,0 +1,7 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
exports.square = function square (x) { return x * x; }
|
|
@ -21,6 +21,16 @@ let wrapper = {
|
|||
iframe: null,
|
||||
|
||||
init: function () {
|
||||
let weave = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
|
||||
// Don't show about:accounts with FxA disabled.
|
||||
if (!weave.fxAccountsEnabled) {
|
||||
document.body.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
let iframe = document.getElementById("remote");
|
||||
this.iframe = iframe;
|
||||
iframe.addEventListener("load", this);
|
||||
|
@ -53,6 +63,11 @@ let wrapper = {
|
|||
onLogin: function (accountData) {
|
||||
log("Received: 'login'. Data:" + JSON.stringify(accountData));
|
||||
|
||||
if (accountData.customizeSync) {
|
||||
Services.prefs.setBoolPref("services.sync.needsCustomization", true);
|
||||
delete accountData.customizeSync;
|
||||
}
|
||||
|
||||
fxAccounts.setSignedInUser(accountData).then(
|
||||
() => {
|
||||
this.injectData("message", { status: "login" });
|
||||
|
@ -133,7 +148,7 @@ let wrapper = {
|
|||
// Button onclick handlers
|
||||
function handleOldSync() {
|
||||
// we just want to navigate the current tab to the new location...
|
||||
window.location = "https://services.mozilla.com/legacysync";
|
||||
window.location = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
|
||||
}
|
||||
|
||||
function getStarted() {
|
||||
|
|
|
@ -9,14 +9,38 @@ XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
|
|||
let gFxAccounts = {
|
||||
|
||||
_initialized: false,
|
||||
_originalLabel: null,
|
||||
_inCustomizationMode: false,
|
||||
|
||||
get weave() {
|
||||
delete this.weave;
|
||||
return this.weave = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
},
|
||||
|
||||
get topics() {
|
||||
delete this.topics;
|
||||
return this.topics = [
|
||||
FxAccountsCommon.ONVERIFIED_NOTIFICATION
|
||||
FxAccountsCommon.ONLOGIN_NOTIFICATION,
|
||||
FxAccountsCommon.ONVERIFIED_NOTIFICATION,
|
||||
FxAccountsCommon.ONLOGOUT_NOTIFICATION
|
||||
];
|
||||
},
|
||||
|
||||
get button() {
|
||||
delete this.button;
|
||||
return this.button = document.getElementById("PanelUI-fxa-status");
|
||||
},
|
||||
|
||||
get syncNeedsCustomization() {
|
||||
try {
|
||||
return Services.prefs.getBoolPref("services.sync.needsCustomization");
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
init: function () {
|
||||
if (this._initialized) {
|
||||
return;
|
||||
|
@ -26,7 +50,15 @@ let gFxAccounts = {
|
|||
Services.obs.addObserver(this, topic, false);
|
||||
}
|
||||
|
||||
gNavToolbox.addEventListener("customizationstarting", this);
|
||||
gNavToolbox.addEventListener("customizationending", this);
|
||||
|
||||
// Save the button's original label so that
|
||||
// we can restore it if overridden later.
|
||||
this._originalLabel = this.button.getAttribute("label");
|
||||
this._initialized = true;
|
||||
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
|
@ -42,11 +74,20 @@ let gFxAccounts = {
|
|||
},
|
||||
|
||||
observe: function (subject, topic) {
|
||||
this.showDoorhanger();
|
||||
if (topic != FxAccountsCommon.ONVERIFIED_NOTIFICATION) {
|
||||
this.updateUI();
|
||||
} else if (!this.syncNeedsCustomization) {
|
||||
this.showSyncStartedDoorhanger();
|
||||
}
|
||||
},
|
||||
|
||||
showDoorhanger: function () {
|
||||
let panel = document.getElementById("sync-popup");
|
||||
handleEvent: function (event) {
|
||||
this._inCustomizationMode = event.type == "customizationstarting";
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
showDoorhanger: function (id) {
|
||||
let panel = document.getElementById(id);
|
||||
let anchor = document.getElementById("PanelUI-menu-button");
|
||||
|
||||
let iconAnchor =
|
||||
|
@ -55,5 +96,62 @@ let gFxAccounts = {
|
|||
|
||||
panel.hidden = false;
|
||||
panel.openPopup(iconAnchor || anchor, "bottomcenter topright");
|
||||
},
|
||||
|
||||
showSyncStartedDoorhanger: function () {
|
||||
this.showDoorhanger("sync-start-panel");
|
||||
},
|
||||
|
||||
showSyncFailedDoorhanger: function () {
|
||||
this.showDoorhanger("sync-error-panel");
|
||||
},
|
||||
|
||||
updateUI: function () {
|
||||
// Bail out if FxA is disabled.
|
||||
if (!this.weave.fxAccountsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FxA is enabled, show the widget.
|
||||
this.button.removeAttribute("hidden");
|
||||
|
||||
// Make sure the button is disabled in customization mode.
|
||||
if (this._inCustomizationMode) {
|
||||
this.button.setAttribute("disabled", "true");
|
||||
} else {
|
||||
this.button.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
// If the user is signed into their Firefox account and we are not
|
||||
// currently in customization mode, show their email address.
|
||||
fxAccounts.getSignedInUser().then(userData => {
|
||||
if (userData && !this._inCustomizationMode) {
|
||||
this.button.setAttribute("signedin", "true");
|
||||
this.button.setAttribute("label", userData.email);
|
||||
this.button.setAttribute("tooltiptext", userData.email);
|
||||
} else {
|
||||
this.button.removeAttribute("signedin");
|
||||
this.button.setAttribute("label", this._originalLabel);
|
||||
this.button.removeAttribute("tooltiptext");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggle: function (event) {
|
||||
if (event.originalTarget.hasAttribute("signedin")) {
|
||||
this.openPreferences();
|
||||
} else {
|
||||
this.openSignInPage();
|
||||
}
|
||||
|
||||
PanelUI.hide();
|
||||
},
|
||||
|
||||
openPreferences: function () {
|
||||
openPreferences("paneSync");
|
||||
},
|
||||
|
||||
openSignInPage: function () {
|
||||
switchToTabHavingURI("about:accounts", true);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -383,13 +383,50 @@
|
|||
</panel>
|
||||
|
||||
<!-- Sync Panel -->
|
||||
<panel id="sync-popup" type="arrow" hidden="true" noautofocus="true"
|
||||
level="top" onclick="this.hidePopup();">
|
||||
<hbox id="sync-popup-outer">
|
||||
<image id="sync-popup-icon"/>
|
||||
<vbox id="sync-popup-inner">
|
||||
<description id="sync-popup-desc-syncing" value="&syncPanel.descriptionSyncing;"/>
|
||||
<description id="sync-popup-desc-prefs">&syncPanel.descriptionPrefs;</description>
|
||||
<panel id="sync-start-panel" class="sync-panel" type="arrow" hidden="true"
|
||||
noautofocus="true" level="top" onclick="this.hidePopup();">
|
||||
<hbox class="sync-panel-outer">
|
||||
<image class="sync-panel-icon"/>
|
||||
<vbox class="sync-panel-inner">
|
||||
<description id="sync-start-panel-title"
|
||||
value="&syncStartPanel.title;"/>
|
||||
<description id="sync-start-panel-subtitle">
|
||||
#ifdef XP_UNIX
|
||||
&syncStartPanel.subTitleUnix;
|
||||
#else
|
||||
&syncStartPanel.subTitle;
|
||||
#endif
|
||||
</description>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</panel>
|
||||
|
||||
<!-- Sync Error Panel -->
|
||||
<panel id="sync-error-panel" class="sync-panel" type="arrow" hidden="true"
|
||||
noautofocus="true" level="top" onclick="this.hidePopup();">
|
||||
<hbox class="sync-panel-outer">
|
||||
<image class="sync-panel-icon"/>
|
||||
<vbox class="sync-panel-inner">
|
||||
<description id="sync-error-panel-title"
|
||||
value="&syncErrorPanel.title;"/>
|
||||
<description id="sync-error-panel-subtitle"
|
||||
value="&syncErrorPanel.subTitle;"/>
|
||||
<hbox class="sync-panel-button-box">
|
||||
<button class="sync-panel-button"
|
||||
#ifdef XP_UNIX
|
||||
label="&syncErrorPanel.prefButtonUnix.label;"
|
||||
accesskey="&syncErrorPanel.prefButtonUnix.accesskey;"
|
||||
#else
|
||||
label="&syncErrorPanel.prefButton.label;"
|
||||
accesskey="&syncErrorPanel.prefButton.accesskey;"
|
||||
#endif
|
||||
onclick="gFxAccounts.openPreferences();"/>
|
||||
<spacer flex="1"/>
|
||||
<button class="sync-panel-button"
|
||||
label="&syncErrorPanel.signInButton.label;"
|
||||
accesskey="&syncErrorPanel.signInButton.accesskey;"
|
||||
onclick="gFxAccounts.openSignInPage();"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</panel>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/* 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/. */
|
||||
|
||||
:root {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
#sync-customize-pane {
|
||||
-moz-padding-start: 74px;
|
||||
background: top left url(chrome://browser/skin/sync-128.png) no-repeat;
|
||||
background-size: 64px;
|
||||
}
|
||||
|
||||
#sync-customize-title {
|
||||
-moz-margin-start: 0;
|
||||
padding-bottom: 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#sync-customize-subtitle {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
checkbox {
|
||||
margin: 0;
|
||||
padding: 0.5em 0 0;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
addEventListener("dialogaccept", function () {
|
||||
window.arguments[0].accepted = true;
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/sync/customize.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE dialog [
|
||||
<!ENTITY % syncCustomizeDTD SYSTEM "chrome://browser/locale/syncCustomize.dtd">
|
||||
%syncCustomizeDTD;
|
||||
]>
|
||||
<dialog id="sync-customize"
|
||||
windowtype="Sync:Customize"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
title="&syncCustomize.dialog.title;"
|
||||
buttonlabelaccept="&syncCustomize.acceptButton.label;"
|
||||
buttons="accept">
|
||||
|
||||
<prefpane id="sync-customize-pane">
|
||||
<preferences>
|
||||
<preference id="engine.bookmarks" name="services.sync.engine.bookmarks" type="bool"/>
|
||||
<preference id="engine.history" name="services.sync.engine.history" type="bool"/>
|
||||
<preference id="engine.tabs" name="services.sync.engine.tabs" type="bool"/>
|
||||
<preference id="engine.passwords" name="services.sync.engine.passwords" type="bool"/>
|
||||
<preference id="engine.addons" name="services.sync.engine.addons" type="bool"/>
|
||||
<preference id="engine.prefs" name="services.sync.engine.prefs" type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<label id="sync-customize-title" value="&syncCustomize.title;"/>
|
||||
<description id="sync-customize-subtitle"
|
||||
#ifdef XP_UNIX
|
||||
value="&syncCustomize.subTitleUnix;"
|
||||
#else
|
||||
value="&syncCustomize.subTitle;"
|
||||
#endif
|
||||
/>
|
||||
|
||||
<checkbox label="&engine.bookmarks.label;"
|
||||
accesskey="&engine.bookmarks.accesskey;"
|
||||
preference="engine.bookmarks"/>
|
||||
<checkbox label="&engine.history.label;"
|
||||
accesskey="&engine.history.accesskey;"
|
||||
preference="engine.history"/>
|
||||
<checkbox label="&engine.tabs.label;"
|
||||
accesskey="&engine.tabs.accesskey;"
|
||||
preference="engine.tabs"/>
|
||||
<checkbox label="&engine.passwords.label;"
|
||||
accesskey="&engine.passwords.accesskey;"
|
||||
preference="engine.passwords"/>
|
||||
<checkbox label="&engine.addons.label;"
|
||||
accesskey="&engine.addons.accesskey;"
|
||||
preference="engine.addons"/>
|
||||
<checkbox label="&engine.prefs.label;"
|
||||
accesskey="&engine.prefs.accesskey;"
|
||||
preference="engine.prefs"/>
|
||||
</prefpane>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/sync/customize.js" />
|
||||
|
||||
</dialog>
|
|
@ -14,6 +14,13 @@ let gSyncUtils = {
|
|||
return this.bundle = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
|
||||
},
|
||||
|
||||
get fxAccountsEnabled() {
|
||||
let service = Components.classes["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
return service.fxAccountsEnabled;
|
||||
},
|
||||
|
||||
// opens in a new window if we're in a modal prefwindow world, in a new tab otherwise
|
||||
_openLink: function (url) {
|
||||
let thisDocEl = document.documentElement,
|
||||
|
@ -71,11 +78,13 @@ let gSyncUtils = {
|
|||
},
|
||||
|
||||
openToS: function () {
|
||||
this._openLink(Weave.Svc.Prefs.get("termsURL"));
|
||||
let root = this.fxAccountsEnabled ? "fxa." : "";
|
||||
this._openLink(Weave.Svc.Prefs.get(root + "termsURL"));
|
||||
},
|
||||
|
||||
openPrivacyPolicy: function () {
|
||||
this._openLink(Weave.Svc.Prefs.get("privacyURL"));
|
||||
let root = this.fxAccountsEnabled ? "fxa." : "";
|
||||
this._openLink(Weave.Svc.Prefs.get(root + "privacyURL"));
|
||||
},
|
||||
|
||||
openFirstSyncProgressPage: function () {
|
||||
|
|
|
@ -9,6 +9,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
|||
|
||||
registerCleanupFunction(function() {
|
||||
// Ensure we don't pollute prefs for next tests.
|
||||
Services.prefs.clearUserPref("identity.fxaccounts.enabled");
|
||||
Services.prefs.clearUserPref("identity.fxaccounts.remote.uri");
|
||||
});
|
||||
|
||||
|
@ -18,6 +19,7 @@ let gTests = [
|
|||
desc: "Test the remote commands",
|
||||
setup: function ()
|
||||
{
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.enabled", true);
|
||||
Services.prefs.setCharPref("identity.fxaccounts.remote.uri",
|
||||
"https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
|
||||
},
|
||||
|
|
|
@ -101,6 +101,9 @@ browser.jar:
|
|||
content/browser/sync/utils.js (content/sync/utils.js)
|
||||
content/browser/sync/progress.js (content/sync/progress.js)
|
||||
content/browser/sync/progress.xhtml (content/sync/progress.xhtml)
|
||||
* content/browser/sync/customize.xul (content/sync/customize.xul)
|
||||
content/browser/sync/customize.js (content/sync/customize.js)
|
||||
content/browser/sync/customize.css (content/sync/customize.css)
|
||||
#endif
|
||||
content/browser/safeMode.css (content/safeMode.css)
|
||||
content/browser/safeMode.js (content/safeMode.js)
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
</vbox>
|
||||
|
||||
<footer id="PanelUI-footer">
|
||||
<!-- The parentNode is used so that the footer is presented as the anchor
|
||||
instead of just the button being the anchor. -->
|
||||
<toolbarbutton id="PanelUI-fxa-status" label="&fxaSignIn.label;"
|
||||
oncommand="gFxAccounts.toggle(event);"
|
||||
hidden="true"/>
|
||||
|
||||
<hbox id="PanelUI-footer-inner">
|
||||
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
|
||||
exitLabel="&appMenuCustomizeExit.label;"
|
||||
oncommand="gCustomizeMode.toggle();"/>
|
||||
|
@ -34,6 +37,7 @@
|
|||
tooltiptext="&quitApplicationCmd.label;"
|
||||
#endif
|
||||
command="cmd_quitApplication"/>
|
||||
</hbox>
|
||||
</footer>
|
||||
</panelview>
|
||||
|
||||
|
|
|
@ -113,9 +113,9 @@
|
|||
<children />
|
||||
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
|
||||
<xul:label class="toolbarbutton-text" crop="right" flex="1"
|
||||
xbl:inherits="value=label,accesskey,crop"/>
|
||||
xbl:inherits="value=label,accesskey,crop,wrap"/>
|
||||
<xul:label class="toolbarbutton-multiline-text" flex="1"
|
||||
xbl:inherits="xbl:text=label,accesskey"/>
|
||||
xbl:inherits="xbl:text=label,accesskey,wrap"/>
|
||||
</content>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
|
|
@ -8,6 +8,17 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
|||
const PAGE_NO_ACCOUNT = 0;
|
||||
const PAGE_HAS_ACCOUNT = 1;
|
||||
const PAGE_NEEDS_UPDATE = 2;
|
||||
const PAGE_PLEASE_WAIT = 3;
|
||||
const FXA_PAGE_LOGGED_OUT = 4;
|
||||
const FXA_PAGE_LOGGED_IN = 5;
|
||||
|
||||
// Indexes into the "login status" deck.
|
||||
// We are in a successful verified state - everything should work!
|
||||
const FXA_LOGIN_VERIFIED = 0;
|
||||
// We have logged in to an unverified account.
|
||||
const FXA_LOGIN_UNVERIFIED = 1;
|
||||
// We are logged in locally, but the server rejected our credentials.
|
||||
const FXA_LOGIN_FAILED = 2;
|
||||
|
||||
let gSyncPane = {
|
||||
_stringBundle: null,
|
||||
|
@ -44,6 +55,10 @@ let gSyncPane = {
|
|||
return;
|
||||
}
|
||||
|
||||
// it may take some time before we can determine what provider to use
|
||||
// and the state of that provider, so show the "please wait" page.
|
||||
this.page = PAGE_PLEASE_WAIT;
|
||||
|
||||
let onUnload = function () {
|
||||
window.removeEventListener("unload", onUnload, false);
|
||||
try {
|
||||
|
@ -88,16 +103,59 @@ let gSyncPane = {
|
|||
},
|
||||
|
||||
updateWeavePrefs: function () {
|
||||
if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
|
||||
Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
|
||||
this.page = PAGE_NO_ACCOUNT;
|
||||
let service = Components.classes["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
// no concept of "pair" in an fxAccounts world.
|
||||
// service.fxAccountsEnabled is false iff sync is already configured for
|
||||
// the legacy provider.
|
||||
if (service.fxAccountsEnabled) {
|
||||
document.getElementById("pairDevice").hidden = true;
|
||||
// determine the fxa status...
|
||||
this.page = PAGE_PLEASE_WAIT;
|
||||
Components.utils.import("resource://gre/modules/FxAccounts.jsm");
|
||||
fxAccounts.getSignedInUser().then(data => {
|
||||
if (!data) {
|
||||
this.page = FXA_PAGE_LOGGED_OUT;
|
||||
return;
|
||||
}
|
||||
this.page = FXA_PAGE_LOGGED_IN;
|
||||
// We are logged in locally, but maybe we are in a state where the
|
||||
// server rejected our credentials (eg, password changed on the server)
|
||||
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
|
||||
let enginesListDisabled;
|
||||
// Not Verfied implies login error state, so check that first.
|
||||
if (!data.verified) {
|
||||
fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED;
|
||||
enginesListDisabled = true;
|
||||
// So we think we are logged in, so login problems are next.
|
||||
} else if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
|
||||
fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
|
||||
enginesListDisabled = true;
|
||||
// Else we must be golden!
|
||||
} else {
|
||||
fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
|
||||
enginesListDisabled = false;
|
||||
}
|
||||
document.getElementById("fxaEmailAddress1").textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress2").textContent = data.email;
|
||||
document.getElementById("fxaEmailAddress3").textContent = data.email;
|
||||
document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName;
|
||||
let enginesList = document.getElementById("fxaSyncEnginesList")
|
||||
enginesList.disabled = enginesListDisabled;
|
||||
// *sigh* - disabling the <richlistbox> draws each item as if it is disabled,
|
||||
// but doesn't disable the checkboxes.
|
||||
for (let checkbox of enginesList.querySelectorAll("checkbox")) {
|
||||
checkbox.disabled = enginesListDisabled;
|
||||
}
|
||||
});
|
||||
// If fxAccountEnabled is false and we are in a "not configured" state,
|
||||
// then fxAccounts is probably fully disabled rather than just unconfigured,
|
||||
// so handle this case. This block can be removed once we remove support
|
||||
// for fxAccounts being disabled.
|
||||
} else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
|
||||
Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
|
||||
this.page = PAGE_NO_ACCOUNT;
|
||||
// else: sync was previously configured for the legacy provider, so we
|
||||
// make the "old" panels available.
|
||||
} else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
|
||||
Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
|
||||
this.needsUpdate();
|
||||
|
@ -163,10 +221,7 @@ let gSyncPane = {
|
|||
.wrappedJSObject;
|
||||
|
||||
if (service.fxAccountsEnabled) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
win.switchToTabHavingURI("about:accounts", true);
|
||||
// seeing as we are doing this in a tab we close the prefs dialog.
|
||||
window.close();
|
||||
this.openContentInBrowser("about:accounts");
|
||||
} else {
|
||||
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
|
||||
if (win) {
|
||||
|
@ -179,6 +234,56 @@ let gSyncPane = {
|
|||
}
|
||||
},
|
||||
|
||||
openContentInBrowser: function(url) {
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
if (!win) {
|
||||
// no window to use, so use _openLink to create a new one. We don't
|
||||
// always use that as it prefers to open a new window rather than use
|
||||
// an existing one.
|
||||
gSyncUtils._openLink(url);
|
||||
return;
|
||||
}
|
||||
win.switchToTabHavingURI(url, true);
|
||||
// seeing as we are doing this in a tab we close the prefs dialog.
|
||||
window.close();
|
||||
},
|
||||
|
||||
reSignIn: function() {
|
||||
this.openContentInBrowser("about:accounts");
|
||||
},
|
||||
|
||||
manageFirefoxAccount: function() {
|
||||
let url = Services.prefs.getCharPref("identity.fxaccounts.settings.uri");
|
||||
this.openContentInBrowser(url);
|
||||
},
|
||||
|
||||
verifyFirefoxAccount: function() {
|
||||
Components.utils.import("resource://gre/modules/FxAccounts.jsm");
|
||||
fxAccounts.resendVerificationEmail().then(() => {
|
||||
fxAccounts.getSignedInUser().then(data => {
|
||||
let sb = this._stringBundle;
|
||||
let title = sb.GetStringFromName("firefoxAccountsVerificationSentTitle");
|
||||
let heading = sb.formatStringFromName("firefoxAccountsVerificationSentHeading",
|
||||
[data.email], 1);
|
||||
let description = sb.GetStringFromName("firefoxAccountVerificationSentDescription");
|
||||
|
||||
Services.prompt.alert(window, title, heading + "\n\n" + description);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
openOldSyncSupportPage: function() {
|
||||
let url = Services.urlFormatter.formatURLPref('app.support.baseURL') + "old-sync"
|
||||
this.openContentInBrowser(url);
|
||||
},
|
||||
|
||||
unlinkFirefoxAccount: function(confirm) {
|
||||
Components.utils.import('resource://gre/modules/FxAccounts.jsm');
|
||||
fxAccounts.signOut().then(() => {
|
||||
this.updateWeavePrefs();
|
||||
});
|
||||
},
|
||||
|
||||
openQuotaDialog: function () {
|
||||
let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
|
||||
if (win) {
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
|
||||
|
||||
<deck id="weavePrefsDeck">
|
||||
|
||||
<!-- These panels are for the "legacy" sync provider -->
|
||||
<vbox id="noAccount" align="center">
|
||||
<spacer flex="1"/>
|
||||
<description id="syncDesc">
|
||||
|
@ -175,6 +177,137 @@
|
|||
onclick="gSyncPane.startOver(true); return false;"
|
||||
value="&unlinkDevice.label;"/>
|
||||
</vbox>
|
||||
|
||||
<!-- These panels are for the Firefox Accounts identity provider -->
|
||||
<vbox id="fxaDeterminingStatus" align="center">
|
||||
<spacer flex="1"/>
|
||||
<p>&determiningStatus.label;</p>
|
||||
<spacer flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<vbox id="noFxaAccount">
|
||||
<description>&welcome.description;</description>
|
||||
<label class="text-link"
|
||||
onclick="gSyncPane.openContentInBrowser('about:accounts?signin=true'); return false;"
|
||||
value="&welcome.startButton.label;"/>
|
||||
<spacer flex="1"/>
|
||||
<label class="text-link"
|
||||
onclick="gSyncPane.openOldSyncSupportPage(); return false;"
|
||||
value="&welcome.useOldSync.label;"/>
|
||||
<spacer flex="10"/>
|
||||
</vbox>
|
||||
|
||||
<vbox id="hasFxaAccount">
|
||||
<groupbox id="fxaGroup">
|
||||
<caption label="&syncBrand.fxa-singular.label;"/>
|
||||
|
||||
<deck id="fxaLoginStatus">
|
||||
|
||||
<!-- logged in and verified and all is good -->
|
||||
<hbox flex="1">
|
||||
<label id="fxaEmailAddress1"/>
|
||||
<label class="text-link"
|
||||
onclick="gSyncPane.manageFirefoxAccount();"
|
||||
value="&manage.label;"/>
|
||||
<spacer flex="1"/>
|
||||
<vbox align="end">
|
||||
<button onclick="gSyncPane.unlinkFirefoxAccount(true);"
|
||||
label="&unlink.label;" />
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<!-- logged in to an unverified account -->
|
||||
<hbox flex="1">
|
||||
<description>
|
||||
&signedInUnverified.beforename.label;
|
||||
<span id="fxaEmailAddress2"></span>
|
||||
&signedInUnverified.aftername.label;
|
||||
</description>
|
||||
<spacer flex="1"/>
|
||||
<vbox align="end">
|
||||
<button onclick="gSyncPane.verifyFirefoxAccount();"
|
||||
caption="&verify.label;"/>
|
||||
<label class="text-link"
|
||||
onclick="/* no warning as account can't have previously synced */ gSyncPane.unlinkFirefoxAccount(false);"
|
||||
value="&forget.label;"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<!-- logged in locally but server rejected credentials -->
|
||||
<hbox flex="1">
|
||||
<description>
|
||||
&signedInLoginFailure.beforename.label;
|
||||
<span id="fxaEmailAddress3"></span>
|
||||
&signedInLoginFailure.aftername.label;
|
||||
</description>
|
||||
<spacer flex="1"/>
|
||||
<vbox align="end">
|
||||
<button onclick="gSyncPane.reSignIn();"
|
||||
label="&signIn.label;"/>
|
||||
<label class="text-link"
|
||||
onclick="gSyncPane.unlinkFirefoxAccount(true);"
|
||||
value="&forget.label;"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</deck>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="syncOptions">
|
||||
<caption label="&syncBrand.fullName.label;"/>
|
||||
<vbox>
|
||||
<label value="&syncMy.label;" />
|
||||
<richlistbox id="fxaSyncEnginesList"
|
||||
orient="vertical"
|
||||
onselect="if (this.selectedCount) this.clearSelection();">
|
||||
<richlistitem>
|
||||
<checkbox label="&engine.addons.label;"
|
||||
accesskey="&engine.addons.accesskey;"
|
||||
preference="engine.addons"/>
|
||||
</richlistitem>
|
||||
<richlistitem>
|
||||
<checkbox label="&engine.bookmarks.label;"
|
||||
accesskey="&engine.bookmarks.accesskey;"
|
||||
preference="engine.bookmarks"/>
|
||||
</richlistitem>
|
||||
<richlistitem>
|
||||
<checkbox label="&engine.passwords.label;"
|
||||
accesskey="&engine.passwords.accesskey;"
|
||||
preference="engine.passwords"/>
|
||||
</richlistitem>
|
||||
<richlistitem>
|
||||
<checkbox label="&engine.prefs.label;"
|
||||
accesskey="&engine.prefs.accesskey;"
|
||||
preference="engine.prefs"/>
|
||||
</richlistitem>
|
||||
<richlistitem>
|
||||
<checkbox label="&engine.history.label;"
|
||||
accesskey="&engine.history.accesskey;"
|
||||
preference="engine.history"/>
|
||||
</richlistitem>
|
||||
<richlistitem>
|
||||
<checkbox label="&engine.tabs.label;"
|
||||
accesskey="&engine.tabs.accesskey;"
|
||||
preference="engine.tabs"/>
|
||||
</richlistitem>
|
||||
</richlistbox>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
<vbox>
|
||||
<label value="&syncDeviceName.label;"
|
||||
accesskey="&syncDeviceName.accesskey;"
|
||||
control="syncComputerName"/>
|
||||
<textbox id="fxaSyncComputerName"
|
||||
onchange="gSyncUtils.changeName(this)"/>
|
||||
</vbox>
|
||||
<hbox id="tosPP" pack="center">
|
||||
<label class="text-link"
|
||||
onclick="event.stopPropagation();gSyncUtils.openToS();"
|
||||
value="&prefs.tosLink.label;"/>
|
||||
<label class="text-link"
|
||||
onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
|
||||
value="&fxaPrivacyNotice.link.label;"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</deck>
|
||||
</prefpane>
|
||||
</overlay>
|
||||
|
|
|
@ -50,7 +50,6 @@ function SelectorSearch(aInspector, aContentDocument, aInputNode) {
|
|||
let options = {
|
||||
panelId: "inspector-searchbox-panel",
|
||||
listBoxId: "searchbox-panel-listbox",
|
||||
fixedWidth: true,
|
||||
autoSelect: true,
|
||||
position: "before_start",
|
||||
direction: "ltr",
|
||||
|
|
|
@ -76,7 +76,6 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
|
|||
|
||||
// Creating the popup to be used to show CSS suggestions.
|
||||
let options = {
|
||||
fixedWidth: true,
|
||||
autoSelect: true,
|
||||
theme: "auto"
|
||||
};
|
||||
|
|
|
@ -25,7 +25,6 @@ loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.j
|
|||
* - theme {String} String related to the theme of the popup.
|
||||
* - autoSelect {Boolean} Boolean to allow the first entry of the popup
|
||||
* panel to be automatically selected when the popup shows.
|
||||
* - fixedWidth {Boolean} Boolean to control dynamic width of the popup.
|
||||
* - direction {String} The direction of the text in the panel. rtl or ltr
|
||||
* - onSelect {String} The select event handler for the richlistbox
|
||||
* - onClick {String} The click event handler for the richlistbox.
|
||||
|
@ -35,7 +34,6 @@ function AutocompletePopup(aDocument, aOptions = {})
|
|||
{
|
||||
this._document = aDocument;
|
||||
|
||||
this.fixedWidth = aOptions.fixedWidth || false;
|
||||
this.autoSelect = aOptions.autoSelect || false;
|
||||
this.position = aOptions.position || "after_start";
|
||||
this.direction = aOptions.direction || "ltr";
|
||||
|
@ -75,7 +73,6 @@ function AutocompletePopup(aDocument, aOptions = {})
|
|||
else {
|
||||
this._document.documentElement.appendChild(this._panel);
|
||||
}
|
||||
this._list = null;
|
||||
}
|
||||
else {
|
||||
this._list = this._panel.firstChild;
|
||||
|
@ -137,14 +134,13 @@ AutocompletePopup.prototype = {
|
|||
*/
|
||||
openPopup: function AP_openPopup(aAnchor, aXOffset = 0, aYOffset = 0)
|
||||
{
|
||||
this.__maxLabelLength = -1;
|
||||
this._updateSize();
|
||||
this._panel.openPopup(aAnchor, this.position, aXOffset, aYOffset);
|
||||
|
||||
if (this.autoSelect) {
|
||||
this.selectFirstItem();
|
||||
}
|
||||
if (!this.fixedWidth) {
|
||||
this._updateSize();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -241,10 +237,8 @@ AutocompletePopup.prototype = {
|
|||
if (this.autoSelect) {
|
||||
this.selectFirstItem();
|
||||
}
|
||||
if (!this.fixedWidth) {
|
||||
this._updateSize();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -260,6 +254,28 @@ AutocompletePopup.prototype = {
|
|||
else {
|
||||
this.selectedIndex = 0;
|
||||
}
|
||||
this._list.ensureIndexIsVisible(this._list.selectedIndex);
|
||||
},
|
||||
|
||||
__maxLabelLength: -1,
|
||||
|
||||
get _maxLabelLength() {
|
||||
if (this.__maxLabelLength != -1) {
|
||||
return this.__maxLabelLength;
|
||||
}
|
||||
|
||||
let max = 0;
|
||||
for (let i = 0; i < this._list.childNodes.length; i++) {
|
||||
let item = this._list.childNodes[i]._autocompleteItem;
|
||||
let str = item.label;
|
||||
if (item.count) {
|
||||
str += (item.count + "");
|
||||
}
|
||||
max = Math.max(str.length, max);
|
||||
}
|
||||
|
||||
this.__maxLabelLength = max;
|
||||
return this.__maxLabelLength;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -272,24 +288,8 @@ AutocompletePopup.prototype = {
|
|||
if (!this._panel) {
|
||||
return;
|
||||
}
|
||||
// Flush the layout so that we get the latest height.
|
||||
this._panel.boxObject.height;
|
||||
let height = {};
|
||||
this._list.scrollBoxObject.getScrolledSize({}, height);
|
||||
// Change the width of the popup only if the scrollbar is visible.
|
||||
if (height.value > this._panel.clientHeight) {
|
||||
this._list.width = this._panel.clientWidth + this._scrollbarWidth;
|
||||
}
|
||||
// Height change is required, otherwise the panel is drawn at an offset
|
||||
// the first time.
|
||||
this._list.height = this._list.clientHeight;
|
||||
// This brings the panel back at right position.
|
||||
this._list.top = 0;
|
||||
// Move the panel to -1,-1 to realign the popup with its anchor node when
|
||||
// decreasing the panel height.
|
||||
this._panel.moveTo(-1, -1);
|
||||
// Changing panel height might make the selected item out of view, so
|
||||
// bring it back to view.
|
||||
|
||||
this._list.style.width = (this._maxLabelLength + 3) +"ch";
|
||||
this._list.ensureIndexIsVisible(this._list.selectedIndex);
|
||||
},
|
||||
|
||||
|
@ -305,16 +305,17 @@ AutocompletePopup.prototype = {
|
|||
this._list.removeChild(this._list.firstChild);
|
||||
}
|
||||
|
||||
if (!this.fixedWidth) {
|
||||
this.__maxLabelLength = -1;
|
||||
|
||||
// Reset the panel and list dimensions. New dimensions are calculated when
|
||||
// a new set of items is added to the autocomplete popup.
|
||||
this._list.width = "";
|
||||
this._list.style.width = "";
|
||||
this._list.height = "";
|
||||
this._panel.width = "";
|
||||
this._panel.height = "";
|
||||
this._panel.top = "";
|
||||
this._panel.left = "";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -496,38 +497,6 @@ AutocompletePopup.prototype = {
|
|||
this._list.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the scrollbar width in the current document.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
get _scrollbarWidth()
|
||||
{
|
||||
if (this.__scrollbarWidth !== null) {
|
||||
return this.__scrollbarWidth;
|
||||
}
|
||||
|
||||
let doc = this._document;
|
||||
if (doc.defaultView.matchMedia("(-moz-overlay-scrollbars)").matches) {
|
||||
// This is for the Mac's floating scrollbar, which actually is drawn over
|
||||
// the content, thus taking no extra width.
|
||||
return (this.__scrollbarWidth = 0);
|
||||
}
|
||||
|
||||
let hbox = doc.createElementNS(XUL_NS, "hbox");
|
||||
hbox.setAttribute("style", "height: 0%; overflow: hidden");
|
||||
|
||||
let scrollbar = doc.createElementNS(XUL_NS, "scrollbar");
|
||||
scrollbar.setAttribute("orient", "vertical");
|
||||
hbox.appendChild(scrollbar);
|
||||
doc.documentElement.appendChild(hbox);
|
||||
|
||||
this.__scrollbarWidth = scrollbar.clientWidth;
|
||||
doc.documentElement.removeChild(hbox);
|
||||
|
||||
return this.__scrollbarWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Manages theme switching for the popup based on the devtools.theme pref.
|
||||
*
|
||||
|
|
|
@ -1089,7 +1089,6 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle)
|
|||
this._prefObserver.on(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
|
||||
|
||||
let options = {
|
||||
fixedWidth: true,
|
||||
autoSelect: true,
|
||||
theme: "auto"
|
||||
};
|
||||
|
|
|
@ -96,8 +96,19 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
|||
<!ENTITY showAllTabsCmd.label "Show All Tabs">
|
||||
<!ENTITY showAllTabsCmd.accesskey "A">
|
||||
|
||||
<!ENTITY syncPanel.descriptionSyncing "&brandShortName; is now syncing.">
|
||||
<!ENTITY syncPanel.descriptionPrefs "You can manage Sync from your browser's Preferences.">
|
||||
<!ENTITY fxaSignIn.label "Sign in to &syncBrand.shortName.label;">
|
||||
<!ENTITY syncStartPanel.title "&brandShortName; is now syncing.">
|
||||
<!ENTITY syncStartPanel.subTitle "You can manage &syncBrand.shortName.label; in Options.">
|
||||
<!ENTITY syncStartPanel.subTitleUnix "You can manage &syncBrand.shortName.label; in Preferences.">
|
||||
<!ENTITY syncErrorPanel.title "Cannot connect to &syncBrand.shortName.label;">
|
||||
<!ENTITY syncErrorPanel.subTitle "Please sign in to resume syncing.">
|
||||
<!ENTITY syncErrorPanel.prefButton.label "Options">
|
||||
<!ENTITY syncErrorPanel.prefButton.accesskey "O">
|
||||
<!ENTITY syncErrorPanel.prefButtonUnix.label "Preferences">
|
||||
<!ENTITY syncErrorPanel.prefButtonUnix.accesskey "P">
|
||||
<!ENTITY syncErrorPanel.signInButton.label "Sign In">
|
||||
<!ENTITY syncErrorPanel.signInButton.accesskey "S">
|
||||
|
||||
|
||||
<!ENTITY fullScreenMinimize.tooltip "Minimize">
|
||||
<!ENTITY fullScreenRestore.tooltip "Restore">
|
||||
|
|
|
@ -135,3 +135,9 @@ syncUnlinkConfirm.label=Unlink
|
|||
featureEnableRequiresRestart=%S must restart to enable this feature.
|
||||
featureDisableRequiresRestart=%S must restart to disable this feature.
|
||||
shouldRestartTitle=Restart %S
|
||||
|
||||
###Preferences::Sync::Firefox Accounts
|
||||
firefoxAccountsVerificationSentTitle=Verification Sent
|
||||
# LOCALIZATION NOTE: %S = user's email address.
|
||||
firefoxAccountsVerificationSentHeading=A verification link has been sent to %S
|
||||
firefoxAccountVerificationSentDescription=Please check your email and click the verification link to begin syncing.
|
||||
|
|
|
@ -45,3 +45,23 @@
|
|||
<!-- Footer stuff -->
|
||||
<!ENTITY prefs.tosLink.label "Terms of Service">
|
||||
<!ENTITY prefs.ppLink.label "Privacy Policy">
|
||||
|
||||
<!-- Firefox Accounts stuff -->
|
||||
<!ENTITY fxaPrivacyNotice.link.label "Privacy Notice">
|
||||
<!ENTITY determiningStatus.label "Determining your Firefox Account status…">
|
||||
<!ENTITY signedInUnverified.beforename.label "">
|
||||
<!ENTITY signedInUnverified.aftername.label "is not verified.">
|
||||
|
||||
<!ENTITY signedInLoginFailure.beforename.label "Please sign in to reconnect">
|
||||
<!ENTITY signedInLoginFailure.aftername.label "">
|
||||
|
||||
<!ENTITY notSignedIn.label "You are not signed in.">
|
||||
<!ENTITY signIn.label "Sign in">
|
||||
<!ENTITY manage.label "Manage">
|
||||
<!ENTITY unlink.label "Unlink This Browser…">
|
||||
<!ENTITY verify.label "Verify Email">
|
||||
<!ENTITY forget.label "Forget this Email">
|
||||
|
||||
<!ENTITY welcome.description "Access your tabs, bookmarks, passwords and more wherever you use &brandShortName;.">
|
||||
<!ENTITY welcome.startButton.label "Sign in or Create an Account">
|
||||
<!ENTITY welcome.useOldSync.label "Using an older version of Sync?">
|
||||
|
|
|
@ -4,3 +4,5 @@
|
|||
|
||||
<!ENTITY syncBrand.shortName.label "Sync">
|
||||
<!ENTITY syncBrand.fullName.label "Firefox Sync">
|
||||
<!ENTITY syncBrand.fxa-singular.label "Firefox Account">
|
||||
<!ENTITY syncBrand.fxa-plural.label "Firefox Accounts">
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<!ENTITY syncCustomize.dialog.title "Sync Selection">
|
||||
<!ENTITY syncCustomize.acceptButton.label "Start">
|
||||
|
||||
<!ENTITY syncCustomize.title "What would you like to sync?">
|
||||
<!ENTITY syncCustomize.subTitle "You can manage this selection in Options.">
|
||||
<!ENTITY syncCustomize.subTitleUnix "You can manage this selection in Preferences.">
|
||||
|
||||
<!--
|
||||
These engine names are the same as in browser/preferences/sync.dtd except
|
||||
for the last two that are marked as being specific to Desktop browsers.
|
||||
-->
|
||||
<!ENTITY engine.bookmarks.label "Bookmarks">
|
||||
<!ENTITY engine.bookmarks.accesskey "m">
|
||||
<!ENTITY engine.history.label "History">
|
||||
<!ENTITY engine.history.accesskey "r">
|
||||
<!ENTITY engine.tabs.label "Tabs">
|
||||
<!ENTITY engine.tabs.accesskey "T">
|
||||
<!ENTITY engine.passwords.label "Passwords">
|
||||
<!ENTITY engine.passwords.accesskey "P">
|
||||
<!ENTITY engine.addons.label "Desktop Add-ons">
|
||||
<!ENTITY engine.addons.accesskey "A">
|
||||
<!ENTITY engine.prefs.label "Desktop Preferences">
|
||||
<!ENTITY engine.prefs.accesskey "S">
|
|
@ -18,6 +18,7 @@
|
|||
locale/browser/aboutSessionRestore.dtd (%chrome/browser/aboutSessionRestore.dtd)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
locale/browser/syncProgress.dtd (%chrome/browser/syncProgress.dtd)
|
||||
locale/browser/syncCustomize.dtd (%chrome/browser/syncCustomize.dtd)
|
||||
locale/browser/aboutSyncTabs.dtd (%chrome/browser/aboutSyncTabs.dtd)
|
||||
#endif
|
||||
locale/browser/browser.dtd (%chrome/browser/browser.dtd)
|
||||
|
|
|
@ -245,7 +245,7 @@ gTests.push({
|
|||
run: function () {
|
||||
let mozTab = yield addTab("about:mozilla");
|
||||
|
||||
// addTab will dismiss navbar, but lets check anyway.
|
||||
yield hideNavBar();
|
||||
ok(!ContextUI.navbarVisible, "navbar dismissed");
|
||||
|
||||
BrowserUI.doCommand("cmd_newTab");
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gSessionStore = Cc["@mozilla.org/browser/sessionstore;1"]
|
||||
.getService(Ci.nsISessionStore);
|
||||
|
||||
function test() {
|
||||
runTests();
|
||||
}
|
||||
|
||||
function getState() {
|
||||
return JSON.parse(gSessionStore.getBrowserState());
|
||||
}
|
||||
|
||||
function getTabData() {
|
||||
return getState().windows[0].tabs;
|
||||
}
|
||||
|
||||
function isValidTabData(aData) {
|
||||
return aData && aData.entries && aData.entries.length &&
|
||||
typeof aData.index == "number";
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "getBrowserState tests",
|
||||
run: function() {
|
||||
// Wait for Session Manager to be initialized.
|
||||
yield waitForCondition(() => window.__SSID);
|
||||
info(window.__SSID);
|
||||
let tabData1 = getTabData();
|
||||
ok(tabData1.every(isValidTabData), "Tab data starts out valid");
|
||||
|
||||
// Open a tab.
|
||||
let tab = Browser.addTab("about:mozilla");
|
||||
let tabData2 = getTabData();
|
||||
is(tabData2.length, tabData1.length, "New tab not added yet.");
|
||||
|
||||
// Wait for the tab's session data to be initialized.
|
||||
yield waitForMessage("Content:SessionHistory", tab.browser.messageManager);
|
||||
yield waitForMs(0);
|
||||
let tabData3 = getTabData();
|
||||
is(tabData3.length, tabData1.length + 1, "New tab added.");
|
||||
ok(tabData3.every(isValidTabData), "Tab data still valid");
|
||||
|
||||
// Close the tab.
|
||||
Browser.closeTab(tab, { forceClose: true } );
|
||||
let tabData4 = getTabData();
|
||||
is(tabData4.length, tabData1.length, "Closed tab removed.");
|
||||
ok(tabData4.every(isValidTabData), "Tab data valid again");
|
||||
}
|
||||
});
|
|
@ -56,6 +56,7 @@ support-files =
|
|||
[browser_private_browsing.js]
|
||||
[browser_prompt.js]
|
||||
[browser_remotetabs.js]
|
||||
[browser_sessionstore.js]
|
||||
[browser_snappedState.js]
|
||||
[browser_tabs.js]
|
||||
[browser_tabs_container.js]
|
||||
|
|
|
@ -338,6 +338,7 @@ SessionStore.prototype = {
|
|||
// Assign it a unique identifier and create its data object
|
||||
aWindow.__SSID = "window" + gUUIDGenerator.generateUUID().toString();
|
||||
this._windows[aWindow.__SSID] = { tabs: [], selected: 0, _closedTabs: [] };
|
||||
this._orderedWindows.push(aWindow.__SSID);
|
||||
|
||||
// Perform additional initialization when the first window is loading
|
||||
if (this._loadState == STATE_STOPPED) {
|
||||
|
@ -348,9 +349,6 @@ SessionStore.prototype = {
|
|||
if (!this.shouldRestore()) {
|
||||
this._clearCache();
|
||||
Services.obs.notifyObservers(null, "sessionstore-windows-restored", "");
|
||||
|
||||
// If nothing is being restored, we only have our single Metro window.
|
||||
this._orderedWindows.push(aWindow.__SSID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -554,15 +552,13 @@ SessionStore.prototype = {
|
|||
|
||||
_getTabData: function(aWindow) {
|
||||
return aWindow.Browser.tabs
|
||||
.filter(tab => !tab.isPrivate)
|
||||
.filter(tab => !tab.isPrivate && tab.browser.__SS_data)
|
||||
.map(tab => {
|
||||
let browser = tab.browser;
|
||||
if (browser.__SS_data) {
|
||||
let tabData = browser.__SS_data;
|
||||
if (browser.__SS_extdata)
|
||||
tabData.extData = browser.__SS_extdata;
|
||||
return tabData;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -801,6 +797,7 @@ SessionStore.prototype = {
|
|||
} catch (ex) { /* currentGroupId is undefined if user has no tab groups */ }
|
||||
|
||||
// Move all window data from sessionstore.js to this._windows.
|
||||
this._orderedWindows = [];
|
||||
for (let i = 0; i < data.windows.length; i++) {
|
||||
let SSID;
|
||||
if (i != windowIndex) {
|
||||
|
|
|
@ -1682,21 +1682,30 @@ toolbarbutton.chevron > .toolbarbutton-icon {
|
|||
|
||||
/* Sync Panel */
|
||||
|
||||
#sync-popup {
|
||||
.sync-panel {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#sync-popup-icon {
|
||||
.sync-panel-icon {
|
||||
width: 32px;
|
||||
background: url("chrome://browser/content/abouthome/sync.png") center center no-repeat;
|
||||
background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
|
||||
}
|
||||
|
||||
#sync-popup-inner {
|
||||
.sync-panel-inner {
|
||||
width: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#sync-popup-desc-prefs {
|
||||
.sync-panel-button-box {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#sync-error-panel-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#sync-start-panel-subtitle,
|
||||
#sync-error-panel-subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -3596,30 +3596,54 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
|||
margin-top: .5em;
|
||||
}
|
||||
|
||||
/* Sync Panel */
|
||||
/* Sync Panels */
|
||||
|
||||
#sync-popup {
|
||||
.sync-panel {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#sync-popup-icon {
|
||||
.sync-panel-icon {
|
||||
width: 32px;
|
||||
background: url("chrome://browser/content/abouthome/sync.png") center center no-repeat;
|
||||
background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
#sync-popup-icon {
|
||||
background: url("chrome://browser/content/abouthome/sync@2x.png") center center no-repeat;
|
||||
.sync-panel-icon {
|
||||
background: url("chrome://browser/content/abouthome/sync@2x.png") top left no-repeat;
|
||||
background-size: 32px 32px;
|
||||
}
|
||||
}
|
||||
|
||||
#sync-popup-inner {
|
||||
.sync-panel-inner {
|
||||
width: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#sync-popup-desc-prefs {
|
||||
.sync-panel-button-box {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.sync-panel-button {
|
||||
@hudButton@
|
||||
margin: 0;
|
||||
min-width: 72px;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
.sync-panel-button:hover:active {
|
||||
@hudButtonPressed@
|
||||
}
|
||||
|
||||
.sync-panel-button:-moz-focusring {
|
||||
@hudButtonFocused@
|
||||
}
|
||||
|
||||
#sync-error-panel-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#sync-start-panel-subtitle,
|
||||
#sync-error-panel-subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
%define menuPanelWidth 22.35em
|
||||
%define exitSubviewGutterWidth 38px
|
||||
%define buttonStateHover :not(:-moz-any([disabled],[checked="true"],[open],:active)):hover
|
||||
%define buttonStateActive :not([disabled]):-moz-any([open],[checked="true"],:hover:active)
|
||||
%define buttonStateHover :not(:-moz-any([disabled],[open],[checked="true"],:active)):-moz-any(:hover,[_moz-menuactive])
|
||||
%define buttonStateActive :not([disabled]):-moz-any([open],[checked="true"],:hover:active,[_moz-menuactive]:active)
|
||||
|
||||
%include ../browser.inc
|
||||
|
||||
|
@ -216,11 +216,16 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
|
||||
#PanelUI-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 -1px 0 rgba(0,0,0,.15);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: 4em;
|
||||
}
|
||||
|
||||
#PanelUI-footer-inner {
|
||||
display: flex;
|
||||
box-shadow: 0 -1px 0 rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
#PanelUI-footer > toolbarseparator {
|
||||
|
@ -234,10 +239,12 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
}
|
||||
|
||||
#PanelUI-help,
|
||||
#PanelUI-fxa-status,
|
||||
#PanelUI-customize,
|
||||
#PanelUI-quit {
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
min-height: 2em;
|
||||
-moz-appearance: none;
|
||||
box-shadow: none;
|
||||
background-image: none;
|
||||
|
@ -248,11 +255,21 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
-moz-box-orient: horizontal;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status {
|
||||
width: calc(@menuPanelWidth@ + 20px);
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status[signedin] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#PanelUI-help,
|
||||
#PanelUI-quit {
|
||||
min-width: 46px;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status > .toolbarbutton-text,
|
||||
#PanelUI-customize > .toolbarbutton-text {
|
||||
text-align: start;
|
||||
}
|
||||
|
@ -267,6 +284,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
-moz-margin-end: 0;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status,
|
||||
#PanelUI-customize {
|
||||
flex: 1;
|
||||
-moz-padding-start: 15px;
|
||||
|
@ -283,6 +301,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
list-style-image: url(chrome://browser/skin/menuPanel-exit.png);
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status,
|
||||
#PanelUI-customize,
|
||||
#PanelUI-help,
|
||||
#PanelUI-quit {
|
||||
|
@ -314,6 +333,12 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
#PanelUI-fxa-status:not([disabled]):hover {
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
border-bottom-color: rgba(0,0,0,0.1);
|
||||
box-shadow: 0 -1px 0 rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
#PanelUI-quit:not([disabled]):hover {
|
||||
background-color: #d94141;
|
||||
outline-color: #c23a3a;
|
||||
|
|
|
@ -2141,21 +2141,30 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
|
|||
|
||||
/* Sync Panel */
|
||||
|
||||
#sync-popup {
|
||||
.sync-panel {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#sync-popup-icon {
|
||||
.sync-panel-icon {
|
||||
width: 32px;
|
||||
background: url("chrome://browser/content/abouthome/sync.png") center center no-repeat;
|
||||
background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
|
||||
}
|
||||
|
||||
#sync-popup-inner {
|
||||
.sync-panel-inner {
|
||||
width: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#sync-popup-desc-prefs {
|
||||
.sync-panel-button-box {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#sync-error-panel-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#sync-start-panel-subtitle,
|
||||
#sync-error-panel-subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,10 +72,17 @@ DEBUGGER_INFO = {
|
|||
},
|
||||
|
||||
# valgrind doesn't explain much about leaks unless you set the
|
||||
# '--leak-check=full' flag.
|
||||
# '--leak-check=full' flag. But there are a lot of objects that are
|
||||
# semi-deliberately leaked, so we set '--show-possibly-lost=no' to avoid
|
||||
# uninteresting output from those objects. We set '--smc-check==all-non-file'
|
||||
# and '--vex-iropt-register-updates=allregs-at-mem-access' so that valgrind
|
||||
# deals properly with JIT'd JavaScript code.
|
||||
"valgrind": {
|
||||
"interactive": False,
|
||||
"args": "--leak-check=full"
|
||||
"args": " ".join(["--leak-check=full",
|
||||
"--show-possibly-lost=no",
|
||||
"--smc-check=all-non-file,"
|
||||
"--vex-iropt-register-updates=allregs-at-mem-access"])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=882703
|
|||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
video = document.createElement("video");
|
||||
|
||||
isnot(video.textTracks, null, "Video should have a list of TextTracks.");
|
||||
|
||||
video.addTextTrack("subtitles", "", "");
|
||||
|
||||
track = video.textTracks[0];
|
||||
|
|
|
@ -8225,10 +8225,20 @@ nsGlobalWindow::EnterModalState()
|
|||
}
|
||||
}
|
||||
|
||||
// Clear the capturing content if it is under topDoc.
|
||||
// Usually the activeESM check above does that, but there are cases when
|
||||
// we don't have activeESM, or it is for different document.
|
||||
nsIDocument* topDoc = topWin->GetExtantDoc();
|
||||
nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
|
||||
if (capturingContent && topDoc &&
|
||||
nsContentUtils::ContentIsCrossDocDescendantOf(capturingContent, topDoc)) {
|
||||
nsIPresShell::SetCapturingContent(nullptr, 0);
|
||||
}
|
||||
|
||||
if (topWin->mModalStateDepth == 0) {
|
||||
NS_ASSERTION(!mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
|
||||
|
||||
mSuspendedDoc = topWin->GetExtantDoc();
|
||||
mSuspendedDoc = topDoc;
|
||||
if (mSuspendedDoc) {
|
||||
mSuspendedDoc->SuppressEventHandling();
|
||||
}
|
||||
|
|
|
@ -682,12 +682,12 @@ bool TabParent::SendRealMouseEvent(WidgetMouseEvent& event)
|
|||
if (mIsDestroyed) {
|
||||
return false;
|
||||
}
|
||||
WidgetMouseEvent e(event);
|
||||
MaybeForwardEventToRenderFrame(event, nullptr, &e);
|
||||
if (!MapEventCoordinatesForChildProcess(&e)) {
|
||||
WidgetMouseEvent outEvent(event);
|
||||
MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
|
||||
if (!MapEventCoordinatesForChildProcess(&outEvent)) {
|
||||
return false;
|
||||
}
|
||||
return PBrowserParent::SendRealMouseEvent(e);
|
||||
return PBrowserParent::SendRealMouseEvent(outEvent);
|
||||
}
|
||||
|
||||
CSSIntPoint TabParent::AdjustTapToChildWidget(const CSSIntPoint& aPoint)
|
||||
|
@ -750,12 +750,12 @@ bool TabParent::SendMouseWheelEvent(WidgetWheelEvent& event)
|
|||
if (mIsDestroyed) {
|
||||
return false;
|
||||
}
|
||||
WidgetWheelEvent e(event);
|
||||
MaybeForwardEventToRenderFrame(event, nullptr, &e);
|
||||
if (!MapEventCoordinatesForChildProcess(&e)) {
|
||||
WidgetWheelEvent outEvent(event);
|
||||
MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
|
||||
if (!MapEventCoordinatesForChildProcess(&outEvent)) {
|
||||
return false;
|
||||
}
|
||||
return PBrowserParent::SendMouseWheelEvent(event);
|
||||
return PBrowserParent::SendMouseWheelEvent(outEvent);
|
||||
}
|
||||
|
||||
bool TabParent::SendRealKeyEvent(WidgetKeyboardEvent& event)
|
||||
|
@ -763,12 +763,12 @@ bool TabParent::SendRealKeyEvent(WidgetKeyboardEvent& event)
|
|||
if (mIsDestroyed) {
|
||||
return false;
|
||||
}
|
||||
WidgetKeyboardEvent e(event);
|
||||
MaybeForwardEventToRenderFrame(event, nullptr, &e);
|
||||
if (!MapEventCoordinatesForChildProcess(&e)) {
|
||||
WidgetKeyboardEvent outEvent(event);
|
||||
MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
|
||||
if (!MapEventCoordinatesForChildProcess(&outEvent)) {
|
||||
return false;
|
||||
}
|
||||
return PBrowserParent::SendRealKeyEvent(e);
|
||||
return PBrowserParent::SendRealKeyEvent(outEvent);
|
||||
}
|
||||
|
||||
bool TabParent::SendRealTouchEvent(WidgetTouchEvent& event)
|
||||
|
@ -797,30 +797,36 @@ bool TabParent::SendRealTouchEvent(WidgetTouchEvent& event)
|
|||
++mEventCaptureDepth;
|
||||
}
|
||||
|
||||
WidgetTouchEvent e(event);
|
||||
// PresShell::HandleEventInternal adds touches on touch end/cancel.
|
||||
// This confuses remote content into thinking that the added touches
|
||||
// are part of the touchend/cancel, when actually they're not.
|
||||
// PresShell::HandleEventInternal adds touches on touch end/cancel. This
|
||||
// confuses remote content and the panning and zooming logic into thinking
|
||||
// that the added touches are part of the touchend/cancel, when actually
|
||||
// they're not.
|
||||
if (event.message == NS_TOUCH_END || event.message == NS_TOUCH_CANCEL) {
|
||||
for (int i = e.touches.Length() - 1; i >= 0; i--) {
|
||||
if (!e.touches[i]->mChanged) {
|
||||
e.touches.RemoveElementAt(i);
|
||||
for (int i = event.touches.Length() - 1; i >= 0; i--) {
|
||||
if (!event.touches[i]->mChanged) {
|
||||
event.touches.RemoveElementAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an out event for remote content that is identical to the event that
|
||||
// we send to the render frame. The out event will be transformed in such a
|
||||
// way that its async transform in the compositor is unapplied. The event that
|
||||
// it is created from does not get mutated.
|
||||
WidgetTouchEvent outEvent(event);
|
||||
|
||||
ScrollableLayerGuid guid;
|
||||
MaybeForwardEventToRenderFrame(event, &guid, &e);
|
||||
MaybeForwardEventToRenderFrame(event, &guid, &outEvent);
|
||||
|
||||
if (mIsDestroyed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MapEventCoordinatesForChildProcess(mChildProcessOffsetAtTouchStart, &e);
|
||||
MapEventCoordinatesForChildProcess(mChildProcessOffsetAtTouchStart, &outEvent);
|
||||
|
||||
return (e.message == NS_TOUCH_MOVE) ?
|
||||
PBrowserParent::SendRealTouchMoveEvent(e, guid) :
|
||||
PBrowserParent::SendRealTouchEvent(e, guid);
|
||||
return (outEvent.message == NS_TOUCH_MOVE) ?
|
||||
PBrowserParent::SendRealTouchMoveEvent(outEvent, guid) :
|
||||
PBrowserParent::SendRealTouchEvent(outEvent, guid);
|
||||
}
|
||||
|
||||
/*static*/ TabParent*
|
||||
|
|
|
@ -579,24 +579,15 @@ Promise::Reject(nsPIDOMWindow* aWindow, JSContext* aCx,
|
|||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Promise::Then(const Optional<nsRefPtr<AnyCallback>>& aResolveCallback,
|
||||
const Optional<nsRefPtr<AnyCallback>>& aRejectCallback)
|
||||
Promise::Then(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback)
|
||||
{
|
||||
nsRefPtr<Promise> promise = new Promise(GetParentObject());
|
||||
|
||||
nsRefPtr<PromiseCallback> resolveCb =
|
||||
PromiseCallback::Factory(promise,
|
||||
aResolveCallback.WasPassed()
|
||||
? aResolveCallback.Value()
|
||||
: nullptr,
|
||||
PromiseCallback::Resolve);
|
||||
PromiseCallback::Factory(promise, aResolveCallback, PromiseCallback::Resolve);
|
||||
|
||||
nsRefPtr<PromiseCallback> rejectCb =
|
||||
PromiseCallback::Factory(promise,
|
||||
aRejectCallback.WasPassed()
|
||||
? aRejectCallback.Value()
|
||||
: nullptr,
|
||||
PromiseCallback::Reject);
|
||||
PromiseCallback::Factory(promise, aRejectCallback, PromiseCallback::Reject);
|
||||
|
||||
AppendCallbacks(resolveCb, rejectCb);
|
||||
|
||||
|
@ -604,9 +595,9 @@ Promise::Then(const Optional<nsRefPtr<AnyCallback>>& aResolveCallback,
|
|||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Promise::Catch(const Optional<nsRefPtr<AnyCallback>>& aRejectCallback)
|
||||
Promise::Catch(AnyCallback* aRejectCallback)
|
||||
{
|
||||
Optional<nsRefPtr<AnyCallback>> resolveCb;
|
||||
nsRefPtr<AnyCallback> resolveCb;
|
||||
return Then(resolveCb, aRejectCallback);
|
||||
}
|
||||
|
||||
|
|
|
@ -83,12 +83,10 @@ public:
|
|||
JS::Handle<JS::Value> aValue, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Then(const Optional<nsRefPtr<AnyCallback>>& aResolveCallback,
|
||||
const Optional<nsRefPtr<AnyCallback>>& aRejectCallback);
|
||||
|
||||
Then(AnyCallback* aResolveCallback, AnyCallback* aRejectCallback);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Catch(const Optional<nsRefPtr<AnyCallback>>& aRejectCallback);
|
||||
Catch(AnyCallback* aRejectCallback);
|
||||
|
||||
// FIXME(nsm): Bug 956197
|
||||
static already_AddRefed<Promise>
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PromiseNativeHandler.h"
|
||||
|
||||
#include "js/OldDebugAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
|
@ -177,8 +179,7 @@ WrapperPromiseCallback::Call(JS::Handle<JS::Value> aValue)
|
|||
// If invoking callback threw an exception, run resolver's reject with the
|
||||
// thrown exception as argument and the synchronous flag set.
|
||||
JS::Rooted<JS::Value> value(cx,
|
||||
mCallback->Call(mNextPromise->GetParentObject(), aValue, rv,
|
||||
CallbackObject::eRethrowExceptions));
|
||||
mCallback->Call(aValue, rv, CallbackObject::eRethrowExceptions));
|
||||
|
||||
rv.WouldReportJSException();
|
||||
|
||||
|
@ -192,6 +193,67 @@ WrapperPromiseCallback::Call(JS::Handle<JS::Value> aValue)
|
|||
return;
|
||||
}
|
||||
|
||||
// If the return value is the same as the promise itself, throw TypeError.
|
||||
if (value.isObject()) {
|
||||
JS::Rooted<JSObject*> valueObj(cx, &value.toObject());
|
||||
Promise* returnedPromise;
|
||||
nsresult r = UNWRAP_OBJECT(Promise, valueObj, returnedPromise);
|
||||
|
||||
if (NS_SUCCEEDED(r) && returnedPromise == mNextPromise) {
|
||||
const char* fileName = nullptr;
|
||||
uint32_t lineNumber = 0;
|
||||
|
||||
// Try to get some information about the callback to report a sane error,
|
||||
// but don't try too hard (only deals with scripted functions).
|
||||
JS::Rooted<JSObject*> unwrapped(cx,
|
||||
js::CheckedUnwrap(mCallback->Callback()));
|
||||
|
||||
if (unwrapped) {
|
||||
JSAutoCompartment ac(cx, unwrapped);
|
||||
if (JS_ObjectIsFunction(cx, unwrapped)) {
|
||||
JS::Rooted<JS::Value> asValue(cx, JS::ObjectValue(*unwrapped));
|
||||
JS::Rooted<JSFunction*> func(cx, JS_ValueToFunction(cx, asValue));
|
||||
|
||||
MOZ_ASSERT(func);
|
||||
JSScript* script = JS_GetFunctionScript(cx, func);
|
||||
if (script) {
|
||||
fileName = JS_GetScriptFilename(cx, script);
|
||||
lineNumber = JS_GetScriptBaseLineNumber(cx, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We're back in aValue's compartment here.
|
||||
JS::Rooted<JSString*> stack(cx, JS_GetEmptyString(JS_GetRuntime(cx)));
|
||||
JS::Rooted<JSString*> fn(cx, JS_NewStringCopyZ(cx, fileName));
|
||||
if (!fn) {
|
||||
// Out of memory. Promise will stay unresolved.
|
||||
JS_ClearPendingException(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSString*> message(cx,
|
||||
JS_NewStringCopyZ(cx,
|
||||
"then() cannot return same Promise that it resolves."));
|
||||
if (!message) {
|
||||
// Out of memory. Promise will stay unresolved.
|
||||
JS_ClearPendingException(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> typeError(cx);
|
||||
if (!JS::CreateTypeError(cx, stack, fn, lineNumber, 0,
|
||||
nullptr, message, &typeError)) {
|
||||
// Out of memory. Promise will stay unresolved.
|
||||
JS_ClearPendingException(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
mNextPromise->RejectInternal(cx, typeError, Promise::SyncTask);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, run resolver's resolve with value and the synchronous flag
|
||||
// set.
|
||||
Maybe<JSAutoCompartment> ac2;
|
||||
|
|
|
@ -564,6 +564,15 @@ function promiseWithThenReplaced() {
|
|||
});
|
||||
}
|
||||
|
||||
function promiseStrictHandlers() {
|
||||
var promise = Promise.resolve(5);
|
||||
promise.then(function() {
|
||||
"use strict";
|
||||
ok(this === undefined, "Strict mode callback should have this === undefined.");
|
||||
runTest();
|
||||
});
|
||||
}
|
||||
|
||||
var tests = [ promiseResolve, promiseReject,
|
||||
promiseException, promiseGC, promiseAsync,
|
||||
promiseDoubleThen, promiseThenException,
|
||||
|
@ -589,6 +598,7 @@ var tests = [ promiseResolve, promiseReject,
|
|||
promiseThenableThrowsAfterCallback,
|
||||
promiseThenableRejectThenResolve,
|
||||
promiseWithThenReplaced,
|
||||
promiseStrictHandlers,
|
||||
];
|
||||
|
||||
function runTest() {
|
||||
|
|
|
@ -174,7 +174,7 @@ function promiseCastArray() {
|
|||
}
|
||||
|
||||
function promiseCastThenable() {
|
||||
var p = Promise.cast({ then: function(resolve) { resolve(2); } });
|
||||
var p = Promise.cast({ then: function(onFulfill, onReject) { onFulfill(2); } });
|
||||
ok(p instanceof Promise, "Should cast to a Promise.");
|
||||
p.then(function(v) {
|
||||
is(v, 2, "Should resolve to 2.");
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
// have different types for "platform-provided function" and "user-provided
|
||||
// function"; for now, we just use "object".
|
||||
callback PromiseInit = void (object resolve, object reject);
|
||||
|
||||
[TreatNonCallableAsNull]
|
||||
callback AnyCallback = any (any value);
|
||||
|
||||
[Func="mozilla::dom::Promise::EnabledForScope", Constructor(PromiseInit init)]
|
||||
|
@ -27,12 +29,14 @@ interface Promise {
|
|||
[NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"]
|
||||
static Promise reject(optional any value);
|
||||
|
||||
// The [TreatNonCallableAsNull] annotation is required since then() should do
|
||||
// nothing instead of throwing errors when non-callable arguments are passed.
|
||||
[NewObject]
|
||||
Promise then(optional AnyCallback? fulfillCallback,
|
||||
optional AnyCallback? rejectCallback);
|
||||
Promise then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
|
||||
[TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
|
||||
|
||||
[NewObject]
|
||||
Promise catch(optional AnyCallback? rejectCallback);
|
||||
Promise catch([TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
|
||||
|
||||
[NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"]
|
||||
static Promise all(sequence<any> iterable);
|
||||
|
|
|
@ -1639,6 +1639,7 @@ XMLHttpRequest::MaybePin(ErrorResult& aRv)
|
|||
JSContext* cx = GetCurrentThreadJSContext();
|
||||
|
||||
if (!mWorkerPrivate->AddFeature(cx, this)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -853,17 +853,8 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
|
|||
|
||||
nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) {
|
||||
APZC_LOG("%p got a scale-end in state %d\n", this, mState);
|
||||
// When a pinch ends, it might either turn into a pan (if only one finger
|
||||
// was lifted) or not (if both fingers were lifted). GestureEventListener
|
||||
// sets mCurrentSpan to a negative value in the latter case, and sets
|
||||
// mFocusPoint to the remaining touch point in the former case.
|
||||
if (aEvent.mCurrentSpan >= 0) {
|
||||
SetState(PANNING);
|
||||
mX.StartTouch(aEvent.mFocusPoint.x);
|
||||
mY.StartTouch(aEvent.mFocusPoint.y);
|
||||
} else {
|
||||
|
||||
SetState(NOTHING);
|
||||
}
|
||||
|
||||
{
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
|
|
|
@ -267,25 +267,24 @@ nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInpu
|
|||
} else if (mState == GESTURE_PINCH) {
|
||||
PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
|
||||
aEvent.mTime,
|
||||
ScreenPoint(), // may change below
|
||||
1.0f, // may change below
|
||||
1.0f, // may change below
|
||||
ScreenPoint(),
|
||||
1.0f,
|
||||
1.0f,
|
||||
aEvent.modifiers);
|
||||
|
||||
if (mTouches.Length() > 0) {
|
||||
// Pinch is changing to pan. APZC will start a pan at mFocusPoint
|
||||
// (which isn't really a focus point in this case...).
|
||||
pinchEvent.mFocusPoint = mTouches[0].mScreenPoint;
|
||||
} else {
|
||||
// Pinch is ending, no pan to follow. APZC will check for the spans
|
||||
// being negative.
|
||||
pinchEvent.mCurrentSpan = pinchEvent.mPreviousSpan = -1.0f;
|
||||
}
|
||||
|
||||
mAsyncPanZoomController->HandleInputEvent(pinchEvent);
|
||||
|
||||
mState = GESTURE_NONE;
|
||||
|
||||
// If the user left a finger on the screen, spoof a touch start event and
|
||||
// send it to APZC so that they can continue panning from that point.
|
||||
if (mTouches.Length() == 1) {
|
||||
MultiTouchInput touchEvent(MultiTouchInput::MULTITOUCH_START,
|
||||
aEvent.mTime,
|
||||
aEvent.modifiers);
|
||||
touchEvent.mTouches.AppendElement(mTouches[0]);
|
||||
mAsyncPanZoomController->HandleInputEvent(touchEvent);
|
||||
}
|
||||
|
||||
rv = nsEventStatus_eConsumeNoDefault;
|
||||
} else if (mState == GESTURE_WAITING_PINCH) {
|
||||
mState = GESTURE_NONE;
|
||||
|
|
|
@ -2169,7 +2169,7 @@ Assembler::as_vcvtFixed(VFPRegister vd, bool isSigned, uint32_t fixedPoint, bool
|
|||
int32_t imm5 = fixedPoint;
|
||||
imm5 = (sx ? 32 : 16) - imm5;
|
||||
JS_ASSERT(imm5 >= 0);
|
||||
imm5 = imm5 >> 1 | (imm5 & 1) << 6;
|
||||
imm5 = imm5 >> 1 | (imm5 & 1) << 5;
|
||||
return writeVFPInst(sf, 0x02BA0040 | VD(vd) | toFixed << 18 | sx << 7 |
|
||||
(!isSigned) << 16 | imm5 | c);
|
||||
}
|
||||
|
|
|
@ -2183,8 +2183,17 @@ public:
|
|||
virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
|
||||
nsDisplayItem* aItem) {
|
||||
|
||||
// If the display item is for a frame that is absolutely positioned, it
|
||||
// should only scroll with the scrolled content if its frame its contained
|
||||
// within the scrolled content's frame.
|
||||
bool shouldWrap = !aItem->Frame()->IsAbsolutelyPositioned() ||
|
||||
nsLayoutUtils::IsProperAncestorFrame(mScrolledFrame, aItem->Frame(), nullptr);
|
||||
if (shouldWrap) {
|
||||
SetCount(++mCount);
|
||||
return new (aBuilder) nsDisplayScrollLayer(aBuilder, aItem, aItem->Frame(), mScrolledFrame, mScrollFrame);
|
||||
} else {
|
||||
return aItem;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
|
|
@ -270,6 +270,7 @@ public:
|
|||
bool IsLTR() const;
|
||||
bool IsScrollbarOnRight() const;
|
||||
bool IsScrollingActive() const { return mScrollingActive || mShouldBuildScrollableLayer; }
|
||||
bool IsProcessingAsyncScroll() const { return mAsyncScroll != nullptr; }
|
||||
void ResetScrollPositionForLayerPixelAlignment()
|
||||
{
|
||||
mScrollPosForLayerPixelAlignment = GetScrollPosition();
|
||||
|
@ -636,6 +637,9 @@ public:
|
|||
virtual bool IsScrollingActive() MOZ_OVERRIDE {
|
||||
return mHelper.IsScrollingActive();
|
||||
}
|
||||
virtual bool IsProcessingAsyncScroll() MOZ_OVERRIDE {
|
||||
return mHelper.IsProcessingAsyncScroll();
|
||||
}
|
||||
virtual void ResetScrollPositionForLayerPixelAlignment() MOZ_OVERRIDE {
|
||||
mHelper.ResetScrollPositionForLayerPixelAlignment();
|
||||
}
|
||||
|
@ -934,6 +938,9 @@ public:
|
|||
virtual bool IsScrollingActive() MOZ_OVERRIDE {
|
||||
return mHelper.IsScrollingActive();
|
||||
}
|
||||
virtual bool IsProcessingAsyncScroll() MOZ_OVERRIDE {
|
||||
return mHelper.IsProcessingAsyncScroll();
|
||||
}
|
||||
virtual void ResetScrollPositionForLayerPixelAlignment() MOZ_OVERRIDE {
|
||||
mHelper.ResetScrollPositionForLayerPixelAlignment();
|
||||
}
|
||||
|
|
|
@ -250,6 +250,11 @@ public:
|
|||
* expectation that scrolling is going to happen.
|
||||
*/
|
||||
virtual bool IsScrollingActive() = 0;
|
||||
/**
|
||||
* Returns true if the scrollframe is currently processing an async
|
||||
* or smooth scroll.
|
||||
*/
|
||||
virtual bool IsProcessingAsyncScroll() = 0;
|
||||
/**
|
||||
* Call this when the layer(s) induced by active scrolling are being
|
||||
* completely redrawn.
|
||||
|
|
|
@ -301,7 +301,7 @@
|
|||
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
|
||||
|
||||
<provider android:name="org.mozilla.gecko.db.HomeProvider"
|
||||
android:authorities="@ANDROID_PACKAGE_NAME@.db.homelists"
|
||||
android:authorities="@ANDROID_PACKAGE_NAME@.db.home"
|
||||
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
|
||||
|
||||
<service
|
||||
|
|
|
@ -1362,15 +1362,15 @@ abstract public class BrowserApp extends GeckoApp
|
|||
}
|
||||
|
||||
final Tabs tabs = Tabs.getInstance();
|
||||
final int tabId = tabs.getTabIdForUrl(url, tabs.getSelectedTab().isPrivate());
|
||||
if (tabId < 0) {
|
||||
final Tab tab = tabs.getFirstTabForUrl(url, tabs.getSelectedTab().isPrivate());
|
||||
if (tab == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the target tab to null so it does not get selected (on editing
|
||||
// mode exit) in lieu of the tab we are about to select.
|
||||
mTargetTabForEditingMode = null;
|
||||
Tabs.getInstance().selectTab(tabId);
|
||||
tabs.selectTab(tab.getId());
|
||||
|
||||
mBrowserToolbar.cancelEdit();
|
||||
|
||||
|
|
|
@ -393,7 +393,7 @@ public class GeckoAppShell
|
|||
* The Gecko-side API: API methods that Gecko calls
|
||||
*/
|
||||
|
||||
@WrapElementForJNI(generateStatic = true, noThrow = true)
|
||||
@WrapElementForJNI(allowMultithread = true, generateStatic = true, noThrow = true)
|
||||
public static void handleUncaughtException(Thread thread, Throwable e) {
|
||||
if (thread == null) {
|
||||
thread = Thread.currentThread();
|
||||
|
|
|
@ -163,7 +163,7 @@ public class Tabs implements GeckoEventListener {
|
|||
return count;
|
||||
}
|
||||
|
||||
public synchronized int isOpen(String url) {
|
||||
public int isOpen(String url) {
|
||||
for (Tab tab : mOrder) {
|
||||
if (tab.getURL().equals(url)) {
|
||||
return tab.getId();
|
||||
|
@ -645,32 +645,44 @@ public class Tabs implements GeckoEventListener {
|
|||
/**
|
||||
* Looks for an open tab with the given URL.
|
||||
* @param url the URL of the tab we're looking for
|
||||
*
|
||||
* @return first Tab with the given URL, or null if there is no such tab.
|
||||
*/
|
||||
public Tab getFirstTabForUrl(String url) {
|
||||
return getFirstTabForUrlHelper(url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for an open tab with the given URL and private state.
|
||||
* @param url the URL of the tab we're looking for
|
||||
* @param isPrivate if true, only look for tabs that are private. if false,
|
||||
* only look for tabs that are non-private.
|
||||
*
|
||||
* @return id of an open tab with the given URL; -1 if the tab doesn't exist.
|
||||
* @return first Tab with the given URL, or null if there is no such tab.
|
||||
*/
|
||||
public int getTabIdForUrl(String url, boolean isPrivate) {
|
||||
public Tab getFirstTabForUrl(String url, boolean isPrivate) {
|
||||
return getFirstTabForUrlHelper(url, isPrivate);
|
||||
}
|
||||
|
||||
private Tab getFirstTabForUrlHelper(String url, Boolean isPrivate) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Tab tab : mOrder) {
|
||||
if (isPrivate != null && isPrivate != tab.isPrivate()) {
|
||||
continue;
|
||||
}
|
||||
String tabUrl = tab.getURL();
|
||||
if (AboutPages.isAboutReader(tabUrl)) {
|
||||
tabUrl = ReaderModeUtils.getUrlFromAboutReader(tabUrl);
|
||||
}
|
||||
if (TextUtils.equals(tabUrl, url) && isPrivate == tab.isPrivate()) {
|
||||
return tab.getId();
|
||||
if (url.equals(tabUrl)) {
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getTabIdForUrl(String url) {
|
||||
return getTabIdForUrl(url, Tabs.getInstance().getSelectedTab().isPrivate());
|
||||
}
|
||||
|
||||
public synchronized Tab getTabForUrl(String url) {
|
||||
int tabId = getTabIdForUrl(url);
|
||||
return getTab(tabId);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -239,7 +239,7 @@ public class Favicons {
|
|||
// Attempt to determine the Favicon URL from the Tabs datastructure. Can dodge having to use
|
||||
// the database sometimes by doing this.
|
||||
String targetURL;
|
||||
Tab theTab = Tabs.getInstance().getTabForUrl(pageURL);
|
||||
Tab theTab = Tabs.getInstance().getFirstTabForUrl(pageURL);
|
||||
if (theTab != null) {
|
||||
targetURL = theTab.getFaviconURL();
|
||||
if (targetURL != null) {
|
||||
|
|
|
@ -219,16 +219,11 @@ public class DynamicPanel extends HomeFragment {
|
|||
public Cursor loadCursor() {
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
|
||||
// XXX: Use the test URI for static fake data
|
||||
final Uri fakeItemsUri = HomeItems.CONTENT_FAKE_URI.buildUpon().
|
||||
appendQueryParameter(BrowserContract.PARAM_PROFILE, "default").build();
|
||||
|
||||
final String selection = HomeItems.DATASET_ID + " = ?";
|
||||
final String[] selectionArgs = new String[] { mDatasetId };
|
||||
|
||||
Log.i(LOGTAG, "Loading fake data for list provider: " + mDatasetId);
|
||||
|
||||
return cr.query(fakeItemsUri, null, selection, selectionArgs, null);
|
||||
// XXX: You can use CONTENT_FAKE_URI for development to pull items from fake_home_items.json.
|
||||
return cr.query(HomeItems.CONTENT_URI, null, selection, selectionArgs, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -120,8 +120,8 @@ public class TwoLinePageRow extends TwoLineRow
|
|||
*/
|
||||
private void updateDisplayedUrl() {
|
||||
boolean isPrivate = Tabs.getInstance().getSelectedTab().isPrivate();
|
||||
int tabId = Tabs.getInstance().getTabIdForUrl(mPageUrl, isPrivate);
|
||||
if (!mShowIcons || tabId < 0) {
|
||||
Tab tab = Tabs.getInstance().getFirstTabForUrl(mPageUrl, isPrivate);
|
||||
if (!mShowIcons || tab == null) {
|
||||
setSecondaryText(mPageUrl);
|
||||
setSecondaryIcon(NO_ICON);
|
||||
} else {
|
||||
|
|
|
@ -638,7 +638,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
|||
}
|
||||
|
||||
// Set TabCounter based on visibility
|
||||
if (isVisible() && ViewHelper.getAlpha(mTabsCounter) != 0) {
|
||||
if (isVisible() && ViewHelper.getAlpha(mTabsCounter) != 0 && !isEditing()) {
|
||||
mTabsCounter.setCountWithAnimation(count);
|
||||
} else {
|
||||
mTabsCounter.setCount(count);
|
||||
|
@ -863,6 +863,10 @@ public class BrowserToolbar extends GeckoRelativeLayout
|
|||
// in setButtonEnabled().
|
||||
final float alpha = (enabled ? 1.0f : 0.24f);
|
||||
|
||||
if (!enabled) {
|
||||
mTabsCounter.onEnterEditingMode();
|
||||
}
|
||||
|
||||
mTabs.setEnabled(enabled);
|
||||
ViewHelper.setAlpha(mTabsCounter, alpha);
|
||||
mMenu.setEnabled(enabled);
|
||||
|
|
|
@ -102,6 +102,16 @@ public class TabCounter extends GeckoTextSwitcher
|
|||
mCount = count;
|
||||
}
|
||||
|
||||
// Alpha animations in editing mode cause action bar corruption on the
|
||||
// Nexus 7 (bug 961749). As a workaround, skip these animations in editing
|
||||
// mode.
|
||||
void onEnterEditingMode() {
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
getChildAt(i).clearAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private AnimationSet createAnimation(float startAngle, float endAngle,
|
||||
FadeMode fadeMode,
|
||||
float zEnd, boolean reverse) {
|
||||
|
|
|
@ -30,11 +30,6 @@ import android.text.TextUtils;
|
|||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
/**
|
||||
* Mozilla: Extra imports.
|
||||
*/
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
/**
|
||||
* Mozilla: Unused import.
|
||||
*/
|
||||
|
@ -262,11 +257,6 @@ public class ActivityChooserModel extends DataSetObservable {
|
|||
*/
|
||||
//private final PackageMonitor mPackageMonitor = new DataModelPackageMonitor();
|
||||
|
||||
/**
|
||||
* Mozilla: Count to monitor added and removed packages.
|
||||
*/
|
||||
private int mApplicationsCount;
|
||||
|
||||
/**
|
||||
* Context for accessing resources.
|
||||
*/
|
||||
|
@ -742,15 +732,6 @@ public class ActivityChooserModel extends DataSetObservable {
|
|||
* @return Whether loading was performed.
|
||||
*/
|
||||
private boolean loadActivitiesIfNeeded() {
|
||||
/**
|
||||
* Mozilla: Hack to find change in the installed/uninstalled applications.
|
||||
*/
|
||||
List<ApplicationInfo> applications = mContext.getPackageManager().getInstalledApplications(0);
|
||||
if (applications != null && applications.size() != mApplicationsCount) {
|
||||
mApplicationsCount = applications.size();
|
||||
mReloadActivities = true;
|
||||
}
|
||||
|
||||
if (mReloadActivities && mIntent != null) {
|
||||
mReloadActivities = false;
|
||||
mActivities.clear();
|
||||
|
|
|
@ -560,6 +560,23 @@ var SelectionHandler = {
|
|||
this.startSelection(aElement, { mode : this.SELECT_ALL });
|
||||
},
|
||||
|
||||
/*
|
||||
* Helper function for moving the selection inside an editable element.
|
||||
*
|
||||
* @param aAnchorX the stationary handle's x-coordinate in client coordinates
|
||||
* @param aX the moved handle's x-coordinate in client coordinates
|
||||
* @param aCaretPos the current position of the caret
|
||||
*/
|
||||
_moveSelectionInEditable: function sh_moveSelectionInEditable(aAnchorX, aX, aCaretPos) {
|
||||
let anchorOffset = aX < aAnchorX ? this._targetElement.selectionEnd
|
||||
: this._targetElement.selectionStart;
|
||||
let newOffset = aCaretPos.offset;
|
||||
let [start, end] = anchorOffset <= newOffset ?
|
||||
[anchorOffset, newOffset] :
|
||||
[newOffset, anchorOffset];
|
||||
this._targetElement.setSelectionRange(start, end);
|
||||
},
|
||||
|
||||
/*
|
||||
* Moves the selection as the user drags a selection handle.
|
||||
*
|
||||
|
@ -597,8 +614,8 @@ var SelectionHandler = {
|
|||
// are reversed, so we need to reverse the logic to extend the selection.
|
||||
if ((aIsStartHandle && !this._isRTL) || (!aIsStartHandle && this._isRTL)) {
|
||||
if (targetIsEditable) {
|
||||
// XXX This will just collapse the selection if the start handle goes past the end handle.
|
||||
this._targetElement.selectionStart = caretPos.offset;
|
||||
let anchorX = this._isRTL ? this._cache.start.x : this._cache.end.x;
|
||||
this._moveSelectionInEditable(anchorX, aX, caretPos);
|
||||
} else {
|
||||
let focusNode = selection.focusNode;
|
||||
let focusOffset = selection.focusOffset;
|
||||
|
@ -607,8 +624,8 @@ var SelectionHandler = {
|
|||
}
|
||||
} else {
|
||||
if (targetIsEditable) {
|
||||
// XXX This will just collapse the selection if the end handle goes past the start handle.
|
||||
this._targetElement.selectionEnd = caretPos.offset;
|
||||
let anchorX = this._isRTL ? this._cache.end.x : this._cache.start.x;
|
||||
this._moveSelectionInEditable(anchorX, aX, caretPos);
|
||||
} else {
|
||||
selection.extend(caretPos.offsetNode, caretPos.offset);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ function dump(a) {
|
|||
|
||||
const STATE_STOPPED = 0;
|
||||
const STATE_RUNNING = 1;
|
||||
const STATE_QUITTING = -1;
|
||||
|
||||
function SessionStore() { }
|
||||
|
||||
|
@ -76,11 +75,7 @@ SessionStore.prototype = {
|
|||
observerService.addObserver(this, "final-ui-startup", true);
|
||||
observerService.addObserver(this, "domwindowopened", true);
|
||||
observerService.addObserver(this, "domwindowclosed", true);
|
||||
observerService.addObserver(this, "browser-lastwindow-close-granted", true);
|
||||
observerService.addObserver(this, "browser:purge-session-history", true);
|
||||
observerService.addObserver(this, "quit-application-requested", true);
|
||||
observerService.addObserver(this, "quit-application-granted", true);
|
||||
observerService.addObserver(this, "quit-application", true);
|
||||
observerService.addObserver(this, "Session:Restore", true);
|
||||
break;
|
||||
case "final-ui-startup":
|
||||
|
@ -98,60 +93,9 @@ SessionStore.prototype = {
|
|||
case "domwindowclosed": // catch closed windows
|
||||
this.onWindowClose(aSubject);
|
||||
break;
|
||||
case "browser-lastwindow-close-granted":
|
||||
// If a save has been queued, kill the timer and save state now
|
||||
if (this._saveTimer) {
|
||||
this._saveTimer.cancel();
|
||||
this._saveTimer = null;
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
// Freeze the data at what we've got (ignoring closing windows)
|
||||
this._loadState = STATE_QUITTING;
|
||||
break;
|
||||
case "quit-application-requested":
|
||||
// Get a current snapshot of all windows
|
||||
this._forEachBrowserWindow(function(aWindow) {
|
||||
self._collectWindowData(aWindow);
|
||||
});
|
||||
break;
|
||||
case "quit-application-granted":
|
||||
// Get a current snapshot of all windows
|
||||
this._forEachBrowserWindow(function(aWindow) {
|
||||
self._collectWindowData(aWindow);
|
||||
});
|
||||
|
||||
// Freeze the data at what we've got (ignoring closing windows)
|
||||
this._loadState = STATE_QUITTING;
|
||||
break;
|
||||
case "quit-application":
|
||||
// Freeze the data at what we've got (ignoring closing windows)
|
||||
this._loadState = STATE_QUITTING;
|
||||
|
||||
observerService.removeObserver(this, "domwindowopened");
|
||||
observerService.removeObserver(this, "domwindowclosed");
|
||||
observerService.removeObserver(this, "browser-lastwindow-close-granted");
|
||||
observerService.removeObserver(this, "quit-application-requested");
|
||||
observerService.removeObserver(this, "quit-application-granted");
|
||||
observerService.removeObserver(this, "quit-application");
|
||||
observerService.removeObserver(this, "Session:Restore");
|
||||
|
||||
// If a save has been queued, kill the timer and save state now
|
||||
if (this._saveTimer) {
|
||||
this._saveTimer.cancel();
|
||||
this._saveTimer = null;
|
||||
this.saveState();
|
||||
}
|
||||
break;
|
||||
case "browser:purge-session-history": // catch sanitization
|
||||
this._clearDisk();
|
||||
|
||||
// If the browser is shutting down, simply return after clearing the
|
||||
// session data on disk as this notification fires after the
|
||||
// quit-application notification so the browser is about to exit.
|
||||
if (this._loadState == STATE_QUITTING)
|
||||
return;
|
||||
|
||||
// Clear all data about closed tabs
|
||||
for (let [ssid, win] in Iterator(this._windows))
|
||||
win.closedTabs = [];
|
||||
|
@ -237,7 +181,7 @@ SessionStore.prototype = {
|
|||
return;
|
||||
|
||||
// Ignore non-browser windows and windows opened while shutting down
|
||||
if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || this._loadState == STATE_QUITTING)
|
||||
if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser")
|
||||
return;
|
||||
|
||||
// Assign it a unique identifier (timestamp) and create its data object
|
||||
|
|
|
@ -217,7 +217,7 @@ let HomePanels = {
|
|||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Remove",
|
||||
id: panel.id
|
||||
id: id
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ const DB_PATH = OS.Path.join(OS.Constants.Path.profileDir, "home.sqlite");
|
|||
const SQL = {
|
||||
createItemsTable:
|
||||
"CREATE TABLE items (" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
"dataset_id TEXT NOT NULL, " +
|
||||
"url TEXT," +
|
||||
"title TEXT," +
|
||||
|
@ -77,6 +77,7 @@ HomeStorage.prototype = {
|
|||
// XXX: Factor this out to some migration path.
|
||||
if (!(yield db.tableExists("items"))) {
|
||||
yield db.execute(SQL.createItemsTable);
|
||||
yield db.setSchemaVersion(SCHEMA_VERSION);
|
||||
}
|
||||
|
||||
// Insert data into DB.
|
||||
|
|
|
@ -123,12 +123,6 @@ MissingRequiredTabChild(mozilla::dom::TabChild* tabChild,
|
|||
const char* context)
|
||||
{
|
||||
if (UsingNeckoIPCSecurity()) {
|
||||
// Bug 833935: during navigation away from page some loads may lack
|
||||
// TabParent: we don't want to kill browser for that. Doesn't happen in
|
||||
// test harness, so fail in debug mode so we can catch new code that fails
|
||||
// to pass security info.
|
||||
MOZ_ASSERT(tabChild);
|
||||
|
||||
if (!tabChild) {
|
||||
printf_stderr("WARNING: child tried to open %s IPDL channel w/o "
|
||||
"security info\n", context);
|
||||
|
|
|
@ -136,6 +136,17 @@ class BaseBootstrapper(object):
|
|||
|
||||
self.run_as_root(command)
|
||||
|
||||
def apt_update(self):
|
||||
command = ['apt-get', 'update']
|
||||
|
||||
self.run_as_root(command)
|
||||
|
||||
def apt_add_architecture(self, arch):
|
||||
command = ['dpkg', '--add-architecture']
|
||||
command.extemd(arch)
|
||||
|
||||
self.run_as_root(command)
|
||||
|
||||
def check_output(self, *args, **kwargs):
|
||||
"""Run subprocess.check_output even if Python doesn't provide it."""
|
||||
fn = getattr(subprocess, 'check_output', BaseBootstrapper._check_output)
|
||||
|
|
|
@ -33,7 +33,9 @@ Or, if you prefer Git:
|
|||
class Bootstrapper(object):
|
||||
"""Main class that performs system bootstrap."""
|
||||
|
||||
def bootstrap(self):
|
||||
def __init__(self, finished=FINISHED):
|
||||
self.instance = None
|
||||
self.finished = finished
|
||||
cls = None
|
||||
args = {}
|
||||
|
||||
|
@ -85,9 +87,12 @@ class Bootstrapper(object):
|
|||
raise NotImplementedError('Bootstrap support is not yet available '
|
||||
'for your OS.')
|
||||
|
||||
instance = cls(**args)
|
||||
instance.install_system_packages()
|
||||
instance.ensure_mercurial_modern()
|
||||
instance.ensure_python_modern()
|
||||
self.instance = cls(**args)
|
||||
|
||||
print(FINISHED)
|
||||
|
||||
def bootstrap(self):
|
||||
self.instance.install_system_packages()
|
||||
self.instance.ensure_mercurial_modern()
|
||||
self.instance.ensure_python_modern()
|
||||
|
||||
print(self.finished)
|
||||
|
|
|
@ -14,14 +14,13 @@ class CentOSBootstrapper(BaseBootstrapper):
|
|||
self.version = version
|
||||
self.dist_id = dist_id
|
||||
|
||||
def install_system_packages(self):
|
||||
kern = platform.uname()
|
||||
|
||||
self.yum_groupinstall(
|
||||
self.group_packages = [
|
||||
'Development Tools',
|
||||
'Development Libraries',
|
||||
'GNOME Software Development')
|
||||
self.yum_install(
|
||||
'GNOME Software Development',
|
||||
]
|
||||
|
||||
self.packages = [
|
||||
'alsa-lib-devel',
|
||||
'autoconf213',
|
||||
'curl-devel',
|
||||
|
@ -36,7 +35,14 @@ class CentOSBootstrapper(BaseBootstrapper):
|
|||
'mesa-libGL-devel',
|
||||
'pulseaudio-libs-devel',
|
||||
'wireless-tools-devel',
|
||||
'yasm')
|
||||
'yasm',
|
||||
]
|
||||
|
||||
def install_system_packages(self):
|
||||
kern = platform.uname()
|
||||
|
||||
self.yum_groupinstall(*self.group_packages)
|
||||
self.yum_install(*self.packages)
|
||||
|
||||
yasm = 'http://pkgs.repoforge.org/yasm/yasm-1.1.0-1.el6.rf.i686.rpm'
|
||||
if 'x86_64' in kern[2]:
|
||||
|
|
|
@ -42,9 +42,10 @@ class DebianBootstrapper(BaseBootstrapper):
|
|||
self.version = version
|
||||
self.dist_id = dist_id
|
||||
|
||||
self.packages = self.COMMON_PACKAGES + self.DISTRO_PACKAGES
|
||||
|
||||
def install_system_packages(self):
|
||||
packages = self.COMMON_PACKAGES + self.DISTRO_PACKAGES
|
||||
self.apt_install(*packages)
|
||||
self.apt_install(*self.packages)
|
||||
|
||||
def _update_package_manager(self):
|
||||
self.run_as_root(['apt-get', 'update'])
|
||||
|
|
|
@ -13,13 +13,13 @@ class FedoraBootstrapper(BaseBootstrapper):
|
|||
self.version = version
|
||||
self.dist_id = dist_id
|
||||
|
||||
def install_system_packages(self):
|
||||
self.yum_groupinstall(
|
||||
self.group_packages = [
|
||||
'Development Tools',
|
||||
'Development Libraries',
|
||||
'GNOME Software Development')
|
||||
'GNOME Software Development',
|
||||
]
|
||||
|
||||
self.yum_install(
|
||||
self.packages = [
|
||||
'alsa-lib-devel',
|
||||
'autoconf213',
|
||||
'gcc-c++',
|
||||
|
@ -32,7 +32,12 @@ class FedoraBootstrapper(BaseBootstrapper):
|
|||
'mesa-libGL-devel',
|
||||
'pulseaudio-libs-devel',
|
||||
'wireless-tools-devel',
|
||||
'yasm')
|
||||
'yasm',
|
||||
]
|
||||
|
||||
def install_system_packages(self):
|
||||
self.yum_groupinstall(*self.group_packages)
|
||||
self.yum_install(*self.packages)
|
||||
|
||||
def upgrade_mercurial(self, current):
|
||||
self.yum_update('mercurial')
|
||||
|
|
|
@ -15,22 +15,7 @@ class FreeBSDBootstrapper(BaseBootstrapper):
|
|||
BaseBootstrapper.__init__(self)
|
||||
self.version = int(version.split('.')[0])
|
||||
|
||||
def pkg_install(self, *packages):
|
||||
if self.which('pkg'):
|
||||
command = ['pkg', 'install', '-x']
|
||||
command.extend([i[0] for i in packages])
|
||||
else:
|
||||
command = ['pkg_add', '-Fr']
|
||||
command.extend([i[-1] for i in packages])
|
||||
|
||||
self.run_as_root(command)
|
||||
|
||||
def install_system_packages(self):
|
||||
# using clang since 9.0
|
||||
if self.version < 9:
|
||||
self.pkg_install(('gcc',))
|
||||
|
||||
self.pkg_install(
|
||||
self.packages = [
|
||||
('autoconf-2.13', 'autoconf213'),
|
||||
('dbus-glib',),
|
||||
('gmake',),
|
||||
|
@ -42,7 +27,26 @@ class FreeBSDBootstrapper(BaseBootstrapper):
|
|||
('mercurial',),
|
||||
('pulseaudio',),
|
||||
('yasm',),
|
||||
('zip',))
|
||||
('zip',),
|
||||
]
|
||||
|
||||
# using clang since 9.0
|
||||
if self.version < 9:
|
||||
self.packages.append(('gcc',))
|
||||
|
||||
|
||||
def pkg_install(self, *packages):
|
||||
if self.which('pkg'):
|
||||
command = ['pkg', 'install', '-x']
|
||||
command.extend([i[0] for i in packages])
|
||||
else:
|
||||
command = ['pkg_add', '-Fr']
|
||||
command.extend([i[-1] for i in packages])
|
||||
|
||||
self.run_as_root(command)
|
||||
|
||||
def install_system_packages(self):
|
||||
self.pkg_install(*self.packages)
|
||||
|
||||
def upgrade_mercurial(self, current):
|
||||
self.pkg_install('mercurial')
|
||||
|
|
|
@ -10,9 +10,7 @@ class OpenBSDBootstrapper(BaseBootstrapper):
|
|||
def __init__(self, version):
|
||||
BaseBootstrapper.__init__(self)
|
||||
|
||||
def install_system_packages(self):
|
||||
# we use -z because there's no other way to say "any autoconf-2.13"
|
||||
self.run_as_root(['pkg_add', '-z',
|
||||
self.packages = [
|
||||
'mercurial',
|
||||
'llvm',
|
||||
'autoconf-2.13',
|
||||
|
@ -26,4 +24,9 @@ class OpenBSDBootstrapper(BaseBootstrapper):
|
|||
'gtar',
|
||||
'wget',
|
||||
'unzip',
|
||||
'zip'])
|
||||
'zip',
|
||||
]
|
||||
|
||||
def install_system_packages(self):
|
||||
# we use -z because there's no other way to say "any autoconf-2.13"
|
||||
self.run_as_root(['pkg_add', '-z'] + self.packages)
|
||||
|
|
|
@ -172,3 +172,13 @@ function uninstallFakePAC() {
|
|||
let CID = PACSystemSettings.CID;
|
||||
Cm.nsIComponentRegistrar.unregisterFactory(CID, PACSystemSettings);
|
||||
}
|
||||
|
||||
// We want to ensure the legacy provider is used for most of these tests; the
|
||||
// tests that know how to deal with the Firefox Accounts identity hack things
|
||||
// to ensure that still works.
|
||||
function setDefaultIdentityConfig() {
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.enabled", false);
|
||||
// Services.prefs.setBoolPref("services.sync.fxaccounts.enabled", false);
|
||||
}
|
||||
setDefaultIdentityConfig();
|
||||
|
|
|
@ -69,7 +69,9 @@ WeaveService.prototype = {
|
|||
// first check if Firefox accounts is available at all. This is so we can
|
||||
// get this landed without forcing Fxa to be used (and require nightly
|
||||
// testers to manually set this pref)
|
||||
// Once we decide we want Fxa to be available, we just remove this block.
|
||||
// Once we decide we want Fxa to be available, we just remove this block
|
||||
// (although a fly in this ointment is tests - it might be that we must
|
||||
// just set this as a pref with a default of true)
|
||||
let fxAccountsAvailable;
|
||||
try {
|
||||
fxAccountsAvailable = Services.prefs.getBoolPref("identity.fxaccounts.enabled");
|
||||
|
@ -101,58 +103,12 @@ WeaveService.prototype = {
|
|||
return this.fxAccountsEnabled = fxAccountsEnabled;
|
||||
},
|
||||
|
||||
maybeInitWithFxAccountsAndEnsureLoaded: function() {
|
||||
Components.utils.import("resource://services-sync/main.js");
|
||||
// FxAccounts imports lots of stuff, so only do this as we need it
|
||||
Cu.import("resource://gre/modules/FxAccounts.jsm");
|
||||
|
||||
// This isn't quite sufficient here to handle all the cases. Cases
|
||||
// we need to handle:
|
||||
// - User is signed in to FxAccounts, btu hasn't set up sync.
|
||||
return fxAccounts.getSignedInUser().then(
|
||||
(accountData) => {
|
||||
if (accountData) {
|
||||
Cu.import("resource://services-sync/browserid_identity.js");
|
||||
// The Sync Identity module needs to be set in both these places if
|
||||
// it's swapped out as we are doing here. When Weave.Service initializes
|
||||
// it grabs a reference to Weave.Status._authManager, and for references
|
||||
// to Weave.Service.identity to resolve correctly, we also need to reset
|
||||
// Weave.Service.identity as well.
|
||||
Weave.Service.identity = Weave.Status._authManager = new BrowserIDManager(),
|
||||
// Init the identity module with any account data from
|
||||
// firefox accounts. The Identity module will fetch the signed in
|
||||
// user from fxAccounts directly.
|
||||
Weave.Service.identity.initWithLoggedInUser().then(function () {
|
||||
// Set the cluster data that we got from the token
|
||||
Weave.Service.clusterURL = Weave.Service.identity.clusterURL;
|
||||
// checkSetup() will check the auth state of the identity module
|
||||
// and records that status in Weave.Status
|
||||
if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED) {
|
||||
// This makes sure that Weave.Service is loaded
|
||||
Svc.Obs.notify("weave:service:setup-complete");
|
||||
// TODO: this shouldn't be here. It should be at the end
|
||||
// of the promise chain of the 'fxaccounts:onverified' handler.
|
||||
Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
|
||||
this.ensureLoaded();
|
||||
}
|
||||
}.bind(this));
|
||||
} else if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED) {
|
||||
// This makes sure that Weave.Service is loaded
|
||||
this.ensureLoaded();
|
||||
}
|
||||
},
|
||||
(err) => {dump("err in getting logged in account "+err.message)}
|
||||
).then(null, (err) => {dump("err in processing logged in account "+err.message)});
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "app-startup":
|
||||
let os = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
os.addObserver(this, "final-ui-startup", true);
|
||||
os.addObserver(this, "fxaccounts:onverified", true);
|
||||
os.addObserver(this, "fxaccounts:onlogout", true);
|
||||
break;
|
||||
|
||||
case "final-ui-startup":
|
||||
|
@ -160,11 +116,6 @@ WeaveService.prototype = {
|
|||
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this.timer.initWithCallback({
|
||||
notify: function() {
|
||||
if (this.fxAccountsEnabled) {
|
||||
// init the fxAccounts identity manager.
|
||||
this.maybeInitWithFxAccountsAndEnsureLoaded();
|
||||
} else {
|
||||
// init the "old" style, sync-specific identity manager.
|
||||
// We only load more if it looks like Sync is configured.
|
||||
let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
|
||||
if (!prefs.prefHasUserValue("username")) {
|
||||
|
@ -180,33 +131,9 @@ WeaveService.prototype = {
|
|||
if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED) {
|
||||
this.ensureLoaded();
|
||||
}
|
||||
}
|
||||
}.bind(this)
|
||||
}, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
break;
|
||||
|
||||
case 'fxaccounts:onverified':
|
||||
// Tell sync that if this is a first sync, it should try and sync the
|
||||
// server data with what is on the client - despite the name implying
|
||||
// otherwise, this is what "resetClient" does.
|
||||
// TOOD: This implicitly assumes we're in the CLIENT_NOT_CONFIGURED state, and
|
||||
// if we're not, we should handle it here.
|
||||
Components.utils.import("resource://services-sync/main.js"); // ensure 'Weave' exists
|
||||
Weave.Svc.Prefs.set("firstSync", "resetClient");
|
||||
this.maybeInitWithFxAccountsAndEnsureLoaded().then(() => {
|
||||
// and off we go...
|
||||
// TODO: I have this being done in maybeInitWithFxAccountsAndEnsureLoaded
|
||||
// because I had a bug in the promise chains that was triggering this
|
||||
// too early. This should be fixed.
|
||||
//Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
|
||||
});
|
||||
break;
|
||||
case 'fxaccounts:onlogout':
|
||||
Components.utils.import("resource://services-sync/main.js"); // ensure 'Weave' exists
|
||||
// startOver is throwing some errors and we can't re-log in in this
|
||||
// session - so for now, we don't do this!
|
||||
//Weave.Service.startOver();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -150,7 +150,10 @@ this.configureIdentity = function(identityOverrides) {
|
|||
if (ns.Service.identity instanceof BrowserIDManager) {
|
||||
// do the FxAccounts thang...
|
||||
configureFxAccountIdentity(ns.Service.identity, config);
|
||||
return ns.Service.identity.initWithLoggedInUser();
|
||||
return ns.Service.identity.initializeWithCurrentIdentity().then(() => {
|
||||
// need to wait until this identity manager is readyToAuthenticate.
|
||||
return ns.Service.identity.whenReadyToAuthenticate.promise;
|
||||
});
|
||||
}
|
||||
// old style identity provider.
|
||||
setBasicCredentials(config.username, config.sync.password, config.sync.syncKey);
|
||||
|
@ -172,7 +175,9 @@ this.SyncTestingInfrastructure = function (server, username, password, syncKey)
|
|||
config.sync.password = password;
|
||||
if (syncKey)
|
||||
config.sync.syncKey = syncKey;
|
||||
configureIdentity(config);
|
||||
let cb = Async.makeSpinningCallback();
|
||||
configureIdentity(config).then(cb, cb);
|
||||
cb.wait();
|
||||
|
||||
let i = server.identity;
|
||||
let uri = i.primaryScheme + "://" + i.primaryHost + ":" +
|
||||
|
@ -224,16 +229,16 @@ this.add_identity_test = function(test, testFunction) {
|
|||
test.add_task(function() {
|
||||
note("sync");
|
||||
let oldIdentity = Status._authManager;
|
||||
Status._authManager = ns.Service.identity = new IdentityManager();
|
||||
Status.__authManager = ns.Service.identity = new IdentityManager();
|
||||
yield testFunction();
|
||||
Status._authManager = ns.Service.identity = oldIdentity;
|
||||
Status.__authManager = ns.Service.identity = oldIdentity;
|
||||
});
|
||||
// another task for the FxAccounts identity manager.
|
||||
test.add_task(function() {
|
||||
note("FxAccounts");
|
||||
let oldIdentity = Status._authManager;
|
||||
Status._authManager = ns.Service.identity = new BrowserIDManager();
|
||||
Status.__authManager = ns.Service.identity = new BrowserIDManager();
|
||||
yield testFunction();
|
||||
Status._authManager = ns.Service.identity = oldIdentity;
|
||||
Status.__authManager = ns.Service.identity = oldIdentity;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ Cu.import("resource://services-common/tokenserverclient.js");
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://services-sync/stages/cluster.js");
|
||||
|
||||
// Lazy imports to prevent unnecessary load on startup.
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "BulkKeyBundle",
|
||||
|
@ -26,6 +27,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "BulkKeyBundle",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, 'fxAccountsCommon', function() {
|
||||
let ob = {};
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js", ob);
|
||||
return ob;
|
||||
});
|
||||
|
||||
function deriveKeyBundle(kB) {
|
||||
let out = CryptoUtils.hkdf(kB, undefined,
|
||||
"identity.mozilla.com/picl/v1/oldsync", 2*32);
|
||||
|
@ -39,6 +46,8 @@ function deriveKeyBundle(kB) {
|
|||
this.BrowserIDManager = function BrowserIDManager() {
|
||||
this._fxaService = fxAccounts;
|
||||
this._tokenServerClient = new TokenServerClient();
|
||||
// will be a promise that resolves when we are ready to authenticate
|
||||
this.whenReadyToAuthenticate = null;
|
||||
this._log = Log.repository.getLogger("Sync.BrowserIDManager");
|
||||
this._log.Level = Log.Level[Svc.Prefs.get("log.logger.identity")];
|
||||
|
||||
|
@ -53,6 +62,107 @@ this.BrowserIDManager.prototype = {
|
|||
_token: null,
|
||||
_account: null,
|
||||
|
||||
// it takes some time to fetch a sync key bundle, so until this flag is set,
|
||||
// we don't consider the lack of a keybundle as a failure state.
|
||||
_shouldHaveSyncKeyBundle: false,
|
||||
|
||||
get readyToAuthenticate() {
|
||||
// We are finished initializing when we *should* have a sync key bundle,
|
||||
// although we might not actually have one due to auth failures etc.
|
||||
return this._shouldHaveSyncKeyBundle;
|
||||
},
|
||||
|
||||
get needsCustomization() {
|
||||
try {
|
||||
return Services.prefs.getBoolPref("services.sync.needsCustomization");
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
Services.obs.addObserver(this, fxAccountsCommon.ONVERIFIED_NOTIFICATION, false);
|
||||
Services.obs.addObserver(this, fxAccountsCommon.ONLOGOUT_NOTIFICATION, false);
|
||||
return this.initializeWithCurrentIdentity();
|
||||
},
|
||||
|
||||
initializeWithCurrentIdentity: function() {
|
||||
this._log.trace("initializeWithCurrentIdentity");
|
||||
Components.utils.import("resource://services-sync/main.js");
|
||||
|
||||
// Reset the world before we do anything async.
|
||||
this.whenReadyToAuthenticate = Promise.defer();
|
||||
this._shouldHaveSyncKeyBundle = false;
|
||||
this.username = ""; // this calls resetCredentials which drops the key bundle.
|
||||
|
||||
return fxAccounts.getSignedInUser().then(accountData => {
|
||||
if (!accountData) {
|
||||
this._log.info("initializeWithCurrentIdentity has no user logged in");
|
||||
this._account = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.needsCustomization) {
|
||||
// If the user chose to "Customize sync options" when signing
|
||||
// up with Firefox Accounts, ask them to choose what to sync.
|
||||
const url = "chrome://browser/content/sync/customize.xul";
|
||||
const features = "centerscreen,chrome,modal,dialog,resizable=no";
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
let data = {accepted: false};
|
||||
win.openDialog(url, "_blank", features, data);
|
||||
|
||||
if (data.accepted) {
|
||||
Services.prefs.clearUserPref("services.sync.needsCustomization");
|
||||
} else {
|
||||
// Log out if the user canceled the dialog.
|
||||
return fxAccounts.signOut();
|
||||
}
|
||||
}
|
||||
|
||||
this._account = accountData.email;
|
||||
// We start a background keybundle fetch...
|
||||
this._log.info("Starting background fetch for key bundle.");
|
||||
this._fetchSyncKeyBundle().then(() => {
|
||||
this._shouldHaveSyncKeyBundle = true; // and we should actually have one...
|
||||
this.whenReadyToAuthenticate.resolve();
|
||||
this._log.info("Background fetch for key bundle done");
|
||||
}).then(null, err => {
|
||||
this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
|
||||
this.whenReadyToAuthenticate.reject(err);
|
||||
// report what failed...
|
||||
this._log.error("Background fetch for key bundle failed: " + err);
|
||||
throw err;
|
||||
});
|
||||
// and we are done - the fetch continues on in the background...
|
||||
}).then(null, err => {
|
||||
dump("err in processing logged in account "+err.message);
|
||||
});
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
switch (topic) {
|
||||
case fxAccountsCommon.ONVERIFIED_NOTIFICATION:
|
||||
case fxAccountsCommon.ONLOGIN_NOTIFICATION:
|
||||
// For now, we just assume it's the same user logging back in.
|
||||
// Bug 958927 exists to work out what to do if that's not true. It might
|
||||
// be that the :onlogout observer does a .startOver (or maybe not - TBD)
|
||||
// But for now, do nothing, and sync will just start re-synching in its
|
||||
// own sweet time...
|
||||
this.initializeWithCurrentIdentity();
|
||||
break;
|
||||
|
||||
case fxAccountsCommon.ONLOGOUT_NOTIFICATION:
|
||||
Components.utils.import("resource://services-sync/main.js");
|
||||
// Setting .username calls resetCredentials which drops the key bundle
|
||||
// and resets _shouldHaveSyncKeyBundle.
|
||||
this.username = "";
|
||||
this._account = null;
|
||||
Weave.Service.logout();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Provide override point for testing token expiration.
|
||||
*/
|
||||
|
@ -60,8 +170,6 @@ this.BrowserIDManager.prototype = {
|
|||
return Date.now();
|
||||
},
|
||||
|
||||
clusterURL: null,
|
||||
|
||||
get account() {
|
||||
return this._account;
|
||||
},
|
||||
|
@ -148,6 +256,7 @@ this.BrowserIDManager.prototype = {
|
|||
this._syncKey = null;
|
||||
this._syncKeyBundle = null;
|
||||
this._syncKeyUpdated = true;
|
||||
this._shouldHaveSyncKeyBundle = false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -166,8 +275,8 @@ this.BrowserIDManager.prototype = {
|
|||
|
||||
// No need to check this.syncKey as our getter for that attribute
|
||||
// uses this.syncKeyBundle
|
||||
// If bundle creation failed.
|
||||
if (!this.syncKeyBundle) {
|
||||
// If bundle creation started, but failed.
|
||||
if (this._shouldHaveSyncKeyBundle && !this.syncKeyBundle) {
|
||||
return LOGIN_FAILED_NO_PASSPHRASE;
|
||||
}
|
||||
|
||||
|
@ -221,44 +330,23 @@ this.BrowserIDManager.prototype = {
|
|||
return userData;
|
||||
},
|
||||
|
||||
// initWithLoggedInUser will fetch the logged in user from firefox accounts,
|
||||
// and if such a logged in user exists, will use that user to initialize
|
||||
// the identity module. Returns a Promise.
|
||||
initWithLoggedInUser: function() {
|
||||
// Get the signed in user from FxAccounts.
|
||||
return this._fxaService.getSignedInUser()
|
||||
.then(userData => {
|
||||
if (!userData) {
|
||||
this._log.warn("initWithLoggedInUser found no logged in user");
|
||||
throw new Error("initWithLoggedInUser found no logged in user");
|
||||
}
|
||||
// Make a note of the last logged in user.
|
||||
this._account = userData.email;
|
||||
_fetchSyncKeyBundle: function() {
|
||||
// Fetch a sync token for the logged in user from the token server.
|
||||
return this._refreshTokenForLoggedInUser();
|
||||
})
|
||||
.then(token => {
|
||||
return this._refreshTokenForLoggedInUser(
|
||||
).then(token => {
|
||||
this._token = token;
|
||||
// Set the username to be the uid returned by the token server.
|
||||
// TODO: check here to see if the uid is different that the current
|
||||
// this.username. If so, we may need to reinit sync, detect if the new
|
||||
// user has sync set up, etc
|
||||
this.username = this._token.uid.toString();
|
||||
|
||||
return this._fxaService.getKeys();
|
||||
})
|
||||
.then(userData => {
|
||||
}).then(userData => {
|
||||
// unlikely, but if the logged in user somehow changed between these
|
||||
// calls we better fail.
|
||||
if (!userData || userData.email !== this.account) {
|
||||
throw new Error("The currently logged-in user has changed.");
|
||||
}
|
||||
// Set the username to be the uid returned by the token server.
|
||||
this.username = this._token.uid.toString();
|
||||
// both Jelly and FxAccounts give us kA/kB as hex.
|
||||
let kB = Utils.hexToBytes(userData.kB);
|
||||
this._syncKeyBundle = deriveKeyBundle(kB);
|
||||
|
||||
// Set the clusterURI for this user based on the endpoint in the
|
||||
// token. This is a bit of a hack, and we should figure out a better
|
||||
// way of distributing it to components that need it.
|
||||
let clusterURI = Services.io.newURI(this._token.endpoint, null, null);
|
||||
clusterURI.path = "/";
|
||||
this.clusterURL = clusterURI.spec;
|
||||
this._log.info("initWithLoggedUser has username " + this.username + ", endpoint is " + this.clusterURL);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -368,5 +456,45 @@ this.BrowserIDManager.prototype = {
|
|||
}
|
||||
request.setHeader("authorization", header.headers.authorization);
|
||||
return request;
|
||||
},
|
||||
|
||||
createClusterManager: function(service) {
|
||||
return new BrowserIDClusterManager(service);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* An implementation of the ClusterManager for this identity
|
||||
*/
|
||||
|
||||
function BrowserIDClusterManager(service) {
|
||||
ClusterManager.call(this, service);
|
||||
}
|
||||
|
||||
BrowserIDClusterManager.prototype = {
|
||||
__proto__: ClusterManager.prototype,
|
||||
|
||||
_findCluster: function() {
|
||||
let promiseClusterURL = function() {
|
||||
return fxAccounts.getSignedInUser().then(userData => {
|
||||
return this.identity._fetchTokenForUser(userData).then(token => {
|
||||
// Set the clusterURI for this user based on the endpoint in the
|
||||
// token. This is a bit of a hack, and we should figure out a better
|
||||
// way of distributing it to components that need it.
|
||||
let clusterURI = Services.io.newURI(token.endpoint, null, null);
|
||||
clusterURI.path = "/";
|
||||
return clusterURI.spec;
|
||||
});
|
||||
});
|
||||
}.bind(this);
|
||||
|
||||
let cb = Async.makeSpinningCallback();
|
||||
promiseClusterURL().then(function (clusterURL) {
|
||||
cb(null, clusterURL);
|
||||
},
|
||||
function (err) {
|
||||
cb(err);
|
||||
});
|
||||
return cb.wait();
|
||||
},
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@ LOGIN_FAILED_NETWORK_ERROR: "error.login.reason.network",
|
|||
LOGIN_FAILED_SERVER_ERROR: "error.login.reason.server",
|
||||
LOGIN_FAILED_INVALID_PASSPHRASE: "error.login.reason.recoverykey",
|
||||
LOGIN_FAILED_LOGIN_REJECTED: "error.login.reason.account",
|
||||
LOGIN_FAILED_NOT_READY: "error.login.reason.initializing",
|
||||
|
||||
// sync failure status codes
|
||||
METARECORD_DOWNLOAD_FAIL: "error.sync.reason.metarecord_download_fail",
|
||||
|
|
|
@ -9,6 +9,7 @@ this.EXPORTED_SYMBOLS = ["IdentityManager"];
|
|||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
@ -21,7 +22,8 @@ for (let symbol of ["BulkKeyBundle", "SyncKeyBundle"]) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Manages identity and authentication for Sync.
|
||||
* Manages "legacy" identity and authentication for Sync.
|
||||
* See browserid_identity for the Firefox Accounts based identity manager.
|
||||
*
|
||||
* The following entities are managed:
|
||||
*
|
||||
|
@ -81,6 +83,24 @@ IdentityManager.prototype = {
|
|||
|
||||
_syncKeyBundle: null,
|
||||
|
||||
/**
|
||||
* Initialize the identity provider. Returns a promise that is resolved
|
||||
* when initialization is complete and the provider can be queried for
|
||||
* its state
|
||||
*/
|
||||
initialize: function() {
|
||||
// nothing to do for this identity provider
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates if the identity manager is still initializing
|
||||
*/
|
||||
get readyToAuthenticate() {
|
||||
// We initialize in a fully sync manner, so we are always finished.
|
||||
return true;
|
||||
},
|
||||
|
||||
get account() {
|
||||
return Svc.Prefs.get("account", this.username);
|
||||
},
|
||||
|
@ -505,5 +525,10 @@ IdentityManager.prototype = {
|
|||
onRESTRequestBasic: function onRESTRequestBasic(request) {
|
||||
let up = this.username + ":" + this.basicPassword;
|
||||
request.setHeader("authorization", "Basic " + btoa(up));
|
||||
},
|
||||
|
||||
createClusterManager: function(service) {
|
||||
Cu.import("resource://services-sync/stages/cluster.js");
|
||||
return new ClusterManager(service);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,9 +12,11 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Status",
|
||||
"resource://services-sync/status.js");
|
||||
|
||||
this.SyncScheduler = function SyncScheduler(service) {
|
||||
this.service = service;
|
||||
this.init();
|
||||
|
|
|
@ -30,7 +30,6 @@ Cu.import("resource://services-sync/policies.js");
|
|||
Cu.import("resource://services-sync/record.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/rest.js");
|
||||
Cu.import("resource://services-sync/stages/cluster.js");
|
||||
Cu.import("resource://services-sync/stages/enginesync.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
Cu.import("resource://services-sync/userapi.js");
|
||||
|
@ -323,7 +322,7 @@ Sync11Service.prototype = {
|
|||
|
||||
this._log.info("Loading Weave " + WEAVE_VERSION);
|
||||
|
||||
this._clusterManager = new ClusterManager(this);
|
||||
this._clusterManager = this.identity.createClusterManager(this);
|
||||
this.recordManager = new RecordManager(this);
|
||||
|
||||
this.enabled = true;
|
||||
|
@ -649,6 +648,13 @@ Sync11Service.prototype = {
|
|||
},
|
||||
|
||||
verifyLogin: function verifyLogin() {
|
||||
// If the identity isn't ready it might not know the username...
|
||||
if (!this.identity.readyToAuthenticate) {
|
||||
this._log.info("Not ready to authenticate in verifyLogin.");
|
||||
this.status.login = LOGIN_FAILED_NOT_READY;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.identity.username) {
|
||||
this._log.warn("No username in verifyLogin.");
|
||||
this.status.login = LOGIN_FAILED_NO_USERNAME;
|
||||
|
|
|
@ -12,13 +12,31 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/browserid_identity.js");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://services-common/async.js");
|
||||
|
||||
this.Status = {
|
||||
_log: Log.repository.getLogger("Sync.Status"),
|
||||
_authManager: new IdentityManager(),
|
||||
__authManager: null,
|
||||
ready: false,
|
||||
|
||||
get _authManager() {
|
||||
if (this.__authManager) {
|
||||
return this.__authManager;
|
||||
}
|
||||
let service = Components.classes["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
let idClass = service.fxAccountsEnabled ? BrowserIDManager : IdentityManager;
|
||||
this.__authManager = new idClass();
|
||||
// .initialize returns a promise, so we need to spin until it resolves.
|
||||
let cb = Async.makeSpinningCallback();
|
||||
this.__authManager.initialize().then(cb, cb);
|
||||
cb.wait();
|
||||
return this.__authManager;
|
||||
},
|
||||
|
||||
get service() {
|
||||
return this._service;
|
||||
},
|
||||
|
|
|
@ -73,3 +73,6 @@ pref("services.sync.log.logger.userapi", "Debug");
|
|||
pref("services.sync.log.cryptoDebug", false);
|
||||
|
||||
pref("services.sync.tokenServerURI", "http://auth.oldsync.dev.lcip.org/1.0/sync/1.1");
|
||||
|
||||
pref("services.sync.fxa.termsURL", "https://accounts.firefox.com/legal/terms");
|
||||
pref("services.sync.fxa.privacyURL", "https://accounts.firefox.com/legal/privacy");
|
||||
|
|
|
@ -532,6 +532,7 @@ class BaseMarionetteTestRunner(object):
|
|||
self.shuffle = shuffle
|
||||
self.sdcard = sdcard
|
||||
self.mixin_run_tests = []
|
||||
self.manifest_skipped_tests = []
|
||||
|
||||
if testvars:
|
||||
if not os.path.exists(testvars):
|
||||
|
@ -806,22 +807,30 @@ class BaseMarionetteTestRunner(object):
|
|||
manifest = TestManifest()
|
||||
manifest.read(filepath)
|
||||
|
||||
all_tests = manifest.active_tests(exists=False, disabled=False)
|
||||
manifest_tests = manifest.active_tests(exists=False,
|
||||
disabled=False,
|
||||
disabled=True,
|
||||
device=self.device,
|
||||
app=self.appName,
|
||||
**mozinfo.info)
|
||||
skip_tests = list(set([x['path'] for x in all_tests]) -
|
||||
set([x['path'] for x in manifest_tests]))
|
||||
for skipped in skip_tests:
|
||||
self.logger.info('TEST-SKIP | %s | device=%s, app=%s' %
|
||||
(os.path.basename(skipped),
|
||||
self.device,
|
||||
self.appName))
|
||||
unfiltered_tests = []
|
||||
for test in manifest_tests:
|
||||
if test.get('disabled'):
|
||||
self.manifest_skipped_tests.append(test)
|
||||
else:
|
||||
unfiltered_tests.append(test)
|
||||
|
||||
target_tests = manifest.get(tests=unfiltered_tests, **testargs)
|
||||
for test in unfiltered_tests:
|
||||
if test['path'] not in [x['path'] for x in target_tests]:
|
||||
test.setdefault('disabled', 'filtered by type (%s)' % self.type)
|
||||
self.manifest_skipped_tests.append(test)
|
||||
|
||||
for test in self.manifest_skipped_tests:
|
||||
self.logger.info('TEST-SKIP | %s | %s' % (
|
||||
os.path.basename(test['path']),
|
||||
test['disabled']))
|
||||
self.todo += 1
|
||||
|
||||
target_tests = manifest.get(tests=manifest_tests, **testargs)
|
||||
if self.shuffle:
|
||||
random.shuffle(target_tests)
|
||||
for i in target_tests:
|
||||
|
@ -874,17 +883,32 @@ class BaseMarionetteTestRunner(object):
|
|||
|
||||
def generate_xml(self, results_list):
|
||||
|
||||
def _extract_xml(test, result='passed'):
|
||||
def _extract_xml_from_result(test_result, result='passed'):
|
||||
_extract_xml(
|
||||
test_name=unicode(test_result.name).split()[0],
|
||||
test_class=test_result.test_class,
|
||||
duration=test_result.duration,
|
||||
result=result,
|
||||
output='\n'.join(test_result.output))
|
||||
|
||||
def _extract_xml_from_skipped_manifest_test(test):
|
||||
_extract_xml(
|
||||
test_name=test['name'],
|
||||
result='skipped',
|
||||
output=test['disabled'])
|
||||
|
||||
def _extract_xml(test_name, test_class='', duration=0,
|
||||
result='passed', output=''):
|
||||
testcase = doc.createElement('testcase')
|
||||
testcase.setAttribute('classname', test.test_class)
|
||||
testcase.setAttribute('name', unicode(test.name).split()[0])
|
||||
testcase.setAttribute('time', str(test.duration))
|
||||
testcase.setAttribute('classname', test_class)
|
||||
testcase.setAttribute('name', test_name)
|
||||
testcase.setAttribute('time', str(duration))
|
||||
testsuite.appendChild(testcase)
|
||||
|
||||
if result in ['failure', 'error', 'skipped']:
|
||||
f = doc.createElement(result)
|
||||
f.setAttribute('message', 'test %s' % result)
|
||||
f.appendChild(doc.createTextNode(test.reason))
|
||||
f.appendChild(doc.createTextNode(output))
|
||||
testcase.appendChild(f)
|
||||
|
||||
doc = dom.Document()
|
||||
|
@ -905,33 +929,38 @@ class BaseMarionetteTestRunner(object):
|
|||
for results in results_list])))
|
||||
testsuite.setAttribute('errors', str(sum([len(results.errors)
|
||||
for results in results_list])))
|
||||
testsuite.setAttribute('skips', str(sum([len(results.skipped) +
|
||||
testsuite.setAttribute(
|
||||
'skips', str(sum([len(results.skipped) +
|
||||
len(results.expectedFailures)
|
||||
for results in results_list])))
|
||||
for results in results_list]) +
|
||||
len(self.manifest_skipped_tests)))
|
||||
|
||||
for results in results_list:
|
||||
|
||||
for result in results.errors:
|
||||
_extract_xml(result, result='error')
|
||||
_extract_xml_from_result(result, result='error')
|
||||
|
||||
for result in results.failures:
|
||||
_extract_xml(result, result='failure')
|
||||
_extract_xml_from_result(result, result='failure')
|
||||
|
||||
if hasattr(results, 'unexpectedSuccesses'):
|
||||
for test in results.unexpectedSuccesses:
|
||||
# unexpectedSuccesses is a list of Testcases only, no tuples
|
||||
_extract_xml(test, result='failure')
|
||||
_extract_xml_from_result(test, result='failure')
|
||||
|
||||
if hasattr(results, 'skipped'):
|
||||
for result in results.skipped:
|
||||
_extract_xml(result, result='skipped')
|
||||
_extract_xml_from_result(result, result='skipped')
|
||||
|
||||
if hasattr(results, 'expectedFailures'):
|
||||
for result in results.expectedFailures:
|
||||
_extract_xml(result, result='skipped')
|
||||
_extract_xml_from_result(result, result='skipped')
|
||||
|
||||
for result in results.tests_passed:
|
||||
_extract_xml(result)
|
||||
_extract_xml_from_result(result)
|
||||
|
||||
for test in self.manifest_skipped_tests:
|
||||
_extract_xml_from_skipped_manifest_test(test)
|
||||
|
||||
doc.appendChild(testsuite)
|
||||
return doc.toprettyxml(encoding='utf-8')
|
||||
|
|
|
@ -44,15 +44,31 @@ class HTMLReportingTestRunnerMixin(object):
|
|||
tests = sum([results.testsRun for results in results_list])
|
||||
failures = sum([len(results.failures) for results in results_list])
|
||||
expected_failures = sum([len(results.expectedFailures) for results in results_list])
|
||||
skips = sum([len(results.skipped) for results in results_list])
|
||||
skips = sum([len(results.skipped) for results in results_list]) + len(self.manifest_skipped_tests)
|
||||
errors = sum([len(results.errors) for results in results_list])
|
||||
passes = sum([results.passed for results in results_list])
|
||||
unexpected_passes = sum([len(results.unexpectedSuccesses) for results in results_list])
|
||||
test_time = self.elapsedtime.total_seconds()
|
||||
test_logs = []
|
||||
|
||||
def _extract_html(test):
|
||||
def _extract_html_from_result(result):
|
||||
_extract_html(
|
||||
result=result.result,
|
||||
test_name=result.name,
|
||||
test_class=result.test_class,
|
||||
debug=result.debug,
|
||||
output='\n'.join(result.output))
|
||||
|
||||
def _extract_html_from_skipped_manifest_test(test):
|
||||
_extract_html(
|
||||
result='skipped',
|
||||
test_name=test['name'],
|
||||
output=test.get('disabled'))
|
||||
|
||||
def _extract_html(result, test_name, test_class='', duration=0,
|
||||
debug=None, output=''):
|
||||
additional_html = []
|
||||
debug = debug or {}
|
||||
links_html = []
|
||||
|
||||
result_map = {
|
||||
|
@ -61,13 +77,13 @@ class HTMLReportingTestRunnerMixin(object):
|
|||
'UNEXPECTED-FAIL': 'failure',
|
||||
'UNEXPECTED-PASS': 'unexpected pass'}
|
||||
|
||||
if test.result in ['SKIPPED', 'UNEXPECTED-FAIL', 'KNOWN-FAIL', 'ERROR']:
|
||||
if test.debug.get('screenshot'):
|
||||
screenshot = 'data:image/png;base64,%s' % test.debug['screenshot']
|
||||
if result.upper() in ['SKIPPED', 'UNEXPECTED-FAIL', 'KNOWN-FAIL', 'ERROR']:
|
||||
if debug.get('screenshot'):
|
||||
screenshot = 'data:image/png;base64,%s' % debug['screenshot']
|
||||
additional_html.append(html.div(
|
||||
html.a(html.img(src=screenshot), href="#"),
|
||||
class_='screenshot'))
|
||||
for name, content in test.debug.items():
|
||||
for name, content in debug.items():
|
||||
try:
|
||||
if 'screenshot' in name:
|
||||
href = '#'
|
||||
|
@ -86,7 +102,7 @@ class HTMLReportingTestRunnerMixin(object):
|
|||
pass
|
||||
|
||||
log = html.div(class_='log')
|
||||
for line in '\n'.join(test.output).splitlines():
|
||||
for line in output.splitlines():
|
||||
separator = line.startswith(' ' * 10)
|
||||
if separator:
|
||||
log.append(line[:80])
|
||||
|
@ -99,16 +115,19 @@ class HTMLReportingTestRunnerMixin(object):
|
|||
additional_html.append(log)
|
||||
|
||||
test_logs.append(html.tr([
|
||||
html.td(result_map.get(test.result, test.result).title(), class_='col-result'),
|
||||
html.td(test.test_class, class_='col-class'),
|
||||
html.td(unicode(test.name), class_='col-name'),
|
||||
html.td(int(test.duration), class_='col-duration'),
|
||||
html.td(result_map.get(result, result).title(), class_='col-result'),
|
||||
html.td(test_class, class_='col-class'),
|
||||
html.td(test_name, class_='col-name'),
|
||||
html.td(str(duration), class_='col-duration'),
|
||||
html.td(links_html, class_='col-links'),
|
||||
html.td(additional_html, class_='debug')],
|
||||
class_=result_map.get(test.result, test.result).lower() + ' results-table-row'))
|
||||
class_=result_map.get(result, result).lower() + ' results-table-row'))
|
||||
|
||||
for results in results_list:
|
||||
[_extract_html(test) for test in results.tests]
|
||||
[_extract_html_from_result(test) for test in results.tests]
|
||||
|
||||
for test in self.manifest_skipped_tests:
|
||||
_extract_html_from_skipped_manifest_test(test)
|
||||
|
||||
generated = datetime.datetime.now()
|
||||
doc = html.html(
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче