merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2014-03-13 12:52:51 +01:00
Родитель 8d09c94c90 ed2f101c5e
Коммит 210cd1a4c5
69 изменённых файлов: 1333 добавлений и 377 удалений

Просмотреть файл

@ -0,0 +1,40 @@
/* 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";
const { Cu } = require("chrome");
// Because Firefox Holly, we still need to check if `CustomizableUI` is
// available. Once Australis will officially land, we can safely remove it.
// See Bug 959142
try {
Cu.import("resource:///modules/CustomizableUI.jsm", {});
}
catch (e) {
throw Error("Unsupported Application: The module" + module.id +
" does not support this application.");
}
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { receive } = require("../event/utils");
const { InputPort } = require("./system");
const { object} = require("../util/sequence");
const { getOuterId } = require("../window/utils");
const Input = function() {};
Input.prototype = Object.create(InputPort.prototype);
Input.prototype.onCustomizeStart = function (window) {
receive(this, object([getOuterId(window), true]));
}
Input.prototype.onCustomizeEnd = function (window) {
receive(this, object([getOuterId(window), null]));
}
Input.prototype.addListener = input => CustomizableUI.addListener(input);
Input.prototype.removeListener = input => CustomizableUI.removeListener(input);
exports.CustomizationInput = Input;

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cc, Ci, Cr } = require("chrome");
const { Cc, Ci, Cr, Cu } = require("chrome");
const { Input, start, stop, end, receive, outputs } = require("../event/utils");
const { once, off } = require("../event/core");
const { id: addonID } = require("../self");
@ -14,6 +14,7 @@ const { addObserver, removeObserver } = Cc['@mozilla.org/observer-service;1'].
const addonUnloadTopic = "sdk:loader:destroy";
const isXrayWrapper = Cu.isXrayWrapper;
// In the past SDK used to double-wrap notifications dispatched, which
// made them awkward to use outside of SDK. At present they no longer
// do that, although we still supported for legacy reasons.
@ -48,23 +49,29 @@ InputPort.prototype.constructor = InputPort;
// When port is started (which is when it's subgraph get's
// first subscriber) actual observer is registered.
InputPort.start = input => {
addObserver(input, input.topic, false);
input.addListener(input);
// Also register add-on unload observer to end this signal
// when that happens.
addObserver(input, addonUnloadTopic, false);
};
InputPort.prototype[start] = InputPort.start;
InputPort.addListener = input => addObserver(input, input.topic, false);
InputPort.prototype.addListener = InputPort.addListener;
// When port is stopped (which is when it's subgraph has no
// no subcribers left) an actual observer unregistered.
// Note that port stopped once it ends as well (which is when
// add-on is unloaded).
InputPort.stop = input => {
removeObserver(input, input.topic);
input.removeListener(input);
removeObserver(input, addonUnloadTopic);
};
InputPort.prototype[stop] = InputPort.stop;
InputPort.removeListener = input => removeObserver(input, input.topic);
InputPort.prototype.removeListener = InputPort.removeListener;
// `InputPort` also implements `nsIObserver` interface and
// `nsISupportsWeakReference` interfaces as it's going to be used as such.
InputPort.prototype.QueryInterface = function(iid) {
@ -80,9 +87,11 @@ InputPort.prototype.QueryInterface = function(iid) {
InputPort.prototype.observe = function(subject, topic, data) {
// Unwrap message from the subject. SDK used to have it's own version of
// wrappedJSObjects which take precedence, if subject has `wrappedJSObject`
// use it as message. Otherwise use subject as is.
// and it's not an XrayWrapper use it as message. Otherwise use subject as
// is.
const message = subject === null ? null :
isLegacyWrapper(subject) ? unwrapLegacy(subject) :
isXrayWrapper(subject) ? subject :
subject.wrappedJSObject ? subject.wrappedJSObject :
subject;

Просмотреть файл

@ -13,7 +13,6 @@ module.metadata = {
};
const { Ci } = require("chrome");
const { validateOptions: valid } = require('./deprecated/api-utils');
const { setTimeout } = require('./timers');
const { isPrivateBrowsingSupported } = require('./self');
const { isWindowPBSupported } = require('./private-browsing/utils');
@ -31,11 +30,14 @@ const { events } = require("./panel/events");
const systemEvents = require("./system/events");
const { filter, pipe, stripListeners } = require("./event/utils");
const { getNodeView, getActiveView } = require("./view/core");
const { isNil, isObject } = require("./lang/type");
const { isNil, isObject, isNumber } = require("./lang/type");
const { getAttachEventType } = require("./content/utils");
const { number, boolean, object } = require('./deprecated/api-utils');
let number = { is: ['number', 'undefined', 'null'] };
let boolean = { is: ['boolean', 'undefined', 'null'] };
let isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
some(value => isNumber(value) && !isNaN(value));
let isSDKObj = obj => obj instanceof Class;
let rectContract = contract({
top: number,
@ -44,16 +46,20 @@ let rectContract = contract({
left: number
});
let rect = {
is: ['object', 'undefined', 'null'],
map: function(v) isNil(v) || !isObject(v) ? v : rectContract(v)
let position = {
is: object,
map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v),
ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)),
msg: 'The option "position" must be a SDK object registered as anchor; ' +
'or an object with one or more of the following keys set to numeric ' +
'values: top, right, bottom, left.'
}
let displayContract = contract({
width: number,
height: number,
focus: boolean,
position: rect
position: position
});
let panelContract = contract(merge({}, displayContract.rules, loaderContract.rules));
@ -176,7 +182,7 @@ const Panel = Class({
get isShowing() !isDisposed(this) && domPanel.isOpen(viewFor(this)),
/* Public API: Panel.show */
show: function show(options, anchor) {
show: function show(options={}, anchor) {
if (options instanceof Ci.nsIDOMElement) {
[anchor, options] = [options, null];
}
@ -191,7 +197,7 @@ const Panel = Class({
let model = modelFor(this);
let view = viewFor(this);
let anchorView = getNodeView(anchor);
let anchorView = getNodeView(anchor || options.position);
options = merge({
position: model.position,
@ -239,24 +245,25 @@ exports.Panel = Panel;
getActiveView.define(Panel, viewFor);
// Filter panel events to only panels that are create by this module.
let panelEvents = filter(events, function({target}) panelFor(target));
let panelEvents = filter(events, ({target}) => panelFor(target));
// Panel events emitted after panel has being shown.
let shows = filter(panelEvents, function({type}) type === "popupshown");
let shows = filter(panelEvents, ({type}) => type === "popupshown");
// Panel events emitted after panel became hidden.
let hides = filter(panelEvents, function({type}) type === "popuphidden");
let hides = filter(panelEvents, ({type}) => type === "popuphidden");
// Panel events emitted after content inside panel is ready. For different
// panels ready may mean different state based on `contentScriptWhen` attribute.
// Weather given event represents readyness is detected by `getAttachEventType`
// helper function.
let ready = filter(panelEvents, function({type, target})
let ready = filter(panelEvents, ({type, target}) =>
getAttachEventType(modelFor(panelFor(target))) === type);
// Forward panel show / hide events to panel's own event listeners.
on(shows, "data", function({target}) emit(panelFor(target), "show"));
on(hides, "data", function({target}) emit(panelFor(target), "hide"));
on(shows, "data", ({target}) => emit(panelFor(target), "show"));
on(hides, "data", ({target}) => emit(panelFor(target), "hide"));
on(ready, "data", function({target}) {
let worker = workerFor(panelFor(target));

Просмотреть файл

@ -23,6 +23,8 @@ const events = require("../system/events");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) {
position = position || {};
let x, y;
let hasTop = !isNil(position.top);
@ -127,14 +129,32 @@ function display(panel, options, anchor) {
({x, y, width, height}) = calculateRegion(options, viewportRect);
}
else {
// The XUL Panel has an arrow, so the margin needs to be reset
// to the default value.
panel.style.margin = "";
let { CustomizableUI, window } = anchor.ownerDocument.defaultView;
// In Australis, widgets may be positioned in an overflow panel or the
// menu panel.
// In such cases clicking this widget will hide the overflow/menu panel,
// and the widget's panel will show instead.
if (CustomizableUI) {
let node = anchor;
({anchor}) = CustomizableUI.getWidget(anchor.id).forWindow(window);
// if `node` is not the `anchor` itself, it means the widget is
// positioned in a panel, therefore we have to hide it before show
// the widget's panel in the same anchor
if (node !== anchor)
CustomizableUI.hidePanelForNode(anchor);
}
width = width || defaultWidth;
height = height || defaultHeight;
// Open the popup by the anchor.
let rect = anchor.getBoundingClientRect();
let window = anchor.ownerDocument.defaultView;
let zoom = getScreenPixelsPerCSSPixel(window);
let screenX = rect.left + window.mozInnerScreenX * zoom;
let screenY = rect.top + window.mozInnerScreenY * zoom;

Просмотреть файл

@ -12,7 +12,6 @@ const { Cc, Ci, CC } = require('chrome');
const options = require('@loader/options');
const file = require('./io/file');
const runtime = require("./system/runtime");
var cfxArgs = require("@test/options");
const appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
getService(Ci.nsIAppStartup);
@ -70,13 +69,12 @@ exports.exit = function exit(code) {
stream.write(status, status.length);
stream.flush();
stream.close();
if (cfxArgs.parseable) {
console.log('wrote to resultFile');
}
}
forcedExit = true;
appStartup.quit(E_FORCE);
if (code == 0) {
forcedExit = true;
}
appStartup.quit(code ? E_ATTEMPT : E_FORCE);
};
// Adapter for nodejs's stdout & stderr:

Просмотреть файл

@ -7,7 +7,6 @@ module.metadata = {
"stability": "experimental"
};
var { setTimeout } = require("../timers");
var { exit, stdout } = require("../system");
var cfxArgs = require("@test/options");
@ -20,16 +19,9 @@ function runTests(findAndRunTests) {
stdout.write(tests.passed + " of " + total + " tests passed.\n");
if (tests.failed == 0) {
if (tests.passed === 0) {
if (tests.passed === 0)
stdout.write("No tests were run\n");
}
setTimeout(function() {
if (cfxArgs.parseable) {
console.log('calling exit(0)');
}
exit(0);
}, 0);
exit(0);
} else {
if (cfxArgs.verbose || cfxArgs.parseable)
printFailedTests(tests, stdout.write);

Просмотреть файл

@ -26,6 +26,7 @@ const { merge } = require('../../util/object');
const { Disposable } = require('../../core/disposable');
const { on, off, emit, setListeners } = require('../../event/core');
const { EventTarget } = require('../../event/target');
const { getNodeView } = require('../../view/core');
const view = require('./view');
const { buttonContract, stateContract } = require('./contract');
@ -89,6 +90,10 @@ exports.ActionButton = ActionButton;
identify.define(ActionButton, ({id}) => toWidgetId(id));
getNodeView.define(ActionButton, button =>
view.nodeFor(toWidgetId(button.id))
);
let actionButtonStateEvents = events.filter(stateEvents,
e => e.target instanceof ActionButton);

Просмотреть файл

@ -26,6 +26,7 @@ const { merge } = require('../../util/object');
const { Disposable } = require('../../core/disposable');
const { on, off, emit, setListeners } = require('../../event/core');
const { EventTarget } = require('../../event/target');
const { getNodeView } = require('../../view/core');
const view = require('./view');
const { toggleButtonContract, toggleStateContract } = require('./contract');
@ -90,6 +91,10 @@ exports.ToggleButton = ToggleButton;
identify.define(ToggleButton, ({id}) => toWidgetId(id));
getNodeView.define(ToggleButton, button =>
view.nodeFor(toWidgetId(button.id))
);
let toggleButtonStateEvents = events.filter(stateEvents,
e => e.target instanceof ToggleButton);

Просмотреть файл

@ -108,6 +108,11 @@ function getImage(icon, isInToolbar, pixelRatio) {
return image;
}
function nodeFor(id, window=getMostRecentBrowserWindow()) {
return customizedWindows.has(window) ? null : getNode(id, window);
};
exports.nodeFor = nodeFor;
function create(options) {
let { id, label, icon, type } = options;
@ -183,7 +188,7 @@ function setIcon(id, window, icon) {
exports.setIcon = setIcon;
function setLabel(id, window, label) {
let node = customizedWindows.has(window) ? null : getNode(id, window);
let node = nodeFor(id, window);
if (node) {
node.setAttribute('label', label);
@ -193,7 +198,7 @@ function setLabel(id, window, label) {
exports.setLabel = setLabel;
function setDisabled(id, window, disabled) {
let node = customizedWindows.has(window) ? null : getNode(id, window);
let node = nodeFor(id, window);
if (node)
node.disabled = disabled;
@ -201,7 +206,7 @@ function setDisabled(id, window, disabled) {
exports.setDisabled = setDisabled;
function setChecked(id, window, checked) {
let node = customizedWindows.has(window) ? null : getNode(id, window);
let node = nodeFor(id, window);
if (node)
node.checked = checked;
@ -209,8 +214,7 @@ function setChecked(id, window, checked) {
exports.setChecked = setChecked;
function click(id) {
let window = getMostRecentBrowserWindow();
let node = customizedWindows.has(window) ? null : getNode(id, window);
let node = nodeFor(id);
if (node)
node.click();

Просмотреть файл

@ -32,17 +32,17 @@ const mailbox = new OutputPort({ id: "frame-mailbox" });
const input = Frames;
const urlToId = url =>
const makeID = url =>
("frame-" + addonID + "-" + url).
split("/").join("-").
split(".").join("-").
replace(/[^A-Za-z0-9_\-]/g, "");
const validate = contract({
id: {
name: {
is: ["string", "undefined"],
ok: x => /^[a-z][a-z0-9-_]+$/i.test(x),
msg: "The `option.id` must be a valid alphanumeric string (hyphens and " +
msg: "The `option.name` must be a valid alphanumeric string (hyphens and " +
"underscores are allowed) starting with letter."
},
url: {
@ -88,7 +88,7 @@ const Frame = Class({
implements: [Disposable, Source],
initialize: function(params={}) {
const options = validate(params);
const id = options.id || urlToId(options.url);
const id = makeID(options.name || options.url);
if (frames.has(id))
throw Error("Frame with this id already exists: " + id);

Просмотреть файл

@ -19,7 +19,7 @@ const { events: browserEvents } = require('../browser/events');
const { events: tabEvents } = require('../tab/events');
const { events: stateEvents } = require('./state/events');
const { windows, isInteractive, getMostRecentBrowserWindow } = require('../window/utils');
const { windows, isInteractive, getFocusedBrowser } = require('../window/utils');
const { getActiveTab, getOwnerWindow } = require('../tabs/utils');
const { ignoreWindow } = require('../private-browsing/utils');
@ -47,7 +47,7 @@ const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWind
const isEnumerable = window => !ignoreWindow(window);
const browsers = _ =>
windows('navigator:browser', { includePrivate: true }).filter(isInteractive);
const getMostRecentTab = _ => getActiveTab(getMostRecentBrowserWindow());
const getMostRecentTab = _ => getActiveTab(getFocusedBrowser());
function getStateFor(component, target) {
if (!isRegistered(component))
@ -172,7 +172,7 @@ exports.properties = properties;
function state(contract) {
return {
state: function state(target, state) {
let nativeTarget = target === 'window' ? getMostRecentBrowserWindow()
let nativeTarget = target === 'window' ? getFocusedBrowser()
: target === 'tab' ? getMostRecentTab()
: viewFor(target);

Просмотреть файл

@ -16,11 +16,13 @@ const { subscribe, send, Reactor, foldp, lift, merges } = require("../../event/u
const { InputPort } = require("../../input/system");
const { OutputPort } = require("../../output/system");
const { Interactive } = require("../../input/browser");
const { CustomizationInput } = require("../../input/customizable-ui");
const { pairs, map, isEmpty, object,
each, keys, values } = require("../../util/sequence");
const { curry, flip } = require("../../lang/functional");
const { patch, diff } = require("diffpatcher/index");
const prefs = require("../../preferences/service");
const { getByOuterId } = require("../../window/utils");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_ROOT = "extensions.sdk-toolbar-collapsed.";
@ -38,8 +40,9 @@ const syncoutput = new OutputPort({ id: "toolbar-change", sync: true });
// date.
const Toolbars = foldp(patch, {}, merges([new InputPort({ id: "toolbar-changed" }),
new InputPort({ id: "toolbar-change" })]));
const State = lift((toolbars, windows) => ({windows: windows, toolbars: toolbars}),
Toolbars, Interactive);
const State = lift((toolbars, windows, customizable) =>
({windows: windows, toolbars: toolbars, customizable: customizable}),
Toolbars, Interactive, new CustomizationInput());
// Shared event handler that makes `event.target.parent` collapsed.
// Used as toolbar's close buttons click handler.
@ -88,12 +91,17 @@ const addView = curry((options, {document}) => {
view.setAttribute("collapsed", options.collapsed);
view.setAttribute("toolbarname", options.title);
view.setAttribute("pack", "end");
view.setAttribute("defaultset", options.items.join(","));
view.setAttribute("customizable", true);
view.setAttribute("style", "max-height: 40px;");
view.setAttribute("customizable", "false");
view.setAttribute("style", "padding: 2px 0; max-height: 40px;");
view.setAttribute("mode", "icons");
view.setAttribute("iconsize", "small");
view.setAttribute("context", "toolbar-context-menu");
view.setAttribute("class", "toolbar-primary chromeclass-toolbar");
let label = document.createElementNS(XUL_NS, "label");
label.setAttribute("value", options.title);
label.setAttribute("collapsed", "true");
view.appendChild(label);
let closeButton = document.createElementNS(XUL_NS, "toolbarbutton");
closeButton.setAttribute("id", "close-" + options.id);
@ -103,6 +111,24 @@ const addView = curry((options, {document}) => {
view.appendChild(closeButton);
// In order to have a close button not costumizable, aligned on the right,
// leaving the customizable capabilities of Australis, we need to create
// a toolbar inside a toolbar.
// This is should be a temporary hack, we should have a proper XBL for toolbar
// instead. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=982005
let toolbar = document.createElementNS(XUL_NS, "toolbar");
toolbar.setAttribute("id", "inner-" + options.id);
toolbar.setAttribute("defaultset", options.items.join(","));
toolbar.setAttribute("customizable", "true");
toolbar.setAttribute("style", "-moz-appearance: none; overflow: hidden");
toolbar.setAttribute("mode", "icons");
toolbar.setAttribute("iconsize", "small");
toolbar.setAttribute("context", "toolbar-context-menu");
toolbar.setAttribute("flex", "1");
view.insertBefore(toolbar, closeButton);
const observer = new document.defaultView.MutationObserver(attributesChanged);
observer.observe(view, { attributes: true,
attributeFilter: ["collapsed", "toolbarname"] });
@ -117,23 +143,34 @@ const removeView = curry((id, {document}) => {
if (view) view.remove();
});
const updateView = curry((id, {title, collapsed}, {document}) => {
const updateView = curry((id, {title, collapsed, isCustomizing}, {document}) => {
const view = document.getElementById(id);
if (view && title)
if (!view)
return;
if (title)
view.setAttribute("toolbarname", title);
if (view && collapsed !== void(0))
if (collapsed !== void(0))
view.setAttribute("collapsed", Boolean(collapsed));
if (isCustomizing !== void(0)) {
view.querySelector("label").collapsed = !isCustomizing;
view.querySelector("toolbar").style.visibility = isCustomizing
? "hidden" : "visible";
}
});
const viewUpdate = curry(flip(updateView));
// Utility function used to register toolbar into CustomizableUI.
const registerToolbar = state => {
// If it's first additon register toolbar as customizableUI component.
CustomizableUI.registerArea(state.id, {
CustomizableUI.registerArea("inner-" + state.id, {
type: CustomizableUI.TYPE_TOOLBAR,
legacy: true,
defaultPlacements: [...state.items, "close-" + state.id]
defaultPlacements: [...state.items]
});
};
// Utility function used to unregister toolbar from the CustomizableUI.
@ -148,7 +185,7 @@ const reactor = new Reactor({
// we unregister toolbar and remove it from each window
// it was added to.
if (update === null) {
unregisterToolbar(id);
unregisterToolbar("inner-" + id);
each(removeView(id), values(past.windows));
send(output, object([id, null]));
@ -190,10 +227,16 @@ const reactor = new Reactor({
if (window)
each(viewAdd(window), values(past.toolbars));
}, values(delta.windows));
each(([id, isCustomizing]) => {
each(viewUpdate(getByOuterId(id), {isCustomizing: !!isCustomizing}),
keys(present.toolbars));
}, pairs(delta.customizable))
},
onEnd: state => {
each(id => {
unregisterToolbar(id);
unregisterToolbar("inner-" + id);
each(removeView(id), values(state.windows));
}, keys(state.toolbars));
}

Просмотреть файл

@ -444,28 +444,10 @@ const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
// Special case for click events: if the widget doesn't have a click
// handler, but it does have a panel, display the panel.
if ("click" == type && !this._listeners("click").length && this.panel) {
// In Australis, widgets may be positioned in an overflow panel or the
// menu panel.
// In such cases clicking this widget will hide the overflow/menu panel,
// and the widget's panel will show instead.
let anchor = domNode;
let { CustomizableUI, window } = domNode.ownerDocument.defaultView;
if (CustomizableUI) {
({anchor}) = CustomizableUI.getWidget(domNode.id).forWindow(window);
// if `anchor` is not the `domNode` itself, it means the widget is
// positioned in a panel, therefore we have to hide it before show
// the widget's panel in the same anchor
if (anchor !== domNode)
CustomizableUI.hidePanelForNode(domNode);
}
// This kind of ugly workaround, instead we should implement
// `getNodeView` for the `Widget` class itself, but that's kind of
// hard without cleaning things up.
this.panel.show(null, getNodeView.implement({}, () => anchor));
this.panel.show(null, getNodeView.implement({}, () => domNode));
}
},
@ -795,8 +777,10 @@ WidgetChrome.prototype._createNode = function WC__createNode() {
// Initial population of a widget's content.
WidgetChrome.prototype.fill = function WC_fill() {
let { node, _doc: document } = this;
// Create element
var iframe = this._doc.createElement("iframe");
let iframe = document.createElement("iframe");
iframe.setAttribute("type", "content");
iframe.setAttribute("transparent", "transparent");
iframe.style.overflow = "hidden";
@ -809,14 +793,23 @@ WidgetChrome.prototype.fill = function WC_fill() {
// Do this early, because things like contentWindow are null
// until the node is attached to a document.
this.node.appendChild(iframe);
node.appendChild(iframe);
var label = this._doc.createElement("label");
let label = document.createElement("label");
label.setAttribute("value", this._widget.label);
label.className = "toolbarbutton-text";
label.setAttribute("crop", "right");
label.setAttribute("flex", "1");
this.node.appendChild(label);
node.appendChild(label);
// This toolbarbutton is created to provide a more consistent user experience
// during customization, see:
// https://bugzilla.mozilla.org/show_bug.cgi?id=959640
let button = document.createElement("toolbarbutton");
button.setAttribute("label", this._widget.label);
button.setAttribute("crop", "right");
button.className = "toolbarbutton-1 chromeclass-toolbar-additional";
node.appendChild(button);
// add event handlers
this.addEventHandlers();

Просмотреть файл

@ -19,6 +19,8 @@ const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
getService(Ci.nsIWindowMediator);
const io = Cc['@mozilla.org/network/io-service;1'].
getService(Ci.nsIIOService);
const FM = Cc["@mozilla.org/focus-manager;1"].
getService(Ci.nsIFocusManager);
const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
@ -356,6 +358,18 @@ function getFocusedWindow() {
}
exports.getFocusedWindow = getFocusedWindow;
/**
* Returns the focused browser window if any, or the most recent one.
* Opening new window, updates most recent window, but focus window
* changes later; so most recent window and focused window are not always
* the same.
*/
function getFocusedBrowser() {
let window = FM.activeWindow;
return isBrowser(window) ? window : getMostRecentBrowserWindow()
}
exports.getFocusedBrowser = getFocusedBrowser;
/**
* Returns the focused element in the most recent focused window
*/

Просмотреть файл

@ -543,11 +543,9 @@ def initializer(env_root, args, out=sys.stdout, err=sys.stderr):
if existing:
print >>err, 'This command must be run in an empty directory.'
return {"result":1}
for d in ['lib','data','test','doc']:
for d in ['lib','data','test']:
os.mkdir(os.path.join(path,d))
print >>out, '*', d, 'directory created'
open(os.path.join(path,'README.md'),'w').write('')
print >>out, '* README.md written'
jid = create_jid()
print >>out, '* generated jID automatically:', jid
open(os.path.join(path,'package.json'),'w').write(PACKAGE_JSON % {'name':addon.lower(),
@ -558,8 +556,6 @@ def initializer(env_root, args, out=sys.stdout, err=sys.stderr):
print >>out, '* test/test-main.js written'
open(os.path.join(path,'lib','main.js'),'w').write('')
print >>out, '* lib/main.js written'
open(os.path.join(path,'doc','main.md'),'w').write('')
print >>out, '* doc/main.md written'
if len(args) == 1:
print >>out, '\nYour sample add-on is now ready.'
print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!'

Просмотреть файл

@ -736,7 +736,6 @@ def run_app(harness_root_dir, manifest_rdf, harness_options,
if os.path.exists(resultfile):
result = open(resultfile).read()
if result:
sys.stderr.write("resultfile contained " + "'" + result + "'\n")
if result in ['OK', 'FAIL']:
done = True
else:
@ -755,9 +754,7 @@ def run_app(harness_root_dir, manifest_rdf, harness_options,
else:
runner.wait(10)
finally:
sys.stderr.write("Done.\n")
outf.close()
sys.stderr.write("Clean the profile.\n")
if profile:
profile.cleanup()

Просмотреть файл

@ -3,18 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.metadata = {
engines: {
'Firefox': '*'
}
};
const { isTabOpen, activateTab, openTab,
closeTab, getTabURL, getWindowHoldingTab } = require('sdk/tabs/utils');
const windows = require('sdk/deprecated/window-utils');
const { LoaderWithHookedConsole } = require('sdk/test/loader');
const { setTimeout } = require('sdk/timers');
const { is } = require('sdk/system/xul-app');
const app = require("sdk/system/xul-app");
const tabs = require('sdk/tabs');
const isAustralis = "gCustomizeMode" in windows.activeBrowserWindow;
const { set: setPref } = require("sdk/preferences/service");
@ -44,6 +38,10 @@ function isChromeVisible(window) {
return x !== 'true';
}
// Once Bug 903018 is resolved, just move the application testing to
// module.metadata.engines
if (app.is('Firefox')) {
exports['test add-on page deprecation message'] = function(assert) {
let { loader, messages } = LoaderWithHookedConsole(module);
loader.require('sdk/addon-page');
@ -74,7 +72,7 @@ exports['test that add-on page has no chrome'] = function(assert, done) {
setTimeout(function() {
activateTab(tab);
assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis,
'chrome is not visible for addon page');
closeTabPromise(tab).then(function() {
@ -100,7 +98,7 @@ exports['test that add-on page with hash has no chrome'] = function(assert, done
setTimeout(function() {
activateTab(tab);
assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis,
'chrome is not visible for addon page');
closeTabPromise(tab).then(function() {
@ -126,7 +124,7 @@ exports['test that add-on page with querystring has no chrome'] = function(asser
setTimeout(function() {
activateTab(tab);
assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis,
'chrome is not visible for addon page');
closeTabPromise(tab).then(function() {
@ -152,7 +150,7 @@ exports['test that add-on page with hash and querystring has no chrome'] = funct
setTimeout(function() {
activateTab(tab);
assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis,
'chrome is not visible for addon page');
closeTabPromise(tab).then(function() {
@ -185,4 +183,8 @@ exports['test that malformed uri is not an addon-page'] = function(assert, done)
});
};
} else {
exports['test unsupported'] = (assert) => assert.pass('This application is unsupported.');
}
require('sdk/test/runner').runTestsFromModule(module);

Просмотреть файл

@ -4,29 +4,36 @@
"use strict";
const { exec } = require("sdk/system/child_process");
const { platform, pathFor } = require("sdk/system");
const PROFILE_DIR = pathFor("ProfD");
const isWindows = platform.toLowerCase().indexOf("win") === 0;
/**
* Ensures using child_process and underlying subprocess.jsm
* works within an addon
*/
exports["test child_process in an addon"] = (assert, done) => {
exec(isWindows ? "DIR /A-D" : "ls -al", {
cwd: PROFILE_DIR
}, (err, stdout, stderr) => {
assert.ok(!err, "no errors");
assert.equal(stderr, "", "stderr is empty");
assert.ok(/extensions\.ini/.test(stdout), "stdout output of `ls -al` finds files");
if (isWindows)
assert.ok(!/<DIR>/.test(stdout), "passing args works");
else
assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), "passing args works");
done();
});
};
const { exec } = require("sdk/system/child_process");
const { platform, pathFor } = require("sdk/system");
const PROFILE_DIR = pathFor("ProfD");
const isWindows = platform.toLowerCase().indexOf("win") === 0;
const app = require("sdk/system/xul-app");
// Once Bug 903018 is resolved, just move the application testing to
// module.metadata.engines
if (app.is("Firefox")) {
exports["test child_process in an addon"] = (assert, done) => {
exec(isWindows ? "DIR /A-D" : "ls -al", {
cwd: PROFILE_DIR
}, (err, stdout, stderr) => {
assert.ok(!err, "no errors");
assert.equal(stderr, "", "stderr is empty");
assert.ok(/extensions\.ini/.test(stdout), "stdout output of `ls -al` finds files");
if (isWindows)
assert.ok(!/<DIR>/.test(stdout), "passing args works");
else
assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), "passing args works");
done();
});
};
} else {
exports["test unsupported"] = (assert) => assert.pass("This application is unsupported.");
}
require("sdk/test/runner").runTestsFromModule(module);

Просмотреть файл

@ -4,21 +4,24 @@
'use strict';
module.metadata = {
'engines': {
'Firefox': '*'
}
};
const { safeMerge: merge } = require('sdk/util/object');
const app = require("sdk/system/xul-app");
merge(module.exports,
require('./tests/test-places-bookmarks'),
require('./tests/test-places-events'),
require('./tests/test-places-favicon'),
require('./tests/test-places-history'),
require('./tests/test-places-host'),
require('./tests/test-places-utils')
);
// Once Bug 903018 is resolved, just move the application testing to
// module.metadata.engines
if (app.is('Firefox')) {
merge(module.exports,
require('./tests/test-places-bookmarks'),
require('./tests/test-places-events'),
require('./tests/test-places-favicon'),
require('./tests/test-places-history'),
require('./tests/test-places-host'),
require('./tests/test-places-utils')
);
} else {
exports['test unsupported'] = (assert) => {
assert.pass('This application is unsupported.');
};
}
require('sdk/test/runner').runTestsFromModule(module);

Просмотреть файл

@ -12,46 +12,54 @@ const { preferencesBranch } = require('sdk/self');
const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
exports.testAOMLocalization = function(assert, done) {
tabs.open({
url: 'about:addons',
onReady: function(tab) {
tab.attach({
contentScriptWhen: 'end',
contentScript: 'function onLoad() {\n' +
'unsafeWindow.removeEventListener("load", onLoad, false);\n' +
'AddonManager.getAddonByID("' + self.id + '", function(aAddon) {\n' +
'unsafeWindow.gViewController.viewObjects.detail.node.addEventListener("ViewChanged", function whenViewChanges() {\n' +
'unsafeWindow.gViewController.viewObjects.detail.node.removeEventListener("ViewChanged", whenViewChanges, false);\n' +
'setTimeout(function() {\n' + // TODO: figure out why this is necessary..
'self.postMessage({\n' +
'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[data-jetpack-id=\'' + self.id + '\']"))\n' +
'});\n' +
'}, 250);\n' +
'}, false);\n' +
'unsafeWindow.gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);\n' +
'});\n' +
'function getAttributes(ele) {\n' +
'if (!ele) return {};\n' +
'return {\n' +
'title: ele.getAttribute("title")\n' +
// Once Bug 903018 is resolved, just move the application testing to
// module.metadata.engines
//
// This should work in Fennec, needs to be refactored to work, via bug 979645
if (app.is('Firefox')) {
exports.testAOMLocalization = function(assert, done) {
tabs.open({
url: 'about:addons',
onReady: function(tab) {
tab.attach({
contentScriptWhen: 'end',
contentScript: 'function onLoad() {\n' +
'unsafeWindow.removeEventListener("load", onLoad, false);\n' +
'AddonManager.getAddonByID("' + self.id + '", function(aAddon) {\n' +
'unsafeWindow.gViewController.viewObjects.detail.node.addEventListener("ViewChanged", function whenViewChanges() {\n' +
'unsafeWindow.gViewController.viewObjects.detail.node.removeEventListener("ViewChanged", whenViewChanges, false);\n' +
'setTimeout(function() {\n' + // TODO: figure out why this is necessary..
'self.postMessage({\n' +
'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[data-jetpack-id=\'' + self.id + '\']"))\n' +
'});\n' +
'}, 250);\n' +
'}, false);\n' +
'unsafeWindow.gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);\n' +
'});\n' +
'function getAttributes(ele) {\n' +
'if (!ele) return {};\n' +
'return {\n' +
'title: ele.getAttribute("title")\n' +
'}\n' +
'}\n' +
'}\n' +
'}\n' +
// Wait for the load event ?
'if (document.readyState == "complete") {\n' +
'onLoad()\n' +
'} else {\n' +
'unsafeWindow.addEventListener("load", onLoad, false);\n' +
'}\n',
onMessage: function(msg) {
// test somePreference
assert.equal(msg.somePreference.title, 'A', 'somePreference title is correct');
tab.close(done);
}
});
}
});
// Wait for the load event ?
'if (document.readyState == "complete") {\n' +
'onLoad()\n' +
'} else {\n' +
'unsafeWindow.addEventListener("load", onLoad, false);\n' +
'}\n',
onMessage: function(msg) {
// test somePreference
assert.equal(msg.somePreference.title, 'A', 'somePreference title is correct');
tab.close(done);
}
});
}
});
}
} else {
exports['test unsupported'] = (assert) => assert.pass('This test is unsupported.');
}
require('sdk/test/runner').runTestsFromModule(module);

Просмотреть файл

@ -46,9 +46,9 @@ exports["test frame API"] = function*(assert) {
/The `options.url`/,
"options.url must be local url");
assert.throws(() => new Frame({url: url, id: "4you" }),
/The `option.id` must be a valid/,
"can only take valid ID's");
assert.throws(() => new Frame({url: url, name: "4you" }),
/The `option.name` must be a valid/,
"can only take valid names");
const f1 = new Frame({ url: url });
@ -64,8 +64,8 @@ exports["test frame API"] = function*(assert) {
"can't have two identical frames");
const f2 = new Frame({ id: "frame-2", url: url });
assert.pass("can create frame with same url but diff id");
const f2 = new Frame({ name: "frame-2", url: url });
assert.pass("can create frame with same url but diff name");
const p2 = wait(f2, "register");
yield p1;
@ -145,7 +145,7 @@ exports["test host to content messaging"] = function*(assert) {
event.source.postMessage("pong!", event.origin);
});
} + "</script>";
const f1 = new Frame({ id: "mailbox", url: url });
const f1 = new Frame({ name: "mailbox", url: url });
const t1 = new Toolbar({ title: "mailbox", items: [f1] });
const e1 = yield wait(f1, "ready");
@ -169,7 +169,7 @@ exports["test content to host messaging"] = function*(assert) {
window.parent.postMessage("ping!", "*");
} + "</script>";
const f1 = new Frame({ id: "inbox", url: url });
const f1 = new Frame({ name: "inbox", url: url });
const t1 = new Toolbar({ title: "inbox", items: [f1] });
const e1 = yield wait(f1, "message");
@ -197,7 +197,7 @@ exports["test direct messaging"] = function*(assert) {
} + "</script>";
const w1 = getMostRecentBrowserWindow();
const f1 = new Frame({ url: url, id: "mail-cluster" });
const f1 = new Frame({ url: url, name: "mail-cluster" });
const t1 = new Toolbar({ title: "claster", items: [f1] });
yield wait(f1, "ready");

Просмотреть файл

@ -1020,6 +1020,44 @@ exports['test button click do not messing up states'] = function(assert) {
loader.unload();
}
exports['test buttons can have anchored panels'] = function(assert, done) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
let { Panel } = loader.require('sdk/panel');
let { identify } = loader.require('sdk/ui/id');
let { getActiveView } = loader.require('sdk/view/core');
let button = ToggleButton({
id: 'my-button-22',
label: 'my button',
icon: './icon.png',
onChange: ({checked}) => checked && panel.show({position: button})
});
let panel = Panel();
panel.once('show', () => {
let { document } = getMostRecentBrowserWindow();
let buttonNode = document.getElementById(identify(button));
let panelNode = getActiveView(panel);
assert.ok(button.state('window').checked,
'button is checked');
assert.equal(panelNode.getAttribute('type'), 'arrow',
'the panel is a arrow type');
assert.strictEqual(buttonNode, panelNode.anchorNode,
'the panel is anchored properly to the button');
loader.unload();
done();
});
button.click();
}
// If the module doesn't support the app we're being run in, require() will
// throw. In that case, remove all tests above from exports, and add one dummy
// test that passes.

Просмотреть файл

@ -12,11 +12,12 @@ module.metadata = {
const { Toolbar } = require("sdk/ui/toolbar");
const { Loader } = require("sdk/test/loader");
const { identify } = require("sdk/ui/id");
const { getMostRecentBrowserWindow, open } = require("sdk/window/utils");
const { getMostRecentBrowserWindow, open, getOuterId } = require("sdk/window/utils");
const { ready, close } = require("sdk/window/helpers");
const { defer } = require("sdk/core/promise");
const { send } = require("sdk/event/utils");
const { send, stop, Reactor } = require("sdk/event/utils");
const { object } = require("sdk/util/sequence");
const { CustomizationInput } = require("sdk/input/customizable-ui");
const { OutputPort } = require("sdk/output/system");
const output = new OutputPort({ id: "toolbar-change" });
@ -357,4 +358,120 @@ exports["test title change"] = function*(assert) {
yield close(w2);
};
exports["test toolbar is not customizable"] = function*(assert, done) {
const { window, document, gCustomizeMode } = getMostRecentBrowserWindow();
const outerId = getOuterId(window);
const input = new CustomizationInput();
const customized = defer();
const customizedEnd = defer();
new Reactor({ onStep: value => {
if (value[outerId] === true)
customized.resolve();
if (value[outerId] === null)
customizedEnd.resolve();
}}).run(input);
const toolbar = new Toolbar({ title: "foo" });
yield wait(toolbar, "attach");
let view = document.getElementById(toolbar.id);
let label = view.querySelector("label");
let inner = view.querySelector("toolbar");
assert.equal(view.getAttribute("customizable"), "false",
"The outer toolbar is not customizable.");
assert.ok(label.collapsed,
"The label is not displayed.")
assert.equal(inner.getAttribute("customizable"), "true",
"The inner toolbar is customizable.");
assert.equal(window.getComputedStyle(inner).visibility, "visible",
"The inner toolbar is visible.");
// Enter in customization mode
gCustomizeMode.toggle();
yield customized.promise;
assert.equal(view.getAttribute("customizable"), "false",
"The outer toolbar is not customizable.");
assert.equal(label.collapsed, false,
"The label is displayed.")
assert.equal(inner.getAttribute("customizable"), "true",
"The inner toolbar is customizable.");
assert.equal(window.getComputedStyle(inner).visibility, "hidden",
"The inner toolbar is hidden.");
// Exit from customization mode
gCustomizeMode.toggle();
yield customizedEnd.promise;
assert.equal(view.getAttribute("customizable"), "false",
"The outer toolbar is not customizable.");
assert.ok(label.collapsed,
"The label is not displayed.")
assert.equal(inner.getAttribute("customizable"), "true",
"The inner toolbar is customizable.");
assert.equal(window.getComputedStyle(inner).visibility, "visible",
"The inner toolbar is visible.");
toolbar.destroy();
};
exports["test button are attached to toolbar"] = function*(assert) {
const { document } = getMostRecentBrowserWindow();
const { ActionButton, ToggleButton } = require("sdk/ui");
const { identify } = require("sdk/ui/id");
let action = ActionButton({
id: "btn-1",
label: "action",
icon: "./placeholder.png"
});
let toggle = ToggleButton({
id: "btn-2",
label: "toggle",
icon: "./placeholder.png"
});
const toolbar = new Toolbar({
title: "foo",
items: [action, toggle]
});
yield wait(toolbar, "attach");
let actionNode = document.getElementById(identify(action));
let toggleNode = document.getElementById(identify(toggle));
assert.notEqual(actionNode, null,
"action button exists in the document");
assert.notEqual(actionNode, null,
"action button exists in the document");
assert.notEqual(toggleNode, null,
"toggle button exists in the document");
assert.equal(actionNode.nextElementSibling, toggleNode,
"action button is placed before toggle button");
assert.equal(actionNode.parentNode.parentNode.id, toolbar.id,
"buttons are placed in the correct toolbar");
toolbar.destroy();
};
require("sdk/test").run(exports);

Просмотреть файл

@ -444,7 +444,10 @@
accesskey="&toolsMenu.accesskey;">
<menupopup id="menu_ToolsPopup"
#ifdef MOZ_SERVICES_SYNC
onpopupshowing="gSyncUI.updateUI();"
# We have to use setTimeout() here to avoid a flickering menu bar when opening
# the Tools menu, see bug 970769. This can be removed once we got rid of the
# event loop spinning in Weave.Status._authManager.
onpopupshowing="setTimeout(() => gSyncUI.updateUI());"
#endif
>
<menuitem id="menu_openDownloads"

Просмотреть файл

@ -1094,6 +1094,7 @@ let BookmarkingUI = {
let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar");
if (viewToolbarMenuitem) {
// Update View bookmarks toolbar checkbox menuitem.
viewToolbarMenuitem.classList.add("subviewbutton");
let personalToolbar = document.getElementById("PersonalToolbar");
viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
}
@ -1106,7 +1107,8 @@ let BookmarkingUI = {
new PlacesMenu(event, "place:folder=BOOKMARKS_MENU", {
extraClasses: {
mainLevel: "subviewbutton"
entry: "subviewbutton",
footer: "panel-subview-footer"
},
insertionPoint: ".panel-subview-footer"
});
@ -1449,7 +1451,8 @@ let BookmarkingUI = {
"panelMenu_bookmarksMenu",
"panelMenu_bookmarksMenu", {
extraClasses: {
mainLevel: "subviewbutton"
entry: "subviewbutton",
footer: "panel-subview-footer"
}
});
aEvent.target.removeEventListener("ViewShowing", this);

Просмотреть файл

@ -175,8 +175,8 @@
<broadcaster id="tabviewGroupsNumber" groups="1"/>
#ifdef MOZ_SERVICES_SYNC
<broadcaster id="sync-setup-state"/>
<broadcaster id="sync-syncnow-state"/>
<broadcaster id="sync-reauth-state"/>
<broadcaster id="sync-syncnow-state" hidden="true"/>
<broadcaster id="sync-reauth-state" hidden="true"/>
#endif
<broadcaster id="workOfflineMenuitemState"/>
<broadcaster id="socialSidebarBroadcaster" hidden="true"/>

Просмотреть файл

@ -239,12 +239,18 @@ toolbar[customizing] > .overflow-button {
%endif
/* Rules to help integrate SDK widgets */
toolbaritem[sdkstylewidget="true"] > toolbarbutton,
toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > iframe,
toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-text {
display: none;
}
toolbarpaletteitem:-moz-any([place="palette"], [place="panel"]) > toolbaritem[sdkstylewidget="true"] > toolbarbutton {
display: -moz-box;
}
toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > iframe {
display: none;
toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"] > .toolbarbutton-text {
display: -moz-box;
}
toolbarpaletteitem[removable="false"] {

Просмотреть файл

@ -826,7 +826,8 @@
placespopup="true"
context="placesContext"
onpopupshowing="if (!this.parentNode._placesView)
new PlacesMenu(event, 'place:folder=TOOLBAR');">
new PlacesMenu(event, 'place:folder=TOOLBAR',
PlacesUIUtils.getViewForNode(this.parentNode.parentNode).options);">
<menuitem id="BMB_viewBookmarksToolbar"
placesanonid="view-toolbar"
toolbarId="PersonalToolbar"
@ -845,7 +846,8 @@
placespopup="true"
context="placesContext"
onpopupshowing="if (!this.parentNode._placesView)
new PlacesMenu(event, 'place:folder=UNFILED_BOOKMARKS');"/>
new PlacesMenu(event, 'place:folder=UNFILED_BOOKMARKS',
PlacesUIUtils.getViewForNode(this.parentNode.parentNode).options);"/>
</menu>
<menuseparator/>
<!-- Bookmarks menu items will go here -->

Просмотреть файл

@ -2074,21 +2074,10 @@
</field>
<property name="_viewsLeftMap">
<getter><![CDATA[
let viewsLeftMap = {};
try {
viewsLeftMap = JSON.parse(Services.prefs.getCharPref("browser.syncPromoViewsLeftMap"));
} catch (ex) {
// If the old preference exists, migrate it to the new one.
try {
let oldPref = Services.prefs.getIntPref("browser.syncPromoViewsLeft");
Services.prefs.clearUserPref("browser.syncPromoViewsLeft");
viewsLeftMap.bookmarks = oldPref;
viewsLeftMap.passwords = oldPref;
Services.prefs.setCharPref("browser.syncPromoViewsLeftMap",
JSON.stringify(viewsLeftMap));
} catch (ex2) {}
}
return viewsLeftMap;
return JSON.parse(Services.prefs.getCharPref("browser.syncPromoViewsLeftMap"));
} catch (ex) {}
return {};
]]></getter>
</property>
<property name="_viewsLeft">

Просмотреть файл

@ -325,6 +325,11 @@
</method>
<method name="_syncContainerWithSubView">
<body><![CDATA[
// Check that this panel is still alive:
if (!this._panel || !this._panel.parentNode) {
return;
}
if (!this.ignoreMutations && this.showingSubView) {
let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
this._viewContainer.style.height = newHeight + "px";
@ -333,6 +338,11 @@
</method>
<method name="_syncContainerWithMainView">
<body><![CDATA[
// Check that this panel is still alive:
if (!this._panel || !this._panel.parentNode) {
return;
}
if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
let height;
if (this.showingSubViewAsMainView) {

Просмотреть файл

@ -1082,16 +1082,15 @@ let CustomizableUIInternal = {
let node;
if (aWidget.type == "custom") {
if (aWidget.onBuild) {
try {
node = aWidget.onBuild(aDocument);
} catch (ex) {
ERROR("Custom widget with id " + aWidget.id + " threw an error: " + ex.message);
}
node = aWidget.onBuild(aDocument);
}
if (!node || !(node instanceof aDocument.defaultView.XULElement))
ERROR("Custom widget with id " + aWidget.id + " does not return a valid node");
}
else {
if (aWidget.onBeforeCreated) {
aWidget.onBeforeCreated(aDocument);
}
node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
node.setAttribute("id", aWidget.id);
@ -1971,6 +1970,7 @@ let CustomizableUIInternal = {
widget.disabled = aData.disabled === true;
this.wrapWidgetEventHandler("onBeforeCreated", widget);
this.wrapWidgetEventHandler("onClick", widget);
this.wrapWidgetEventHandler("onCreated", widget);
@ -2728,6 +2728,12 @@ this.CustomizableUI = {
* function that will be invoked with the document in which
* to build a widget. Should return the DOM node that has
* been constructed.
* - onBeforeCreated(aDoc): Attached to all non-custom widgets; a function
* that will be invoked before the widget gets a DOM node
* constructed, passing the document in which that will happen.
* This is useful especially for 'view' type widgets that need
* to construct their views on the fly (e.g. from bootstrapped
* add-ons)
* - onCreated(aNode): Attached to all widgets; a function that will be invoked
* whenever the widget has a DOM node constructed, passing the
* constructed node as an argument.

Просмотреть файл

@ -75,4 +75,5 @@ skip-if = os == "linux"
[browser_975719_customtoolbars_behaviour.js]
[browser_978084_dragEnd_after_move.js]
[browser_980155_add_overflow_toolbar.js]
[browser_981418-widget-onbeforecreated-handler.js]
[browser_panel_toggle.js]

Просмотреть файл

@ -0,0 +1,90 @@
/* 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";
const kWidgetId = 'test-981418-widget-onbeforecreated';
// Should be able to add broken view widget
add_task(function testAddOnBeforeCreatedWidget() {
let viewShownDeferred = Promise.defer();
let onBeforeCreatedCalled = false;
let widgetSpec = {
id: kWidgetId,
type: 'view',
viewId: kWidgetId + 'idontexistyet',
onBeforeCreated: function(doc) {
let view = doc.createElement("panelview");
view.id = kWidgetId + 'idontexistyet';
let label = doc.createElement("label");
label.setAttribute("value", "Hello world");
label.className = 'panel-subview-header';
view.appendChild(label);
document.getElementById("PanelUI-multiView").appendChild(view);
onBeforeCreatedCalled = true;
},
onViewShowing: function() {
viewShownDeferred.resolve();
}
};
let noError = true;
try {
CustomizableUI.createWidget(widgetSpec);
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Should not throw an exception trying to add the widget.");
ok(onBeforeCreatedCalled, "onBeforeCreated should have been called");
let widgetNode = document.getElementById(kWidgetId);
ok(widgetNode, "Widget should exist");
if (widgetNode) {
try {
widgetNode.click();
let shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000);
yield viewShownDeferred.promise;
clearTimeout(shownTimeout);
ok(true, "Found view shown");
let tempPanel = document.getElementById("customizationui-widget-panel");
let panelHiddenPromise = promisePanelElementHidden(window, tempPanel);
tempPanel.hidePopup();
yield panelHiddenPromise;
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL);
yield PanelUI.show();
viewShownDeferred = Promise.defer();
widgetNode.click();
shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000);
yield viewShownDeferred.promise;
clearTimeout(shownTimeout);
ok(true, "Found view shown");
let panelHidden = promisePanelHidden(window);
PanelUI.hide();
yield panelHidden;
} catch (ex) {
ok(false, "Unexpected exception (like a timeout for one of the yields) " +
"when testing view widget.");
}
}
noError = true;
try {
CustomizableUI.destroyWidget(kWidgetId);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Should not throw an exception trying to remove the broken view widget.");
});
add_task(function asyncCleanup() {
yield resetCustomization();
});

Просмотреть файл

@ -1292,7 +1292,7 @@ BrowserGlue.prototype = {
},
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 21;
const UI_VERSION = 22;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
let currentUIVersion = 0;
try {
@ -1574,6 +1574,12 @@ BrowserGlue.prototype = {
}
}
if (currentUIVersion < 22) {
// Reset the Sync promobox count to promote the new FxAccount-based Sync.
Services.prefs.clearUserPref("browser.syncPromoViewsLeft");
Services.prefs.clearUserPref("browser.syncPromoViewsLeftMap");
}
if (this._dirty)
this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();

Просмотреть файл

@ -268,6 +268,9 @@ PlacesViewBase.prototype = {
aPopup._emptyMenuitem = document.createElement("menuitem");
aPopup._emptyMenuitem.setAttribute("label", label);
aPopup._emptyMenuitem.setAttribute("disabled", true);
aPopup._emptyMenuitem.className = "bookmark-item";
if (typeof this.options.extraClasses.entry == "string")
aPopup._emptyMenuitem.classList.add(this.options.extraClasses.entry);
}
if (aEmpty) {
@ -339,6 +342,9 @@ PlacesViewBase.prototype = {
element.appendChild(popup);
element.className = "menu-iconic bookmark-item";
if (typeof this.options.extraClasses.entry == "string") {
element.classList.add(this.options.extraClasses.entry);
}
this._domNodes.set(aPlacesNode, popup);
}
@ -365,11 +371,8 @@ PlacesViewBase.prototype = {
let before = aBefore || aPopup._endMarker;
if (element.localName == "menuitem" || element.localName == "menu") {
let extraClasses = this.options.extraClasses;
if (aPopup == this._rootElt && typeof extraClasses.mainLevel == "string") {
element.classList.add(extraClasses.mainLevel);
} else if (typeof extraClasses.secondaryLevel == "string")
element.classList.add(extraClasses.secondaryLevel);
if (typeof this.options.extraClasses.entry == "string")
element.classList.add(this.options.extraClasses.entry);
}
aPopup.insertBefore(element, before);
@ -782,6 +785,12 @@ PlacesViewBase.prototype = {
// Add the "Open All in Tabs" menuitem.
aPopup._endOptOpenAllInTabs = document.createElement("menuitem");
aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
if (typeof this.options.extraClasses.entry == "string")
aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.entry);
if (typeof this.options.extraClasses.footer == "string")
aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer);
aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
"PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
"PlacesUIUtils.getViewForNode(this));");
@ -1802,10 +1811,9 @@ PlacesPanelMenuView.prototype = {
}
else {
button = document.createElement("toolbarbutton");
let className = "bookmark-item";
if (typeof this.options.extraClasses.mainLevel == "string")
className += " " + this.options.extraClasses.mainLevel;
button.className = className;
button.className = "bookmark-item";
if (typeof this.options.extraClasses.entry == "string")
button.classList.add(this.options.extraClasses.entry);
button.setAttribute("label", aChild.title);
let icon = aChild.icon;
if (icon)

Просмотреть файл

@ -32,7 +32,7 @@ function* checkPopupContextMenu() {
yield contextMenuHiddenPromise;
let popupHiddenPromise = onPopupEvent(BMB_menuPopup, "hidden");
// Can't use synthesizeMouseAtCenter because the dropdown panel is in the way
EventUtils.synthesizeMouse(dropmarker, 2, 2, {});
EventUtils.synthesizeKey("VK_ESCAPE", {});
info("Waiting for bookmarks menu to be hidden.");
yield popupHiddenPromise;
}

Просмотреть файл

@ -38,7 +38,7 @@ function testNormal_PopupListener() {
// Now launch the phishing test. Can't use onload here because error pages don't
// fire normal load events.
window.addEventListener("DOMContentLoaded", testPhishing, true);
content.location = "http://www.mozilla.org/firefox/its-a-trap.html";
content.location = "http://www.itisatrap.org/firefox/its-a-trap.html";
}
function testPhishing() {

Просмотреть файл

@ -13,6 +13,8 @@ support-files =
[browser_426329.js]
[browser_483086.js]
[browser_addEngine.js]
[browser_bing.js]
[browser_bing_behavior.js]
[browser_contextmenu.js]
[browser_google.js]
[browser_google_behavior.js]

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,147 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Test Bing search plugin URLs
*/
"use strict";
const BROWSER_SEARCH_PREF = "browser.search.";
let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
// Custom search parameters
const PC_PARAM_VALUE = runtime.isOfficialBranding ? "MOZI" : null;
function test() {
let engine = Services.search.getEngineByName("Bing");
ok(engine, "Bing is installed");
let previouslySelectedEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
let base = "http://www.bing.com/search?q=foo";
if (typeof(PC_PARAM_VALUE) == "string")
base += "&pc=" + PC_PARAM_VALUE;
let url;
// Test search URLs (including purposes).
url = engine.getSubmission("foo").uri.spec;
is(url, base, "Check search URL for 'foo'");
waitForExplicitFinish();
var gCurrTest;
var gTests = [
{
name: "context menu search",
searchURL: base + "&form=MOZSBR",
run: function () {
// Simulate a contextmenu search
// FIXME: This is a bit "low-level"...
BrowserSearch.loadSearch("foo", false, "contextmenu");
}
},
{
name: "keyword search",
searchURL: base + "&form=MOZLBR",
run: function () {
gURLBar.value = "? foo";
gURLBar.focus();
EventUtils.synthesizeKey("VK_RETURN", {});
}
},
{
name: "search bar search",
searchURL: base + "&form=MOZSBR",
run: function () {
let sb = BrowserSearch.searchBar;
sb.focus();
sb.value = "foo";
registerCleanupFunction(function () {
sb.value = "";
});
EventUtils.synthesizeKey("VK_RETURN", {});
}
},
{
name: "home page search",
searchURL: base + "&form=MOZSPG",
run: function () {
// load about:home, but remove the listener first so it doesn't
// get in the way
gBrowser.removeProgressListener(listener);
gBrowser.loadURI("about:home");
info("Waiting for about:home load");
tab.linkedBrowser.addEventListener("load", function load(event) {
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
event.target.location.href == "about:blank") {
info("skipping spurious load event");
return;
}
tab.linkedBrowser.removeEventListener("load", load, true);
// Observe page setup
let doc = gBrowser.contentDocument;
let mutationObserver = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
if (mutation.attributeName == "searchEngineName") {
// Re-add the listener, and perform a search
gBrowser.addProgressListener(listener);
doc.getElementById("searchText").value = "foo";
doc.getElementById("searchSubmit").click();
}
}
});
mutationObserver.observe(doc.documentElement, { attributes: true });
}, true);
}
}
];
function nextTest() {
if (gTests.length) {
gCurrTest = gTests.shift();
info("Running : " + gCurrTest.name);
executeSoon(gCurrTest.run);
} else {
finish();
}
}
let tab = gBrowser.selectedTab = gBrowser.addTab();
let listener = {
onStateChange: function onStateChange(webProgress, req, flags, status) {
info("onStateChange");
// Only care about top-level document starts
let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
Ci.nsIWebProgressListener.STATE_START;
if (!(flags & docStart) || !webProgress.isTopLevel)
return;
info("received document start");
ok(req instanceof Ci.nsIChannel, "req is a channel");
is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
info("Actual URI: " + req.URI.spec);
req.cancel(Components.results.NS_ERROR_FAILURE);
executeSoon(nextTest);
}
}
registerCleanupFunction(function () {
gBrowser.removeProgressListener(listener);
gBrowser.removeTab(tab);
Services.search.currentEngine = previouslySelectedEngine;
});
tab.linkedBrowser.addEventListener("load", function load() {
tab.linkedBrowser.removeEventListener("load", load, true);
gBrowser.addProgressListener(listener);
nextTest();
}, true);
}

Просмотреть файл

@ -36,27 +36,6 @@ default:
const GOOGLE_CLIENT = google_client;
const MOZ_DISTRIBUTION_ID = runtime.distributionID;
function getLocale() {
const localePref = "general.useragent.locale";
return getLocalizedPref(localePref, Services.prefs.getCharPref(localePref));
}
/**
* Wrapper for nsIPrefBranch::getComplexValue.
* @param aPrefName
* The name of the pref to get.
* @returns aDefault if the requested pref doesn't exist.
*/
function getLocalizedPref(aPrefName, aDefault) {
try {
return Services.prefs.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
} catch (ex) {
return aDefault;
}
return aDefault;
}
function test() {
let engine = Services.search.getEngineByName("Google");
ok(engine, "Google");
@ -102,6 +81,7 @@ function test() {
type: Ci.nsISearchEngine.TYPE_MOZSEARCH,
hidden: false,
wrappedJSObject: {
queryCharset: "UTF-8",
"_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAAAAAB9AQAAJgAAACAgAAAAAAAA8gIAAKMBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAFESURBVDjLpZNJSwNBEIXnt4lE4kHxovgT9BDwJHqPy0HEEOJBiAuCRg+KUdC4QS4KrpC4gCBGE3NQ48JsnZ6eZ3UOM6gjaePhQU93v6+qq2q0pqgeJj2S8EdJT1hr0OxBtKCD5iEd8QxDYpvhvOBAuMDKURX9C9aPu4GA1GEVkzvMg10UBfYveWAWgYAP00V01fa+R9M2bA51wJvhIn3qR+ybt3D3JNQBE5sMjCIOLFpoHzOwdsLRO22qA6R6kiZiWwxUvy/PUQZIhYZ1vFM9cvcOOsYNdcBgysISdSJBnZjJMlR0Fw8vAp0xoz5gao/h+NZBy4i/10XGwrPA+hmvDyhVRG2Avu/LwcrkFADZa16L1h330w1RNgc3DiJzCpPYRm1bpveXX11clQR28xwblHpk1vq1iP/5mcoS0CoXDZiL0vsJ+dzfl+3T/VYAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAK5SURBVFjDxVfrSxRRFJ9/Jta/oyWjF5XQm6D6EkHRgygIIgjUTcueVgqVWSRRkppEUQYWWB8ye1iGWilWlo/Ude489s7M6Zw7D9dlt53dmd29cFiWvXvO77x+51xpaaUsoSxBaUWZQ4ECy5xji2xKZDyCMlMEw6lCNiOSgwZKJK1SkcKeSealfP64t0mBjl4Ow39MkDUL0p2RSROOtqhZdeUEYM1pBl39XCg/fEeFtWcY7G9W4csvUxjlBkCsQ4Nt9QyWVfvT6RsAKXw3aoDGATZeYIt+W1kjw7cJG0RctWDTRebbKd8A6h5pwsDb70ba3w/eUr3wt/cmwgfw6Yft4TNMQaY7o1P2ncm4FT4ANQH/jQBJ2xv7kqIXEADDql8eS3+n8bku7oxNm+EDIM/dU92upb3T/NJGeaNbDx/AsbsLRUY5Xn92caWXY5d8RV6gWllxSg4fAEnTC90DQW13BLlgXR2D3dcUeDVkwOthA1bXspxILWcm3HdThcfvufB26LcJpkOEAz9NKI/lzqpSEC7feol5EWnpSeSlIxCALUkApmULdjUqxQVAQnl3D/X/yQda4QBEq2TYc12By091MQ17Bg3R88nHKlQbVmHvj89awNBLYrwT9zXY2aBAxTkGFdiSxP/Jp6FLDw+AS7GfsdJTJ2EqSO5khD43nGfBARy/ZxOQgZHe7GPM1jzUvChUtmnBAXQPcKGMJp3fdFGq6NByEhiAO4b/YptFfQJwNyQ/bZkVQGcf90Ja25ndIyrKBOa/f8wIpwi3X1G8UcxNu7ozUS7tiH0jBswwS3RIaF1w6LYKU/ML2+8sGnjygQswtKrVIy/Qd9qQP6LnO64q4fPAKpxyZIymHo1jWk6p1ag2BsdNwQMHcC+M5kHFJX+YlPxpVlbCx2mZ5DzPI04k4kUwHHdskU3pH76iftG8yWlkAAAAAElFTkSuQmCC",
_urls : [
{

Просмотреть файл

@ -36,21 +36,6 @@ default:
const GOOGLE_CLIENT = google_client;
const MOZ_DISTRIBUTION_ID = runtime.distributionID;
function getLocale() {
const localePref = "general.useragent.locale";
return getLocalizedPref(localePref, Services.prefs.getCharPref(localePref));
}
function getLocalizedPref(aPrefName, aDefault) {
try {
return Services.prefs.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
} catch (ex) {
return aDefault;
}
return aDefault;
}
function test() {
let engine = Services.search.getEngineByName("Google");
ok(engine, "Google is installed");

Просмотреть файл

@ -51,6 +51,27 @@ function isSubObjectOf(expectedObj, actualObj, name) {
}
}
function getLocale() {
const localePref = "general.useragent.locale";
return getLocalizedPref(localePref, Services.prefs.getCharPref(localePref));
}
/**
* Wrapper for nsIPrefBranch::getComplexValue.
* @param aPrefName
* The name of the pref to get.
* @returns aDefault if the requested pref doesn't exist.
*/
function getLocalizedPref(aPrefName, aDefault) {
try {
return Services.prefs.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
} catch (ex) {
return aDefault;
}
return aDefault;
}
function waitForPopupShown(aPopupId, aCallback) {
let popup = document.getElementById(aPopupId);
info("waitForPopupShown: got popup: " + popup.id);

Просмотреть файл

@ -615,8 +615,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):hover:active > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"] > .toolbarbutton-menubutton-dropmarker:not([disabled=true]) > .dropmarker-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover:active > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"] > .toolbarbutton-icon {
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon {
background-color: rgba(154,154,154,.5);
background-image: linear-gradient(hsla(0,0%,100%,.7), hsla(0,0%,100%,.4));
border: 1px solid rgb(154,154,154);
@ -625,15 +624,16 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
transition-duration: 10ms;
}
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
background-color: rgba(90%,90%,90%,.4);
transition: background-color 150ms;
}
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:hover:active,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:hover:active {
padding: 3px;
}
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[checked="true"] {
padding: 5px !important;
}
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
content: "";
display: -moz-box;

Просмотреть файл

@ -41,10 +41,16 @@ toolbarbutton.social-provider-menuitem > .toolbarbutton-icon {
visibility: hidden;
}
menuitem.subviewbutton {
-moz-appearance: none !important;
}
menu.subviewbutton > .menu-right {
-moz-appearance: none;
list-style-image: url(chrome://browser/skin/places/bookmarks-menu-arrow.png);
-moz-image-region: rect(0, 16px, 16px, 0);
width: 16px;
height: 16px;
}
menu[disabled="true"].subviewbutton > .menu-right {

Просмотреть файл

@ -469,15 +469,17 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker,
toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],#back-button,#forward-button)),
#restore-button {
padding: 2px 6px;
padding: 0 4px;
}
toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],[disabled],[open],#back-button,#forward-button)):hover,
toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open])):hover > .toolbarbutton-menubutton-button,
toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open])):hover > .toolbarbutton-menubutton-dropmarker,
toolbar .toolbarbutton-1[type="menu-button"]:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button,
toolbar .toolbarbutton-1[type="menu-button"]:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-dropmarker,
toolbar .toolbaritem-combined-buttons:hover > .toolbarbutton-combined,
#restore-button:not([disabled]):hover {
border-color: hsla(0,0%,0%,.2);
box-shadow: 0 1px 0 hsla(0,0%,100%,.5),
0 1px 0 hsla(0,0%,100%,.5) inset;
}
toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],[disabled],[open],#back-button,#forward-button)):hover,
@ -485,11 +487,9 @@ toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open]))[b
toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open],[buttonover])):hover > .toolbarbutton-menubutton-dropmarker,
#restore-button:not([disabled]):hover {
background: hsla(0,0%,100%,.1) linear-gradient(hsla(0,0%,100%,.3), hsla(0,0%,100%,.1)) padding-box;
box-shadow: 0 1px 0 hsla(0,0%,100%,.5),
0 1px 0 hsla(0,0%,100%,.5) inset;
}
toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],[disabled],#back-button,#forward-button)):-moz-any(:hover:active,[open]),
toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],[disabled],#back-button,#forward-button)):-moz-any(:hover:active,[open],[checked]),
toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open]))[buttonover]:active > .toolbarbutton-menubutton-button,
toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open],[buttonover])):hover:active > .toolbarbutton-menubutton-dropmarker,
toolbar .toolbarbutton-1[type="menu-button"][open] > .toolbarbutton-menubutton-dropmarker,
@ -502,6 +502,11 @@ toolbar .toolbarbutton-1[type="menu-button"][open] > .toolbarbutton-menubutton-d
transition-duration: 10ms;
}
toolbar .toolbarbutton-1[checked]:not(:active):hover {
background-color: hsla(0,0%,0%,.09);
transition: background-color 250ms;
}
.toolbarbutton-1[type="menu-button"] {
padding: 0;
}
@ -536,7 +541,7 @@ toolbar .toolbarbutton-1:not(:-moz-any([open],:hover)) > .toolbarbutton-menubutt
top: calc(50% - 9px);
width: 1px;
height: 18px;
-moz-margin-end: -1px;
-moz-margin-start: -1px;
background-image: linear-gradient(hsla(0,0%,0%,.15) 0, hsla(0,0%,0%,.15) 18px);
background-clip: padding-box;
background-position: center;
@ -555,7 +560,12 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
margin: 2px 6px;
margin: 0 4px;
}
#nav-bar .toolbarbutton-1:not(#back-button):not(#forward-button) {
margin-top: 4px;
margin-bottom: 4px;
}
#nav-bar #PanelUI-button {
@ -565,17 +575,12 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
#nav-bar #PanelUI-menu-button {
margin-top: 0;
margin-bottom: 0;
padding-top: 5px;
padding-bottom: 5px;
padding-top: 1px;
padding-bottom: 1px;
-moz-margin-start: 9px;
-moz-margin-end: 7px;
}
#nav-bar > .toolbarbutton-1 {
margin-top: 4px;
margin-bottom: 4px;
}
@media not all and (min-resolution: 2dppx) {
%include ../shared/toolbarbuttons.inc.css
%include ../shared/menupanel.inc.css
@ -1038,6 +1043,7 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
/* Menu panel and palette styles */
toolbaritem[sdkstylewidget="true"] > toolbarbutton,
:-moz-any(@primaryToolbarButtons@)[cui-areatype="menu-panel"],
toolbarpaletteitem[place="palette"] > :-moz-any(@primaryToolbarButtons@) {
list-style-image: url(chrome://browser/skin/menuPanel@2x.png);
@ -1169,6 +1175,10 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
-moz-image-region: rect(0px, 1728px, 64px, 1664px);
}
toolbaritem[sdkstylewidget="true"] > toolbarbutton {
-moz-image-region: rect(0, 1664px, 64px, 1600px);
}
/* Footer and wide panel control icons */
#edit-controls@inAnyPanel@ > toolbarbutton,
#zoom-controls@inAnyPanel@ > toolbarbutton,
@ -1226,8 +1236,7 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button,
}
/* Help 16px icons fit: */
.toolbarbutton-1[cui-areatype="toolbar"]:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
.toolbarbutton-1[cui-areatype="toolbar"] > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
.toolbarbutton-1[cui-areatype="toolbar"]:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon {
margin: 2px;
}
@ -1589,7 +1598,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
#zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button > .toolbarbutton-text {
padding-top: 6px;
padding-top: 4px;
margin: 2px;
}

Просмотреть файл

@ -96,22 +96,22 @@
display: none;
}
#BMB_bookmarksPopup > menu,
#BMB_bookmarksPopup > menuitem:not(.panel-subview-footer) {
menu.subviewbutton,
menuitem.subviewbutton:not(.panel-subview-footer) {
padding-top: 5px;
padding-bottom: 5px;
}
/* Override OSX-specific toolkit styles for the bookmarks panel */
#BMB_bookmarksPopup > menu > .menu-right {
menu.subviewbutton > .menu-right {
-moz-margin-end: 0;
}
#BMB_bookmarksPopup > menu > .menu-right > image {
menu.subviewbutton > .menu-right > image {
-moz-image-region: rect(0, 9px, 10px, 0);
}
@media (min-resolution: 2dppx) {
#BMB_bookmarksPopup > menu > .menu-right > image {
menu.subviewbutton > .menu-right > image {
-moz-image-region: rect(0, 18px, 20px, 0);
}
}
@ -124,3 +124,8 @@
.cui-widget-panelview menuseparator {
padding: 0 !important;
}
toolbarpaletteitem:-moz-any([place="palette"], [place="panel"]) > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-1 > .toolbarbutton-icon {
width: 32px;
height: 32px;
}

Просмотреть файл

@ -640,22 +640,36 @@ menuitem.subviewbutton@menuStateActive@,
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
}
#BMB_bookmarksPopup > .subviewbutton {
#BMB_bookmarksPopup .subviewbutton {
font: inherit;
font-weight: normal;
}
#BMB_bookmarksPopup > .subviewbutton:not([disabled="true"]) {
#BMB_bookmarksPopup .subviewbutton:not([disabled="true"]) {
color: inherit;
}
#BMB_bookmarksPopup > .panel-arrowcontainer > .panel-arrowcontent > .popup-internal-box > .autorepeatbutton-up,
#BMB_bookmarksPopup > .panel-arrowcontainer > .panel-arrowcontent > .popup-internal-box > .autorepeatbutton-down {
#BMB_bookmarksPopup .panel-arrowcontainer > .panel-arrowcontent > .popup-internal-box > .autorepeatbutton-up,
#BMB_bookmarksPopup .panel-arrowcontainer > .panel-arrowcontent > .popup-internal-box > .autorepeatbutton-down {
-moz-appearance: none;
margin-top: 0;
margin-bottom: 0;
}
/* Remove padding on xul:arrowscrollbox to avoid extra padding on footer */
#BMB_bookmarksPopup arrowscrollbox {
padding-bottom: 0px;
}
#BMB_bookmarksPopup menupopup {
padding-top: 2px;
}
#BMB_bookmarksPopup menupopup > .bookmarks-actions-menuseparator {
/* Hide bottom separator as the styled footer includes a top border serving the same purpose */
display: none;
}
.PanelUI-subView menuseparator,
.PanelUI-subView toolbarseparator,
.cui-widget-panelview menuseparator {
@ -899,12 +913,11 @@ toolbaritem[overflowedItem=true],
background: url("chrome://global/skin/menu/shared-menu-check.png") top 7px left 7px / 11px 11px no-repeat transparent;
}
.PanelUI-subView > menu > .menu-iconic-left,
.PanelUI-subView > menuitem > .menu-iconic-left {
.PanelUI-subView .menu-iconic-left {
-moz-appearance: none;
-moz-margin-end: 3px;
}
.PanelUI-subView > menuitem[checked="true"] > .menu-iconic-left {
.PanelUI-subView menuitem[checked="true"] > .menu-iconic-left {
visibility: hidden;
}

Просмотреть файл

@ -1,5 +1,6 @@
/* Menu panel and palette styles */
toolbaritem[sdkstylewidget="true"] > toolbarbutton,
:-moz-any(@primaryToolbarButtons@)[cui-areatype="menu-panel"],
toolbarpaletteitem[place="palette"] > :-moz-any(@primaryToolbarButtons@) {
list-style-image: url(chrome://browser/skin/menuPanel.png);
@ -135,6 +136,10 @@ toolbarpaletteitem[place="palette"] > #sidebar-button {
-moz-image-region: rect(32px, 864px, 64px, 832px);
}
toolbaritem[sdkstylewidget="true"] > toolbarbutton {
-moz-image-region: rect(0, 832px, 32px, 800px);
}
/* Wide panel control icons */
#edit-controls@inAnyPanel@ > toolbarbutton,

Двоичные данные
browser/themes/windows/Toolbar-XP.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 14 KiB

После

Ширина:  |  Высота:  |  Размер: 14 KiB

Просмотреть файл

@ -14,7 +14,30 @@
padding-right: 12px;
}
#BMB_bookmarksPopup > menuseparator {
/* bookmark panel submenus */
#BMB_bookmarksPopup menupopup {
-moz-appearance: none;
background: transparent;
border: none;
padding: 6px;
}
#BMB_bookmarksPopup menupopup > hbox {
/* emulating chrome://browser/content/places/menu.xml#places-popup-arrow but without the arrow */
box-shadow: 0 0 4px rgba(0,0,0,0.2);
background: #FFF;
border: 1px solid rgba(0,0,0,0.25);
border-radius: 3.5px;
margin-top: -4px;
}
#BMB_bookmarksPopup .menu-text {
color: #000;
}
/* bookmark panel separator */
#BMB_bookmarksPopup menuseparator {
padding-top: 0;
padding-bottom: 0;
}

Просмотреть файл

@ -33,20 +33,23 @@ classes.dex: $(JAVAFILES)
$(DX) --dex --output=$@ classes $(ANDROID_EXTRA_JARS)
# R.java and $(ANDROID_APK_NAME).ap_ are both produced by aapt. To
# save an aapt invocation, we produce them both at the same time.
# save an aapt invocation, we produce them both at the same time. The
# trailing semi-colon defines an empty recipe; defining no recipe at
# all causes Make to treat the target differently, in a way that
# defeats our dependencies.
R.java: .aapt.deps
$(ANDROID_APK_NAME).ap_: .aapt.deps
R.java: .aapt.deps ;
$(ANDROID_APK_NAME).ap_: .aapt.deps ;
# This uses the fact that Android resource directories list all
# resource files one subdirectory below the parent resource directory.
android_res_files := $(wildcard $(addsuffix /*,$(wildcard $(addsuffix /*,$(android_res_dirs)))))
.aapt.deps: $(android_manifest) $(android_res_files) $(wildcard $(ANDROID_ASSETS_DIR))
@$(TOUCH) $@
$(AAPT) package -f -M $< -I $(ANDROID_SDK)/android.jar $(_ANDROID_RES_FLAG) $(_ANDROID_ASSETS_FLAG) \
-J ${@D} \
-F $(ANDROID_APK_NAME).ap_
@$(TOUCH) $@
$(ANDROID_APK_NAME)-unsigned-unaligned.apk: $(ANDROID_APK_NAME).ap_ classes.dex
cp $< $@

Просмотреть файл

@ -96,15 +96,19 @@ else
endif
endif
# We touch the target file before invoking Proguard so that Proguard's
# outputs are fresher than the target, preventing a subsequent
# invocation from thinking Proguard's outputs are stale. This is safe
# because Make removes the target file if any recipe command fails.
.proguard.deps: $(ALL_JARS)
$(REPORT_BUILD)
@$(TOUCH) $@
java -jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
@$(topsrcdir)/mobile/android/config/proguard.cfg \
-optimizationpasses $(PROGUARD_PASSES) \
-injars $(subst ::,:,$(subst $(NULL) ,:,$(strip $(ALL_JARS)))) \
-outjars jars-proguarded \
-libraryjars $(ANDROID_SDK)/android.jar:$(ANDROID_COMPAT_LIB)
@$(TOUCH) $@
CLASSES_WITH_JNI= \
org.mozilla.gecko.GeckoAppShell \
@ -177,33 +181,13 @@ include $(topsrcdir)/config/rules.mk
not_android_res_files := \
*.mkdir.done* \
*.DS_Store* \
*\#* \
$(NULL)
# This uses the fact that Android resource directories list all
# resource files one subdirectory below the parent resource directory.
android_res_files := $(filter-out $(not_android_res_files),$(wildcard $(addsuffix /*,$(wildcard $(addsuffix /*,$(ANDROID_RES_DIRS))))))
# For GeckoView, we want a zip of an Android res/ directory that
# merges the contents of all the ANDROID_RES_DIRS. The inner res/
# directory must have the Android resource two-layer hierarchy.
# The following helper zips files in a directory into a zip file while
# maintaining the directory structure rooted below the directory.
# (adding or creating said file as appropriate). For example, if the
# dir contains dir/subdir/file, calling with directory dir would
# create a zip containing subdir/file. Note: the trailing newline is
# necessary.
# $(1): zip file to add to (or create).
# $(2): directory to zip contents of.
define zip_directory_with_relative_paths
cd $(2) && zip -q $(1) -r * -x $(not_android_res_files)
endef
geckoview_resources.zip: $(android_res_files) $(GLOBAL_DEPS)
$(foreach dir,$(ANDROID_RES_DIRS),$(call zip_directory_with_relative_paths,$(CURDIR)/$@,$(dir)))
$(ANDROID_GENERATED_RESFILES): $(call mkdir_deps,$(sort $(dir $(ANDROID_GENERATED_RESFILES))))
# [Comment 1/3] We don't have correct dependencies for strings.xml at
@ -226,37 +210,73 @@ all_resources = \
$(ANDROID_GENERATED_RESFILES) \
$(NULL)
# All of generated/org/mozilla/gecko/R.java, gecko.ap_, and R.txt are
# produced by aapt; this saves aapt invocations.
# For GeckoView, we want a zip of an Android res/ directory that
# merges the contents of all the ANDROID_RES_DIRS. The inner res/
# directory must have the Android resource two-layer hierarchy.
$(gecko_package_dir)/R.java: .aapt.deps
gecko.ap_: .aapt.deps
R.txt: .aapt.deps
# The following helper zips files in a directory into a zip file while
# maintaining the directory structure rooted below the directory.
# (adding or creating said file as appropriate). For example, if the
# dir contains dir/subdir/file, calling with directory dir would
# create a zip containing subdir/file. Note: the trailing newline is
# necessary.
# $(1): zip file to add to (or create).
# $(2): directory to zip contents of.
define zip_directory_with_relative_paths
cd $(2) && zip -q $(1) -r * -x $(not_android_res_files)
endef
geckoview_resources.zip: $(all_resources) $(GLOBAL_DEPS)
$(foreach dir,$(ANDROID_RES_DIRS),$(call zip_directory_with_relative_paths,$(CURDIR)/$@,$(dir)))
# All of generated/org/mozilla/gecko/R.java, gecko.ap_, and R.txt are
# produced by aapt; this saves aapt invocations. The trailing
# semi-colon defines an empty recipe; defining no recipe at all causes
# Make to treat the target differently, in a way that defeats our
# dependencies.
$(gecko_package_dir)/R.java: .aapt.deps ;
gecko.ap_: .aapt.deps ;
R.txt: .aapt.deps ;
# [Comment 2/3] This tom-foolery provides a target that forces a
# rebuild of gecko.ap_. This is used during packaging to ensure that
# resources are fresh. The alternative would be complicated; see
# [Comment 1/3].
gecko-nodeps/R.java: .aapt.nodeps
gecko-nodeps.ap_: .aapt.nodeps
gecko-nodeps/R.txt: .aapt.nodeps
gecko-nodeps/R.java: .aapt.nodeps ;
gecko-nodeps.ap_: .aapt.nodeps ;
gecko-nodeps/R.txt: .aapt.nodeps ;
# This ignores the default set of resources ignored by aapt, plus
# files starting with '#'. (Emacs produces temp files named #temp#.)
# This doesn't actually set the environment variable; it's used as a
# parameter in the aapt invocation below.
ANDROID_AAPT_IGNORE := !.svn:!.git:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~:\#*
# 1: target file.
# 2: dependencies.
# 3: name of ap_ file to write.
# 4: directory to write R.java into.
# 5: directory to write R.txt into.
# We touch the target file before invoking aapt so that aapt's outputs
# are fresher than the target, preventing a subsequent invocation from
# thinking aapt's outputs are stale. This is safe because Make
# removes the target file if any recipe command fails.
define aapt_command
$(1): $$(call mkdir_deps,$(filter-out ./,$(dir $(3) $(4) $(5)))) $(2)
@$$(TOUCH) $$@
$$(AAPT) package -f -M AndroidManifest.xml -I $$(ANDROID_SDK)/android.jar \
--auto-add-overlay \
$$(addprefix -S ,$$(ANDROID_RES_DIRS)) \
--custom-package org.mozilla.gecko --non-constant-id \
-F $(3) \
-J $(4) \
--output-text-symbols $(5)
@$$(TOUCH) $$@
--output-text-symbols $(5) \
--ignore-assets "$$(ANDROID_AAPT_IGNORE)"
endef
# [Comment 3/3] The first of these rules is used during regular

Просмотреть файл

@ -5,7 +5,6 @@
package org.mozilla.gecko.background.healthreport;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.SortedSet;
@ -14,6 +13,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.nativecode.NativeCrypto;
public abstract class EnvironmentV1 {
private static final String LOG_TAG = "GeckoEnvironment";
@ -70,24 +70,15 @@ public abstract class EnvironmentV1 {
}
public static class HashAppender extends EnvironmentAppender {
final MessageDigest hasher;
private final StringBuilder builder;
public HashAppender() throws NoSuchAlgorithmException {
// Note to the security-minded reader: we deliberately use SHA-1 here, not
// a stronger hash. These identifiers don't strictly need a cryptographic
// hash function, because there is negligible value in attacking the hash.
// We use SHA-1 because it's *shorter* -- the exact same reason that Git
// chose SHA-1.
hasher = MessageDigest.getInstance("SHA-1");
builder = new StringBuilder();
}
@Override
public void append(String s) {
try {
hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
// This can never occur. Thanks, Java.
}
builder.append((s == null) ? "null" : s);
}
@Override
@ -99,7 +90,21 @@ public abstract class EnvironmentV1 {
public String toString() {
// We *could* use ASCII85 but the savings would be negated by the
// inclusion of JSON-unsafe characters like double-quote.
return new Base64(-1, null, false).encodeAsString(hasher.digest());
final byte[] inputBytes;
try {
inputBytes = builder.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
Logger.warn(LOG_TAG, "Invalid charset String passed to getBytes", e);
return null;
}
// Note to the security-minded reader: we deliberately use SHA-1 here, not
// a stronger hash. These identifiers don't strictly need a cryptographic
// hash function, because there is negligible value in attacking the hash.
// We use SHA-1 because it's *shorter* -- the exact same reason that Git
// chose SHA-1.
final byte[] hash = NativeCrypto.sha1(inputBytes);
return new Base64(-1, null, false).encodeAsString(hash);
}
}

Просмотреть файл

@ -107,13 +107,6 @@
<!-- Firefox account strings. -->
<!-- Localization note: these describe icons and graphics, and are
consumed by assistive devices. -->
<!ENTITY fxaccount_checkbox_contentDescription2 'Firefox accounts checkbox graphic'>
<!ENTITY fxaccount_icon_contentDescription2 'Firefox accounts icon'>
<!ENTITY fxaccount_intro_contentDescription2 'Firefox accounts introduction graphics'>
<!ENTITY fxaccount_mail_contentDescription2 'Firefox accounts envelope graphic'>
<!-- Localization note: these are shown in all screens that query the
user for an email address and password. Hide and show are button
labels. -->

Просмотреть файл

@ -21,7 +21,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="45dp"
android:contentDescription="@string/fxaccount_checkbox_contentDescription"
android:contentDescription="@string/fxaccount_empty_contentDescription"
android:src="@drawable/fxaccount_checkbox" >
</ImageView>
@ -40,7 +40,7 @@
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout>
</ScrollView>
</ScrollView>

Просмотреть файл

@ -24,7 +24,7 @@
android:layout_gravity="center_horizontal"
android:layout_marginBottom="40dp"
android:background="@android:color/transparent"
android:contentDescription="@string/fxaccount_mail_contentDescription"
android:contentDescription="@string/fxaccount_empty_contentDescription"
android:src="@drawable/fxaccount_mail" >
</ImageView>
@ -49,7 +49,7 @@
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout>
</ScrollView>
</ScrollView>

Просмотреть файл

@ -88,7 +88,7 @@
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout>
</ScrollView>

Просмотреть файл

@ -36,7 +36,7 @@
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout>
</ScrollView>

Просмотреть файл

@ -26,7 +26,7 @@
android:layout_gravity="center_horizontal"
android:layout_marginBottom="30dp"
android:layout_marginTop="30dp"
android:contentDescription="@string/fxaccount_intro_contentDescription"
android:contentDescription="@string/fxaccount_empty_contentDescription"
android:src="@drawable/fxaccount_intro" >
</ImageView>
@ -49,7 +49,7 @@
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout>
</ScrollView>
</ScrollView>

Просмотреть файл

@ -76,7 +76,7 @@
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout>
</ScrollView>
</ScrollView>

Просмотреть файл

@ -51,7 +51,7 @@
<ImageView
style="@style/FxAccountIcon"
android:contentDescription="@string/fxaccount_icon_contentDescription" />
android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout>
</ScrollView>
</ScrollView>

Просмотреть файл

@ -111,10 +111,9 @@
<string name="fxaccount_link_pn">https://accounts.firefox.com/legal/privacy</string>
<string name="fxaccount_link_forgot_password">https://accounts.firefox.com/reset_password</string>
<string name="fxaccount_checkbox_contentDescription">&fxaccount_checkbox_contentDescription2;</string>
<string name="fxaccount_icon_contentDescription">&fxaccount_icon_contentDescription2;</string>
<string name="fxaccount_intro_contentDescription">&fxaccount_intro_contentDescription2;</string>
<string name="fxaccount_mail_contentDescription">&fxaccount_mail_contentDescription2;</string>
<!-- Per Bug 974627, decorative images should not have non-empty
contentDescription text, and it should not be translated. -->
<string name="fxaccount_empty_contentDescription"></string>
<string name="fxaccount_email_hint">&fxaccount_email_hint;</string>
<string name="fxaccount_password_hint">&fxaccount_password_hint;</string>

Просмотреть файл

@ -30,6 +30,7 @@ BACKGROUND_TESTS_JAVA_FILES := \
src/healthreport/prune/TestHealthReportPruneService.java \
src/healthreport/prune/TestPrunePolicyDatabaseStorage.java \
src/healthreport/TestEnvironmentBuilder.java \
src/healthreport/TestEnvironmentV1HashAppender.java \
src/healthreport/TestHealthReportBroadcastService.java \
src/healthreport/TestHealthReportDatabaseStorage.java \
src/healthreport/TestHealthReportGenerator.java \

Просмотреть файл

@ -0,0 +1,146 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.healthreport;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.LinkedList;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.background.healthreport.EnvironmentV1.EnvironmentAppender;
import org.mozilla.gecko.background.healthreport.EnvironmentV1.HashAppender;
import org.mozilla.gecko.background.helpers.FakeProfileTestCase;
import org.mozilla.gecko.sync.Utils;
/**
* Tests the HashAppender functionality. Note that these tests must be run on an Android
* device because the SHA-1 native library needs to be loaded.
*/
public class TestEnvironmentV1HashAppender extends FakeProfileTestCase {
// input and expected values via: http://oauth.googlecode.com/svn/code/c/liboauth/src/sha1.c
private final static String[] INPUTS = new String[] {
"abc",
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"" // To be filled in below.
};
static {
final String baseStr = "01234567";
final int repetitions = 80;
final StringBuilder builder = new StringBuilder(baseStr.length() * repetitions);
for (int i = 0; i < 80; ++i) {
builder.append(baseStr);
}
INPUTS[2] = builder.toString();
}
private final static String[] EXPECTEDS = new String[] {
"a9993e364706816aba3e25717850c26c9cd0d89d",
"84983e441c3bd26ebaae4aa1f95129e5e54670f1",
"dea356a2cddd90c7a7ecedc5ebb563934f460452"
};
static {
for (int i = 0; i < EXPECTEDS.length; ++i) {
EXPECTEDS[i] = new Base64(-1, null, false).encodeAsString(Utils.hex2Byte(EXPECTEDS[i]));
}
}
public void testSHA1Hashing() throws Exception {
for (int i = 0; i < INPUTS.length; ++i) {
final String input = INPUTS[i];
final String expected = EXPECTEDS[i];
final HashAppender appender = new HashAppender();
addStringToAppenderInParts(appender, input);
final String result = appender.toString();
assertEquals(expected, result);
}
}
/**
* Tests to ensure output is the same as the former MessageDigest implementation (bug 959652).
*/
public void testAgainstMessageDigestImpl() throws Exception {
// List.add doesn't allow add(null) so we make a LinkedList here.
final LinkedList<String> inputs = new LinkedList<String>(Arrays.asList(INPUTS));
inputs.add(null);
for (final String input : inputs) {
final HashAppender hAppender = new HashAppender();
final MessageDigestHashAppender mdAppender = new MessageDigestHashAppender();
hAppender.append(input);
mdAppender.append(input);
final String hResult = hAppender.toString();
final String mdResult = mdAppender.toString();
assertEquals(mdResult, hResult);
}
}
public void testIntegersAgainstMessageDigestImpl() throws Exception {
final int[] INPUTS = {Integer.MIN_VALUE, -1337, -42, 0, 42, 1337, Integer.MAX_VALUE};
for (final int input : INPUTS) {
final HashAppender hAppender = new HashAppender();
final MessageDigestHashAppender mdAppender = new MessageDigestHashAppender();
hAppender.append(input);
mdAppender.append(input);
final String hResult = hAppender.toString();
final String mdResult = mdAppender.toString();
assertEquals(mdResult, hResult);
}
}
private void addStringToAppenderInParts(final EnvironmentAppender appender, final String input) {
int substrInd = 0;
int substrLength = 1;
while (substrInd < input.length()) {
final int endInd = Math.min(substrInd + substrLength, input.length());
appender.append(input.substring(substrInd, endInd));
substrInd = endInd;
++substrLength;
}
}
// --- COPY-PASTA'D CODE, FOR TESTING PURPOSES. ---
public static class MessageDigestHashAppender extends EnvironmentAppender {
final MessageDigest hasher;
public MessageDigestHashAppender() throws NoSuchAlgorithmException {
// Note to the security-minded reader: we deliberately use SHA-1 here, not
// a stronger hash. These identifiers don't strictly need a cryptographic
// hash function, because there is negligible value in attacking the hash.
// We use SHA-1 because it's *shorter* -- the exact same reason that Git
// chose SHA-1.
hasher = MessageDigest.getInstance("SHA-1");
}
@Override
public void append(String s) {
try {
hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
// This can never occur. Thanks, Java.
}
}
@Override
public void append(int profileCreation) {
append(Integer.toString(profileCreation, 10));
}
@Override
public String toString() {
// We *could* use ASCII85 but the savings would be negated by the
// inclusion of JSON-unsafe characters like double-quote.
return new Base64(-1, null, false).encodeAsString(hasher.digest());
}
}
}

Просмотреть файл

@ -17,6 +17,7 @@ Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/observers.js");
const Prefs = new Preferences("services.common.tokenserverclient.");
@ -328,6 +329,9 @@ TokenServerClient.prototype = {
return;
}
// Any response status can have an X-Backoff header.
this._maybeNotifyBackoff(response, "x-backoff");
// The service shouldn't have any 3xx, so we don't need to handle those.
if (response.status != 200) {
// We /should/ have a Cornice error report in the JSON. We log that to
@ -379,6 +383,10 @@ TokenServerClient.prototype = {
error.cause = "unknown-service";
}
// A Retry-After header should theoretically only appear on a 503, but
// we'll look for it on any error response.
this._maybeNotifyBackoff(response, "retry-after");
cb(error, null);
return;
}
@ -405,6 +413,23 @@ TokenServerClient.prototype = {
});
},
// Given an optional header value, notify that a backoff has been requested.
_maybeNotifyBackoff: function (response, headerName) {
let headerVal = response.headers[headerName];
if (!headerVal) {
return;
}
let backoffInterval;
try {
backoffInterval = parseInt(headerVal, 10);
} catch (ex) {
this._log.error("TokenServer response had invalid backoff value in '" +
headerName + "' header: " + headerVal);
return;
}
Observers.notify("tokenserver:backoff:interval", backoffInterval);
},
// override points for testing.
newRESTRequest: function(url) {
return new RESTRequest(url);

Просмотреть файл

@ -135,7 +135,7 @@ this.BrowserIDManager.prototype = {
}
// If we are already happy then there is nothing more to do.
if (Weave.Status.login == LOGIN_SUCCEEDED) {
if (this._syncKeyBundle) {
return Promise.resolve();
}
@ -513,7 +513,6 @@ this.BrowserIDManager.prototype = {
// This will arrange for us to be in the right 'currentAuthState'
// such that UI will show the right error.
this._shouldHaveSyncKeyBundle = true;
this._syncKeyBundle = null;
Weave.Status.login = this._authFailureReason;
Services.obs.notifyObservers(null, "weave:service:login:error", null);
throw err;

Просмотреть файл

@ -92,6 +92,7 @@ SyncScheduler.prototype = {
Svc.Obs.add("weave:engine:sync:applied", this);
Svc.Obs.add("weave:service:setup-complete", this);
Svc.Obs.add("weave:service:start-over", this);
Svc.Obs.add("tokenserver:backoff:interval", this);
if (Status.checkSetup() == STATUS_OK) {
Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
@ -181,6 +182,7 @@ SyncScheduler.prototype = {
this.nextSync = 0;
this.handleSyncError();
break;
case "tokenserver:backoff:interval":
case "weave:service:backoff:interval":
let requested_interval = subject * 1000;
this._log.debug("Got backoff notification: " + requested_interval + "ms");

Просмотреть файл

@ -13,6 +13,7 @@ Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://gre/modules/FxAccountsClient.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://services-common/tokenserverclient.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/constants.js");
@ -363,6 +364,36 @@ add_task(function test_getTokenErrors() {
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
});
add_task(function test_getTokenErrorWithRetry() {
_("tokenserver sends an observer notification on various backoff headers.");
// Set Sync's backoffInterval to zero - after we simulated the backoff header
// it should reflect the value we sent.
Status.backoffInterval = 0;
_("Arrange for a 503 with a Retry-After header.");
yield initializeIdentityWithTokenServerFailure({
status: 503,
headers: {"content-type": "application/json",
"retry-after": "100"},
body: JSON.stringify({}),
});
// The observer should have fired - check it got the value in the response.
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
// Sync will have the value in ms with some slop - so check it is at least that.
Assert.ok(Status.backoffInterval >= 100000);
_("Arrange for a 200 with an X-Backoff header.");
Status.backoffInterval = 0;
yield initializeIdentityWithTokenServerFailure({
status: 503,
headers: {"content-type": "application/json",
"x-backoff": "200"},
body: JSON.stringify({}),
});
// The observer should have fired - check it got the value in the response.
Assert.ok(Status.backoffInterval >= 200000);
});
add_task(function test_getHAWKErrors() {
_("BrowserIDManager correctly handles various HAWK failures.");