зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central
This commit is contained in:
Коммит
210cd1a4c5
|
@ -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
Двоичные данные
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.");
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче