diff --git a/addon-sdk/source/examples/reading-data/tests/test-main.js b/addon-sdk/source/examples/reading-data/tests/test-main.js
index c2c2f12eda47..1e6a18e46bb7 100644
--- a/addon-sdk/source/examples/reading-data/tests/test-main.js
+++ b/addon-sdk/source/examples/reading-data/tests/test-main.js
@@ -1,7 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+// Disable tests below for now.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=987348
+/*
var m = require("main");
var self = require("sdk/self");
@@ -22,3 +26,4 @@ exports.testID = function(test) {
test.assertEqual(self.data.url("sample.html"),
"resource://reading-data-example-at-jetpack-dot-mozillalabs-dot-com/reading-data/data/sample.html");
};
+*/
diff --git a/addon-sdk/source/examples/reddit-panel/tests/test-main.js b/addon-sdk/source/examples/reddit-panel/tests/test-main.js
index 09c7c086c7ee..f13003e7654f 100644
--- a/addon-sdk/source/examples/reddit-panel/tests/test-main.js
+++ b/addon-sdk/source/examples/reddit-panel/tests/test-main.js
@@ -1,7 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+// Disable tests below for now.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=987348
+/*
var m = require("main");
var self = require("sdk/self");
@@ -19,3 +23,4 @@ exports.testMain = function(test) {
exports.testData = function(test) {
test.assert(self.data.load("panel.js").length > 0);
};
+*/
diff --git a/addon-sdk/source/lib/sdk/core/disposable.js b/addon-sdk/source/lib/sdk/core/disposable.js
index 162fc4f33526..a9f992b6f795 100644
--- a/addon-sdk/source/lib/sdk/core/disposable.js
+++ b/addon-sdk/source/lib/sdk/core/disposable.js
@@ -9,66 +9,124 @@ module.metadata = {
};
-let { Class } = require("./heritage");
-let { on, off } = require('../system/events');
-let unloadSubject = require('@loader/unload');
+const { Class } = require("./heritage");
+const { Observer, subscribe, unsubscribe, observe } = require("./observer");
+const { isWeak, WeakReference } = require("./reference");
+const method = require("../../method/core");
-let disposables = WeakMap();
+const unloadSubject = require('@loader/unload');
+const addonUnloadTopic = "sdk:loader:destroy";
-function initialize(instance) {
- // Create an event handler that will dispose instance on unload.
- function handler(event) {
- if (event.subject.wrappedJSObject === unloadSubject) {
- instance.destroy();
- }
- }
- // Form weak reference between disposable instance and an unload event
- // handler associated with it. This will make sure that event handler can't
- // be garbage collected as long as instance is referenced. Also note that
- // system events intentionally hold weak reference to an event handler, this
- // will let GC claim both instance and an unload handler before actual add-on
- // unload if instance contains no other references.
- disposables.set(instance, handler);
- on("sdk:loader:destroy", handler);
-}
-exports.initialize = initialize;
-function dispose(instance) {
- // Disposes given instance by removing it from weak map so that handler can
- // be GC-ed even if references to instance are kept. Also unregister unload
- // handler.
+const uninstall = method("disposable/uninstall");
+exports.uninstall = uninstall;
- let handler = disposables.get(instance);
- if (handler) off("sdk:loader:destroy", handler);
- disposables.delete(instance);
-}
+
+const shutdown = method("disposable/shutdown");
+exports.shutdown = shutdown;
+
+const disable = method("disposable/disable");
+exports.disable = disable;
+
+const upgrade = method("disposable/upgrade");
+exports.upgrade = upgrade;
+
+const downgrade = method("disposable/downgrade");
+exports.downgrade = downgrade;
+
+const unload = method("disposable/unload");
+exports.unload = unload;
+
+const dispose = method("disposable/dispose");
exports.dispose = dispose;
+dispose.define(Object, object => object.dispose());
+
+
+const setup = method("disposable/setup");
+exports.setup = setup;
+setup.define(Object, (object, ...args) => object.setup(...args));
+
+
+// Set's up disposable instance.
+const setupDisposable = disposable => {
+ subscribe(disposable, addonUnloadTopic, isWeak(disposable));
+};
+
+// Tears down disposable instance.
+const disposeDisposable = disposable => {
+ unsubscribe(disposable, addonUnloadTopic);
+};
// Base type that takes care of disposing it's instances on add-on unload.
// Also makes sure to remove unload listener if it's already being disposed.
-let Disposable = Class({
- initialize: function setupDisposable() {
+const Disposable = Class({
+ implements: [Observer],
+ initialize: function(...args) {
// First setup instance before initializing it's disposal. If instance
// fails to initialize then there is no instance to be disposed at the
// unload.
- this.setup.apply(this, arguments);
- initialize(this);
+ setup(this, ...args);
+ setupDisposable(this);
},
- setup: function setup() {
- // Implement your initialize logic here.
- },
- dispose: function dispose() {
- // Implement your cleanup logic here.
- },
-
- destroy: function destroy() {
+ destroy: function(reason) {
// Destroying disposable removes unload handler so that attempt to dispose
// won't be made at unload & delegates to dispose.
- if (disposables.has(this)) {
- dispose(this);
- this.dispose();
- }
+ disposeDisposable(this);
+ unload(this, reason);
+ },
+ setup: function() {
+ // Implement your initialize logic here.
+ },
+ dispose: function() {
+ // Implement your cleanup logic here.
}
});
exports.Disposable = Disposable;
+
+// Disposable instances observe add-on unload notifications in
+// order to trigger `unload` on them.
+observe.define(Disposable, (disposable, subject, topic, data) => {
+ const isUnloadTopic = topic === addonUnloadTopic;
+ const isUnloadSubject = subject.wrappedJSObject === unloadSubject;
+ if (isUnloadTopic && isUnloadSubject) {
+ unsubscribe(disposable, topic);
+ unload(disposable);
+ }
+});
+
+const unloaders = {
+ destroy: dispose,
+ uninstall: uninstall,
+ shutdown: shutdown,
+ disable: disable,
+ upgrade: upgrade,
+ downgrade: downgrade
+}
+const unloaded = new WeakMap();
+unload.define(Disposable, (disposable, reason) => {
+ if (!unloaded.get(disposable)) {
+ unloaded.set(disposable, true);
+ // Pick an unload handler associated with an unload
+ // reason (falling back to destroy if not found) and
+ // delegate unloading to it.
+ const unload = unloaders[reason] || unloaders.destroy;
+ unload(disposable);
+ }
+});
+
+
+// If add-on is disabled munally, it's being upgraded, downgraded
+// or uniststalled `dispose` is invoked to undo any changes that
+// has being done by it in this session.
+disable.define(Disposable, dispose);
+downgrade.define(Disposable, dispose);
+upgrade.define(Disposable, dispose);
+uninstall.define(Disposable, dispose);
+
+// If application is shut down no dispose is invoked as undo-ing
+// changes made by instance is likely to just waste of resources &
+// increase shutdown time. Although specefic components may choose
+// to implement shutdown handler that does something better.
+shutdown.define(Disposable, disposable => {});
+
diff --git a/addon-sdk/source/lib/sdk/core/observer.js b/addon-sdk/source/lib/sdk/core/observer.js
new file mode 100644
index 000000000000..30f27df2aa33
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/core/observer.js
@@ -0,0 +1,87 @@
+/* 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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+
+const { Cc, Ci, Cr } = require("chrome");
+const { Class } = require("./heritage");
+const { isWeak } = require("./reference");
+const method = require("../../method/core");
+
+const { addObserver, removeObserver } = Cc['@mozilla.org/observer-service;1'].
+ getService(Ci.nsIObserverService);
+
+
+// This is a method that will be invoked when notification observer
+// subscribed to occurs.
+const observe = method("observer/observe");
+exports.observe = observe;
+
+// Method to subscribe to the observer notification.
+const subscribe = method("observe/subscribe");
+exports.subscribe = subscribe;
+
+
+// Method to unsubscribe from the observer notifications.
+const unsubscribe = method("observer/unsubscribe");
+exports.unsubscribe = unsubscribe;
+
+
+// This is wrapper class that takes a `delegate` and produces
+// instance of `nsIObserver` which will delegate to a given
+// object when observer notification occurs.
+const ObserverDelegee = Class({
+ initialize: function(delegate) {
+ this.delegate = delegate;
+ },
+ QueryInterface: function(iid) {
+ const isObserver = iid.equals(Ci.nsIObserver);
+ const isWeakReference = iid.equals(Ci.nsISupportsWeakReference);
+
+ if (!isObserver && !isWeakReference)
+ throw Cr.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+ observe: function(subject, topic, data) {
+ observe(this.delegate, subject, topic, data);
+ }
+});
+
+
+// Class that can be either mixed in or inherited from in
+// order to subscribe / unsubscribe for observer notifications.
+const Observer = Class({});
+exports.Observer = Observer;
+
+// Weak maps that associates instance of `ObserverDelegee` with
+// an actual observer. It ensures that `ObserverDelegee` instance
+// won't be GC-ed until given `observer` is.
+const subscribers = new WeakMap();
+
+// Implementation of `subscribe` for `Observer` type just registers
+// observer for an observer service. If `isWeak(observer)` is `true`
+// observer service won't hold strong reference to a given `observer`.
+subscribe.define(Observer, (observer, topic) => {
+ if (!subscribers.has(observer)) {
+ const delegee = new ObserverDelegee(observer);
+ subscribers.set(observer, delegee);
+ addObserver(delegee, topic, isWeak(observer));
+ }
+});
+
+// Unsubscribes `observer` from observer notifications for the
+// given `topic`.
+unsubscribe.define(Observer, (observer, topic) => {
+ const delegee = subscribers.get(observer);
+ if (delegee) {
+ subscribers.delete(observer);
+ removeObserver(delegee, topic);
+ }
+});
diff --git a/addon-sdk/source/lib/sdk/core/reference.js b/addon-sdk/source/lib/sdk/core/reference.js
new file mode 100644
index 000000000000..04549cd0f145
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/core/reference.js
@@ -0,0 +1,29 @@
+/* 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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const method = require("../../method/core");
+const { Class } = require("./heritage");
+
+// Object that inherit or mix WeakRefence inn will register
+// weak observes for system notifications.
+const WeakReference = Class({});
+exports.WeakReference = WeakReference;
+
+
+// If `isWeak(object)` is `true` observer installed
+// for such `object` will be weak, meaning that it will
+// be GC-ed if nothing else but observer is observing it.
+// By default everything except `WeakReference` will return
+// `false`.
+const isWeak = method("reference/weak?");
+exports.isWeak = isWeak;
+
+isWeak.define(Object, _ => false);
+isWeak.define(WeakReference, _ => true);
diff --git a/addon-sdk/source/lib/sdk/page-mod.js b/addon-sdk/source/lib/sdk/page-mod.js
index 06954db54fff..b13a176f9dff 100644
--- a/addon-sdk/source/lib/sdk/page-mod.js
+++ b/addon-sdk/source/lib/sdk/page-mod.js
@@ -13,6 +13,7 @@ const { contract } = require('./util/contract');
const { getAttachEventType, WorkerHost } = require('./content/utils');
const { Class } = require('./core/heritage');
const { Disposable } = require('./core/disposable');
+const { WeakReference } = require('./core/reference');
const { Worker } = require('./content/worker');
const { EventTarget } = require('./event/target');
const { on, emit, once, setListeners } = require('./event/core');
@@ -97,7 +98,8 @@ const PageMod = Class({
implements: [
modContract.properties(modelFor),
EventTarget,
- Disposable
+ Disposable,
+ WeakReference
],
extends: WorkerHost(workerFor),
setup: function PageMod(options) {
@@ -128,7 +130,7 @@ const PageMod = Class({
applyOnExistingDocuments(mod);
},
- destroy: function destroy() {
+ dispose: function() {
let style = styleFor(this);
if (style)
detach(style);
diff --git a/addon-sdk/source/lib/sdk/page-worker.js b/addon-sdk/source/lib/sdk/page-worker.js
index a5a7bf60d71c..727043b50f4e 100644
--- a/addon-sdk/source/lib/sdk/page-worker.js
+++ b/addon-sdk/source/lib/sdk/page-worker.js
@@ -13,6 +13,7 @@ const { filter, pipe, map, merge: streamMerge, stripListeners } = require('./eve
const { detach, attach, destroy, WorkerHost } = require('./content/utils');
const { Worker } = require('./content/worker');
const { Disposable } = require('./core/disposable');
+const { WeakReference } = require('./core/reference');
const { EventTarget } = require('./event/target');
const { unload } = require('./system/unload');
const { events, streamEventsFrom } = require('./content/events');
@@ -84,7 +85,8 @@ function isValidURL(page, url) !page.rules || page.rules.matchesAny(url)
const Page = Class({
implements: [
EventTarget,
- Disposable
+ Disposable,
+ WeakReference
],
extends: WorkerHost(workerFor),
setup: function Page(options) {
diff --git a/addon-sdk/source/lib/sdk/panel.js b/addon-sdk/source/lib/sdk/panel.js
index eb80421c11c2..274c2c50a625 100644
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -21,6 +21,7 @@ const { merge } = require("./util/object");
const { WorkerHost, detach, attach, destroy } = require("./content/utils");
const { Worker } = require("./content/worker");
const { Disposable } = require("./core/disposable");
+const { WeakReference } = require('./core/reference');
const { contract: loaderContract } = require("./content/loader");
const { contract } = require("./util/contract");
const { on, off, emit, setListeners } = require("./event/core");
@@ -111,7 +112,8 @@ const Panel = Class({
// set and return values from model on get.
panelContract.properties(modelFor),
EventTarget,
- Disposable
+ Disposable,
+ WeakReference
],
extends: WorkerHost(workerFor),
setup: function setup(options) {
@@ -197,7 +199,7 @@ const Panel = Class({
let model = modelFor(this);
let view = viewFor(this);
- let anchorView = getNodeView(anchor || options.position);
+ let anchorView = getNodeView(anchor || options.position || model.position);
options = merge({
position: model.position,
diff --git a/addon-sdk/source/lib/sdk/ui/state.js b/addon-sdk/source/lib/sdk/ui/state.js
index f48e78c9a8ed..dd3e6d3282e0 100644
--- a/addon-sdk/source/lib/sdk/ui/state.js
+++ b/addon-sdk/source/lib/sdk/ui/state.js
@@ -174,10 +174,11 @@ function state(contract) {
state: function state(target, state) {
let nativeTarget = target === 'window' ? getFocusedBrowser()
: target === 'tab' ? getMostRecentTab()
+ : target === this ? null
: viewFor(target);
if (!nativeTarget && target !== this && !isNil(target))
- throw new Error('target not allowed.');
+ throw new Error(ERR_INVALID_TARGET);
target = nativeTarget || target;
diff --git a/addon-sdk/source/lib/sdk/widget.js b/addon-sdk/source/lib/sdk/widget.js
index 89bfb1e15fe4..08487d56a922 100644
--- a/addon-sdk/source/lib/sdk/widget.js
+++ b/addon-sdk/source/lib/sdk/widget.js
@@ -1,13 +1,12 @@
/* 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";
// The widget module currently supports only Firefox.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=560716
module.metadata = {
- "stability": "stable",
+ "stability": "deprecated",
"engines": {
"Firefox": "*"
}
@@ -39,6 +38,7 @@ const EVENTS = {
// normal toolbarbuttons. If they're any wider than this margin, we'll
// treat them as wide widgets instead, which fill up the width of the panel:
const AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF = 70;
+const AUSTRALIS_PANEL_WIDE_CLASSNAME = "panel-wide-item";
const { validateOptions } = require("./deprecated/api-utils");
const panels = require("./panel");
@@ -55,6 +55,11 @@ const unload = require("./system/unload");
const { getNodeView } = require("./view/core");
const prefs = require('./preferences/service');
+require("./util/deprecate").deprecateUsage(
+ "The widget module is deprecated. " +
+ "Please consider using the sdk/ui module instead."
+);
+
// Data types definition
const valid = {
number: { is: ["null", "undefined", "number"] },
@@ -230,6 +235,8 @@ function haveInserted(widgetId) {
return prefs.has(INSERTION_PREF_ROOT + widgetId);
}
+const isWide = node => node.classList.contains(AUSTRALIS_PANEL_WIDE_CLASSNAME);
+
/**
* Main Widget class: entry point of the widget API
*
@@ -626,7 +633,7 @@ BrowserWindow.prototype = {
let placement = CustomizableUI.getPlacementOfWidget(id);
if (!placement) {
- if (haveInserted(id))
+ if (haveInserted(id) || isWide(node))
return;
placement = {area: 'nav-bar', position: undefined};
@@ -703,7 +710,7 @@ WidgetChrome.prototype._createNode = function WC__createNode() {
node.setAttribute("sdkstylewidget", "true");
if (this._widget.width > AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF) {
- node.classList.add("panel-wide-item");
+ node.classList.add(AUSTRALIS_PANEL_WIDE_CLASSNAME);
}
// TODO move into a stylesheet, configurable by consumers.
diff --git a/addon-sdk/source/test/addons/places/main.js b/addon-sdk/source/test/addons/places/main.js
index 486c362bfc1f..ff72fe8b435b 100644
--- a/addon-sdk/source/test/addons/places/main.js
+++ b/addon-sdk/source/test/addons/places/main.js
@@ -11,8 +11,8 @@ const app = require("sdk/system/xul-app");
// module.metadata.engines
if (app.is('Firefox')) {
merge(module.exports,
- require('./tests/test-places-bookmarks'),
require('./tests/test-places-events'),
+ require('./tests/test-places-bookmarks'),
require('./tests/test-places-favicon'),
require('./tests/test-places-history'),
require('./tests/test-places-host'),
diff --git a/addon-sdk/source/test/addons/places/tests/test-places-events.js b/addon-sdk/source/test/addons/places/tests/test-places-events.js
index 00a0d0e06be5..1813c2705e3c 100644
--- a/addon-sdk/source/test/addons/places/tests/test-places-events.js
+++ b/addon-sdk/source/test/addons/places/tests/test-places-events.js
@@ -18,6 +18,13 @@ const { setTimeout } = require('sdk/timers');
const { before, after } = require('sdk/test/utils');
const bmsrv = Cc['@mozilla.org/browser/nav-bookmarks-service;1'].
getService(Ci.nsINavBookmarksService);
+const { release, platform } = require('node/os');
+
+const isOSX10_6 = (() => {
+ let vString = release();
+ return vString && /darwin/.test(platform()) && /10\.6/.test(vString);
+})();
+
const {
search
} = require('sdk/places/history');
@@ -51,6 +58,14 @@ exports['test bookmark-item-added'] = function (assert, done) {
exports['test bookmark-item-changed'] = function (assert, done) {
let id;
let complete = makeCompleted(done);
+
+ // Due to bug 969616 and bug 971964, disabling tests in 10.6 (happens only
+ // in debug builds) to prevent intermittent failures
+ if (isOSX10_6) {
+ assert.ok(true, 'skipping test in OSX 10.6');
+ return done();
+ }
+
function handler ({type, data}) {
if (type !== 'bookmark-item-changed') return;
if (data.id !== id) return;
@@ -87,6 +102,13 @@ exports['test bookmark-item-moved'] = function (assert, done) {
let complete = makeCompleted(done);
let previousIndex, previousParentId;
+ // Due to bug 969616 and bug 971964, disabling tests in 10.6 (happens only
+ // in debug builds) to prevent intermittent failures
+ if (isOSX10_6) {
+ assert.ok(true, 'skipping test in OSX 10.6');
+ return done();
+ }
+
function handler ({type, data}) {
if (type !== 'bookmark-item-moved') return;
if (data.id !== id) return;
@@ -213,7 +235,7 @@ exports['test history-start-batch, history-end-batch, history-start-clear'] = fu
off(clearEvent, 'data', clearHandler);
complete();
}
-
+
on(startEvent, 'data', startHandler);
on(clearEvent, 'data', clearHandler);
diff --git a/addon-sdk/source/test/test-disposable.js b/addon-sdk/source/test/test-disposable.js
index 95b438d4fbb8..b549fd02a120 100644
--- a/addon-sdk/source/test/test-disposable.js
+++ b/addon-sdk/source/test/test-disposable.js
@@ -5,9 +5,150 @@
const { Loader } = require("sdk/test/loader");
const { Class } = require("sdk/core/heritage");
+const { Disposable } = require("sdk/core/disposable");
const { Cc, Ci, Cu } = require("chrome");
const { setTimeout } = require("sdk/timers");
+exports["test destroy reasons"] = assert => {
+ const Foo = Class({
+ extends: Disposable,
+ dispose: function() {
+ disposals = disposals + 1;
+ }
+ });
+
+ let disposals = 0;
+ const f1 = new Foo();
+
+ f1.destroy();
+ assert.equal(disposals, 1, "disposed on destroy");
+ f1.destroy();
+ assert.equal(disposals, 1, "second destroy is ignored");
+
+ disposals = 0;
+ const f2 = new Foo();
+
+ f2.destroy("uninstall");
+ assert.equal(disposals, 1, "uninstall invokes disposal");
+ f2.destroy("uninstall")
+ f2.destroy();
+ assert.equal(disposals, 1, "disposal happens just once");
+
+ disposals = 0;
+ const f3 = new Foo();
+
+ f3.destroy("shutdown");
+ assert.equal(disposals, 0, "shutdown doesn't invoke disposal");
+ f3.destroy();
+ assert.equal(disposals, 0, "shutdown already skipped disposal");
+
+ disposals = 0;
+ const f4 = new Foo();
+
+ f4.destroy("disable");
+ assert.equal(disposals, 1, "disable invokes disposal");
+ f4.destroy("disable")
+ f4.destroy();
+ assert.equal(disposals, 1, "destroy happens just once");
+
+ disposals = 0;
+ const f5 = new Foo();
+
+ f5.destroy("disable");
+ assert.equal(disposals, 1, "disable invokes disposal");
+ f5.destroy("disable")
+ f5.destroy();
+ assert.equal(disposals, 1, "destroy happens just once");
+
+ disposals = 0;
+ const f6 = new Foo();
+
+ f6.destroy("upgrade");
+ assert.equal(disposals, 1, "upgrade invokes disposal");
+ f6.destroy("upgrade")
+ f6.destroy();
+ assert.equal(disposals, 1, "destroy happens just once");
+
+ disposals = 0;
+ const f7 = new Foo();
+
+ f7.destroy("downgrade");
+ assert.equal(disposals, 1, "downgrade invokes disposal");
+ f7.destroy("downgrade")
+ f7.destroy();
+ assert.equal(disposals, 1, "destroy happens just once");
+
+
+ disposals = 0;
+ const f8 = new Foo();
+
+ f8.destroy("whatever");
+ assert.equal(disposals, 1, "unrecognized reason invokes disposal");
+ f8.destroy("meh")
+ f8.destroy();
+ assert.equal(disposals, 1, "destroy happens just once");
+};
+
+exports["test different unload hooks"] = assert => {
+ const { uninstall, shutdown, disable, upgrade,
+ downgrade, dispose } = require("sdk/core/disposable");
+ const UberUnload = Class({
+ extends: Disposable,
+ setup: function() {
+ this.log = [];
+ }
+ });
+
+ uninstall.define(UberUnload, x => x.log.push("uninstall"));
+ shutdown.define(UberUnload, x => x.log.push("shutdown"));
+ disable.define(UberUnload, x => x.log.push("disable"));
+ upgrade.define(UberUnload, x => x.log.push("upgrade"));
+ downgrade.define(UberUnload, x => x.log.push("downgrade"));
+ dispose.define(UberUnload, x => x.log.push("dispose"));
+
+ const u1 = new UberUnload();
+ u1.destroy("uninstall");
+ u1.destroy();
+ u1.destroy("shutdown");
+ assert.deepEqual(u1.log, ["uninstall"], "uninstall hook invoked");
+
+ const u2 = new UberUnload();
+ u2.destroy("shutdown");
+ u2.destroy();
+ u2.destroy("uninstall");
+ assert.deepEqual(u2.log, ["shutdown"], "shutdown hook invoked");
+
+ const u3 = new UberUnload();
+ u3.destroy("disable");
+ u3.destroy();
+ u3.destroy("uninstall");
+ assert.deepEqual(u3.log, ["disable"], "disable hook invoked");
+
+ const u4 = new UberUnload();
+ u4.destroy("upgrade");
+ u4.destroy();
+ u4.destroy("uninstall");
+ assert.deepEqual(u4.log, ["upgrade"], "upgrade hook invoked");
+
+ const u5 = new UberUnload();
+ u5.destroy("downgrade");
+ u5.destroy();
+ u5.destroy("uninstall");
+ assert.deepEqual(u5.log, ["downgrade"], "downgrade hook invoked");
+
+ const u6 = new UberUnload();
+ u6.destroy();
+ u6.destroy();
+ u6.destroy("uninstall");
+ assert.deepEqual(u6.log, ["dispose"], "dispose hook invoked");
+
+ const u7 = new UberUnload();
+ u7.destroy("whatever");
+ u7.destroy();
+ u7.destroy("uninstall");
+ assert.deepEqual(u7.log, ["dispose"], "dispose hook invoked");
+};
+
exports["test disposables are desposed on unload"] = function(assert) {
let loader = Loader(module);
let { Disposable } = loader.require("sdk/core/disposable");
@@ -84,6 +225,7 @@ exports["test destroyed windows dispose before unload"] = function(assert) {
exports["test disposables are GC-able"] = function(assert, done) {
let loader = Loader(module);
let { Disposable } = loader.require("sdk/core/disposable");
+ let { WeakReference } = loader.require("sdk/core/reference");
let arg1 = {}
let arg2 = 2
@@ -91,6 +233,7 @@ exports["test disposables are GC-able"] = function(assert, done) {
let Foo = Class({
extends: Disposable,
+ implements: [WeakReference],
setup: function setup(a, b) {
assert.equal(a, arg1,
"arguments passed to constructur is passed to setup");
@@ -106,7 +249,7 @@ exports["test disposables are GC-able"] = function(assert, done) {
this.fooed = false
disposals = disposals + 1
}
- })
+ });
let foo1 = Foo(arg1, arg2)
let foo2 = Foo(arg1, arg2)
diff --git a/addon-sdk/source/test/test-observers.js b/addon-sdk/source/test/test-observers.js
new file mode 100644
index 000000000000..b60cba102d6c
--- /dev/null
+++ b/addon-sdk/source/test/test-observers.js
@@ -0,0 +1,123 @@
+/* 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 { Loader } = require("sdk/test/loader");
+const { isWeak, WeakReference } = require("sdk/core/reference");
+const { subscribe, unsubscribe,
+ observe, Observer } = require("sdk/core/observer");
+const { Class } = require("sdk/core/heritage");
+
+const { Cc, Ci, Cu } = require("chrome");
+const { notifyObservers } = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+
+const message = x => ({wrappedJSObject: x});
+
+exports["test subscribe unsubscribe"] = assert => {
+ const topic = Date.now().toString(32);
+ const Subscriber = Class({
+ extends: Observer,
+ initialize: function(observe) {
+ this.observe = observe;
+ }
+ });
+ observe.define(Subscriber, (x, subject, _, data) =>
+ x.observe(subject.wrappedJSObject.x));
+
+ let xs = [];
+ const x = Subscriber((...rest) => xs.push(...rest));
+
+ let ys = [];
+ const y = Subscriber((...rest) => ys.push(...rest));
+
+ const publish = (topic, data) =>
+ notifyObservers(message(data), topic, null);
+
+ publish({x:0});
+
+ subscribe(x, topic);
+
+ publish(topic, {x:1});
+
+ subscribe(y, topic);
+
+ publish(topic, {x:2});
+ publish(topic + "!", {x: 2.5});
+
+ unsubscribe(x, topic);
+
+ publish(topic, {x:3});
+
+ subscribe(y, topic);
+
+ publish(topic, {x:4});
+
+ subscribe(x, topic);
+
+ publish(topic, {x:5});
+
+ unsubscribe(x, topic);
+ unsubscribe(y, topic);
+
+ publish(topic, {x:6});
+
+ assert.deepEqual(xs, [1, 2, 5]);
+ assert.deepEqual(ys, [2, 3, 4, 5]);
+}
+
+exports["test weak observers are GC-ed on unload"] = (assert, end) => {
+ const topic = Date.now().toString(32);
+ const loader = Loader(module);
+ const { Observer, observe,
+ subscribe, unsubscribe } = loader.require("sdk/core/observer");
+ const { isWeak, WeakReference } = loader.require("sdk/core/reference");
+
+ const MyObserver = Class({
+ extends: Observer,
+ initialize: function(observe) {
+ this.observe = observe;
+ }
+ });
+ observe.define(MyObserver, (x, ...rest) => x.observe(...rest));
+
+ const MyWeakObserver = Class({
+ extends: MyObserver,
+ implements: [WeakReference]
+ });
+
+ let xs = [];
+ let ys = [];
+ let x = new MyObserver((subject, topic, data) => {
+ xs.push(subject.wrappedJSObject, topic, data);
+ });
+ let y = new MyWeakObserver((subject, topic, data) => {
+ ys.push(subject.wrappedJSObject, topic, data);
+ });
+
+ subscribe(x, topic);
+ subscribe(y, topic);
+
+
+ notifyObservers(message({ foo: 1 }), topic, null);
+ x = null;
+ y = null;
+ loader.unload();
+
+ Cu.schedulePreciseGC(() => {
+
+ notifyObservers(message({ bar: 2 }), topic, ":)");
+
+ assert.deepEqual(xs, [{ foo: 1 }, topic, null,
+ { bar: 2 }, topic, ":)"],
+ "non week observer is kept");
+
+ assert.deepEqual(ys, [{ foo: 1 }, topic, null],
+ "week observer was GC-ed");
+
+ end();
+ });
+};
+
+require("sdk/test").run(exports);
\ No newline at end of file
diff --git a/addon-sdk/source/test/test-panel.js b/addon-sdk/source/test/test-panel.js
index 5867f7704798..6c2d21c88d5c 100644
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -859,6 +859,8 @@ exports['test passing DOM node as first argument'] = function (assert, done) {
let shown = defer();
function onMessage(type, message) {
+ if (type != 'warn') return;
+
let warning = 'Passing a DOM node to Panel.show() method is an unsupported ' +
'feature that will be soon replaced. ' +
'See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877';
diff --git a/addon-sdk/source/test/test-reference.js b/addon-sdk/source/test/test-reference.js
new file mode 100644
index 000000000000..1fe08004ce17
--- /dev/null
+++ b/addon-sdk/source/test/test-reference.js
@@ -0,0 +1,99 @@
+/* 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 { isWeak, WeakReference } = require("sdk/core/reference");
+const { Class } = require("sdk/core/heritage");
+
+exports["test non WeakReferences"] = assert => {
+ assert.equal(isWeak({}), false,
+ "regular objects aren't weak");
+
+ assert.equal(isWeak([]), false,
+ "arrays aren't weak");
+};
+
+exports["test a WeakReference"] = assert => {
+ assert.equal(isWeak(new WeakReference()), true,
+ "instances of WeakReferences are weak");
+};
+
+exports["test a subclass"] = assert => {
+ const SubType = Class({
+ extends: WeakReference,
+ foo: function() {
+ }
+ });
+
+ const x = new SubType();
+ assert.equal(isWeak(x), true,
+ "subtypes of WeakReferences are weak");
+
+ const SubSubType = Class({
+ extends: SubType
+ });
+
+ const y = new SubSubType();
+ assert.equal(isWeak(y), true,
+ "sub subtypes of WeakReferences are weak");
+};
+
+exports["test a mixin"] = assert => {
+ const Mixer = Class({
+ implements: [WeakReference]
+ });
+
+ assert.equal(isWeak(new Mixer()), true,
+ "instances with mixed WeakReference are weak");
+
+ const Mux = Class({});
+ const MixMux = Class({
+ extends: Mux,
+ implements: [WeakReference]
+ });
+
+ assert.equal(isWeak(new MixMux()), true,
+ "instances with mixed WeakReference that " +
+ "inheret from other are weak");
+
+
+ const Base = Class({});
+ const ReMix = Class({
+ extends: Base,
+ implements: [MixMux]
+ });
+
+ assert.equal(isWeak(new ReMix()), true,
+ "subtime of mix is still weak");
+};
+
+exports["test an override"] = assert => {
+ const SubType = Class({ extends: WeakReference });
+ isWeak.define(SubType, x => false);
+
+ assert.equal(isWeak(new SubType()), false,
+ "behavior of isWeak can still be overrided on subtype");
+
+ const Mixer = Class({ implements: [WeakReference] });
+ const SubMixer = Class({ extends: Mixer });
+ isWeak.define(SubMixer, x => false);
+ assert.equal(isWeak(new SubMixer()), false,
+ "behavior of isWeak can still be overrided on mixed type");
+};
+
+exports["test an opt-in"] = assert => {
+ var BaseType = Class({});
+ var SubType = Class({ extends: BaseType });
+
+ isWeak.define(SubType, x => true);
+ assert.equal(isWeak(new SubType()), true,
+ "isWeak can be just implemented");
+
+ var x = {};
+ isWeak.implement(x, x => true);
+ assert.equal(isWeak(x), true,
+ "isWeak can be implemented per instance");
+};
+
+require("sdk/test").run(exports);
diff --git a/addon-sdk/source/test/test-ui-action-button.js b/addon-sdk/source/test/test-ui-action-button.js
index 5d0bbf4f27ce..f6a766401f00 100644
--- a/addon-sdk/source/test/test-ui-action-button.js
+++ b/addon-sdk/source/test/test-ui-action-button.js
@@ -288,6 +288,43 @@ exports['test button global state updated'] = function(assert) {
loader.unload();
}
+exports['test button global state set and get with state method'] = function(assert) {
+ let loader = Loader(module);
+ let { ActionButton } = loader.require('sdk/ui');
+
+ let button = ActionButton({
+ id: 'my-button-16',
+ label: 'my button',
+ icon: './icon.png'
+ });
+
+ // read the button's state
+ let state = button.state(button);
+
+ assert.equal(state.label, 'my button',
+ 'label is correct');
+ assert.equal(state.icon, './icon.png',
+ 'icon is correct');
+ assert.equal(state.disabled, false,
+ 'disabled is correct');
+
+ // set the new button's state
+ button.state(button, {
+ label: 'New label',
+ icon: './new-icon.png',
+ disabled: true
+ });
+
+ assert.equal(button.label, 'New label',
+ 'label is updated');
+ assert.equal(button.icon, './new-icon.png',
+ 'icon is updated');
+ assert.equal(button.disabled, true,
+ 'disabled is updated');
+
+ loader.unload();
+}
+
exports['test button global state updated on multiple windows'] = function(assert, done) {
let loader = Loader(module);
let { ActionButton } = loader.require('sdk/ui');
diff --git a/addon-sdk/source/test/test-ui-toggle-button.js b/addon-sdk/source/test/test-ui-toggle-button.js
index 9272968ee32e..2eb66b1b1817 100644
--- a/addon-sdk/source/test/test-ui-toggle-button.js
+++ b/addon-sdk/source/test/test-ui-toggle-button.js
@@ -298,6 +298,43 @@ exports['test button global state updated'] = function(assert) {
loader.unload();
}
+exports['test button global state set and get with state method'] = function(assert) {
+ let loader = Loader(module);
+ let { ToggleButton } = loader.require('sdk/ui');
+
+ let button = ToggleButton({
+ id: 'my-button-16',
+ label: 'my button',
+ icon: './icon.png'
+ });
+
+ // read the button's state
+ let state = button.state(button);
+
+ assert.equal(state.label, 'my button',
+ 'label is correct');
+ assert.equal(state.icon, './icon.png',
+ 'icon is correct');
+ assert.equal(state.disabled, false,
+ 'disabled is correct');
+
+ // set the new button's state
+ button.state(button, {
+ label: 'New label',
+ icon: './new-icon.png',
+ disabled: true
+ });
+
+ assert.equal(button.label, 'New label',
+ 'label is updated');
+ assert.equal(button.icon, './new-icon.png',
+ 'icon is updated');
+ assert.equal(button.disabled, true,
+ 'disabled is updated');
+
+ loader.unload();
+};
+
exports['test button global state updated on multiple windows'] = function(assert, done) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
@@ -1027,35 +1064,62 @@ exports['test buttons can have anchored panels'] = function(assert, done) {
let { identify } = loader.require('sdk/ui/id');
let { getActiveView } = loader.require('sdk/view/core');
- let button = ToggleButton({
+ let b1 = ToggleButton({
id: 'my-button-22',
label: 'my button',
icon: './icon.png',
- onChange: ({checked}) => checked && panel.show({position: button})
+ onChange: ({checked}) => checked && panel.show()
});
- let panel = Panel();
+ let b2 = ToggleButton({
+ id: 'my-button-23',
+ label: 'my button',
+ icon: './icon.png',
+ onChange: ({checked}) => checked && panel.show({position: b2})
+ });
+
+ let panel = Panel({
+ position: b1
+ });
+
+ let { document } = getMostRecentBrowserWindow();
+ let b1Node = document.getElementById(identify(b1));
+ let b2Node = document.getElementById(identify(b2));
+ let panelNode = getActiveView(panel);
panel.once('show', () => {
- let { document } = getMostRecentBrowserWindow();
- let buttonNode = document.getElementById(identify(button));
- let panelNode = getActiveView(panel);
-
- assert.ok(button.state('window').checked,
+ assert.ok(b1.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');
+ assert.strictEqual(b1Node, panelNode.anchorNode,
+ 'the panel is anchored properly to the button given in costructor');
- loader.unload();
+ panel.hide();
- done();
+ panel.once('show', () => {
+ assert.ok(b2.state('window').checked,
+ 'button is checked');
+
+ assert.equal(panelNode.getAttribute('type'), 'arrow',
+ 'the panel is a arrow type');
+
+ // test also that the button passed in `show` method, takes the precedence
+ // over the button set in panel's constructor.
+ assert.strictEqual(b2Node, panelNode.anchorNode,
+ 'the panel is anchored properly to the button passed to show method');
+
+ loader.unload();
+
+ done();
+ });
+
+ b2.click();
});
- button.click();
+ b1.click();
}
require('sdk/test').run(exports);
diff --git a/addon-sdk/source/test/test-widget.js b/addon-sdk/source/test/test-widget.js
index 2973d7128d57..56d409787be3 100644
--- a/addon-sdk/source/test/test-widget.js
+++ b/addon-sdk/source/test/test-widget.js
@@ -9,13 +9,11 @@ module.metadata = {
}
};
-const widgets = require("sdk/widget");
const { Cc, Ci, Cu } = require("chrome");
-const { Loader } = require('sdk/test/loader');
+const { LoaderWithHookedConsole } = require('sdk/test/loader');
const url = require("sdk/url");
const timer = require("sdk/timers");
const self = require("sdk/self");
-const windowUtils = require("sdk/deprecated/window-utils");
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { close, open, focus } = require("sdk/window/helpers");
const tabs = require("sdk/tabs/utils");
@@ -43,8 +41,24 @@ function openNewWindowTab(url, options) {
});
}
+exports.testDeprecationMessage = function(assert, done) {
+ let { loader } = LoaderWithHookedConsole(module, onMessage);
+
+ // Intercept all console method calls
+ let calls = [];
+ function onMessage(type, msg) {
+ assert.equal(type, 'error', 'the only message is an error');
+ assert.ok(/^DEPRECATED:/.test(msg), 'deprecated');
+ loader.unload();
+ done();
+ }
+ loader.require('sdk/widget');
+}
+
exports.testConstructor = function(assert, done) {
- let browserWindow = windowUtils.activeBrowserWindow;
+ let { loader: loader0 } = LoaderWithHookedConsole(module);
+ const widgets = loader0.require("sdk/widget");
+ let browserWindow = getMostRecentBrowserWindow();
let doc = browserWindow.document;
let AddonsMgrListener;
@@ -78,7 +92,7 @@ exports.testConstructor = function(assert, done) {
assert.equal(widgetCount(), widgetStartCount, "panel has correct number of child elements after destroy");
// Test automatic widget destroy on unload
- let loader = Loader(module);
+ let { loader } = LoaderWithHookedConsole(module);
let widgetsFromLoader = loader.require("sdk/widget");
let widgetStartCount = widgetCount();
let w = widgetsFromLoader.Widget({ id: "destroy-on-unload", label: "foo", content: "bar" });
@@ -192,8 +206,10 @@ exports.testConstructor = function(assert, done) {
let tests = [];
function nextTest() {
assert.equal(widgetCount(), 0, "widget in last test property cleaned itself up");
- if (!tests.length)
+ if (!tests.length) {
+ loader0.unload();
done();
+ }
else
timer.setTimeout(tests.shift(), 0);
}
@@ -535,7 +551,7 @@ exports.testConstructor = function(assert, done) {
contentScript: "self.port.on('event', function () self.port.emit('event'))"
};
let widget = testSingleWidget(w1Opts);
- let windows = require("sdk/windows").browserWindows;
+ let windows = loader0.require("sdk/windows").browserWindows;
// 2/ Retrieve a WidgetView for the initial browser window
let acceptDetach = false;
@@ -627,16 +643,16 @@ exports.testConstructor = function(assert, done) {
id: "text-test-width",
label: "test widget.width",
content: "test width",
- width: 200,
+ width: 64,
contentScript: "self.postMessage(1)",
contentScriptWhen: "ready",
onMessage: function(message) {
- assert.equal(this.width, 200, 'width is 200');
+ assert.equal(this.width, 64, 'width is 64');
let node = widgetNode(0);
assert.equal(this.width, node.style.minWidth.replace("px", ""));
assert.equal(this.width, node.firstElementChild.style.width.replace("px", ""));
- this.width = 300;
+ this.width = 48;
assert.equal(this.width, node.style.minWidth.replace("px", ""));
assert.equal(this.width, node.firstElementChild.style.width.replace("px", ""));
@@ -677,16 +693,18 @@ exports.testConstructor = function(assert, done) {
};
exports.testWidgetWithValidPanel = function(assert, done) {
- const widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const { Widget } = loader.require("sdk/widget");
+ const { Panel } = loader.require("sdk/panel");
- let widget1 = widgets.Widget({
+ let widget1 = Widget({
id: "testWidgetWithValidPanel",
label: "panel widget 1",
content: "
foo
",
contentScript: "var evt = new MouseEvent('click', {button: 0});" +
"document.body.dispatchEvent(evt);",
contentScriptWhen: "end",
- panel: require("sdk/panel").Panel({
+ panel: Panel({
contentURL: "data:text/html;charset=utf-8,Look ma, a panel!",
onShow: function() {
let { document } = getMostRecentBrowserWindow();
@@ -697,6 +715,7 @@ exports.testWidgetWithValidPanel = function(assert, done) {
assert.strictEqual(panelEle.anchorNode, widgetEle, 'the panel is properly anchored to the widget');
widget1.destroy();
+ loader.unload();
assert.pass("panel displayed on click");
done();
}
@@ -705,7 +724,9 @@ exports.testWidgetWithValidPanel = function(assert, done) {
};
exports.testWidgetWithInvalidPanel = function(assert) {
- const widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+
assert.throws(
function() {
widgets.Widget({
@@ -716,10 +737,14 @@ exports.testWidgetWithInvalidPanel = function(assert) {
},
/^The option \"panel\" must be one of the following types: null, undefined, object$/,
"widget.panel must be a Panel object");
+ loader.unload();
};
exports.testPanelWidget3 = function testPanelWidget3(assert, done) {
- const widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+ const { Panel } = loader.require("sdk/panel");
+
let onClickCalled = false;
let widget3 = widgets.Widget({
id: "panel3",
@@ -732,13 +757,14 @@ exports.testPanelWidget3 = function testPanelWidget3(assert, done) {
onClickCalled = true;
this.panel.show();
},
- panel: require("sdk/panel").Panel({
+ panel: Panel({
contentURL: "data:text/html;charset=utf-8,Look ma, a panel!",
onShow: function() {
assert.ok(
onClickCalled,
"onClick called on click for widget with both panel and onClick");
widget3.destroy();
+ loader.unload();
done();
}
})
@@ -747,7 +773,9 @@ exports.testPanelWidget3 = function testPanelWidget3(assert, done) {
exports.testWidgetWithPanelInMenuPanel = function(assert, done) {
const { CustomizableUI } = Cu.import("resource:///modules/CustomizableUI.jsm", {});
- const widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+ const { Panel } = loader.require("sdk/panel");
let widget1 = widgets.Widget({
id: "panel1",
@@ -760,7 +788,7 @@ exports.testWidgetWithPanelInMenuPanel = function(assert, done) {
});
},
contentScriptWhen: "end",
- panel: require("sdk/panel").Panel({
+ panel: Panel({
contentURL: "data:text/html;charset=utf-8,Look ma, a panel!",
onShow: function() {
let { document } = getMostRecentBrowserWindow();
@@ -771,6 +799,7 @@ exports.testWidgetWithPanelInMenuPanel = function(assert, done) {
'the panel is anchored to the panel menu button instead of widget');
widget1.destroy();
+ loader.unload();
done();
}
})
@@ -797,8 +826,10 @@ exports.testWidgetWithPanelInMenuPanel = function(assert, done) {
};
exports.testWidgetMessaging = function testWidgetMessaging(assert, done) {
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+
let origMessage = "foo";
- const widgets = require("sdk/widget");
let widget = widgets.Widget({
id: "widget-messaging",
label: "foo",
@@ -811,6 +842,7 @@ exports.testWidgetMessaging = function testWidgetMessaging(assert, done) {
else {
assert.equal(origMessage, message);
widget.destroy();
+ loader.unload();
done();
}
}
@@ -818,7 +850,9 @@ exports.testWidgetMessaging = function testWidgetMessaging(assert, done) {
};
exports.testWidgetViews = function testWidgetViews(assert, done) {
- const widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+
let widget = widgets.Widget({
id: "widget-views",
label: "foo",
@@ -833,6 +867,7 @@ exports.testWidgetViews = function testWidgetViews(assert, done) {
});
view.on("detach", function () {
assert.pass("WidgetView destroyed");
+ loader.unload();
done();
});
}
@@ -840,7 +875,10 @@ exports.testWidgetViews = function testWidgetViews(assert, done) {
};
exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(assert, done) {
- const widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+ const { browserWindows } = loader.require("sdk/windows");
+
let view = null;
let widget = widgets.Widget({
id: "widget-view-ui-events",
@@ -856,17 +894,20 @@ exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(assert, done)
onClick: function (eventView) {
assert.equal(view, eventView,
"event first argument is equal to the WidgetView");
- let view2 = widget.getView(require("sdk/windows").browserWindows.activeWindow);
+ let view2 = widget.getView(browserWindows.activeWindow);
assert.equal(view, view2,
"widget.getView return the same WidgetView");
widget.destroy();
+ loader.unload();
done();
}
});
};
exports.testWidgetViewsCustomEvents = function testWidgetViewsCustomEvents(assert, done) {
- const widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+
let widget = widgets.Widget({
id: "widget-view-custom-events",
label: "foo",
@@ -883,33 +924,37 @@ exports.testWidgetViewsCustomEvents = function testWidgetViewsCustomEvents(asser
widget.port.on("event", function (data) {
assert.equal(data, "ok", "event argument is valid on Widget");
widget.destroy();
+ loader.unload();
done();
});
};
exports.testWidgetViewsTooltip = function testWidgetViewsTooltip(assert, done) {
- const widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+ const { browserWindows } = loader.require("sdk/windows");
let widget = new widgets.Widget({
id: "widget-views-tooltip",
label: "foo",
content: "foo"
});
- let view = widget.getView(require("sdk/windows").browserWindows.activeWindow);
+ let view = widget.getView(browserWindows.activeWindow);
widget.tooltip = null;
assert.equal(view.tooltip, "foo",
"view tooltip defaults to base widget label");
assert.equal(widget.tooltip, "foo",
"tooltip defaults to base widget label");
widget.destroy();
+ loader.unload();
done();
};
exports.testWidgetMove = function testWidgetMove(assert, done) {
- let windowUtils = require("sdk/deprecated/window-utils");
- let widgets = require("sdk/widget");
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
- let browserWindow = windowUtils.activeBrowserWindow;
+ let browserWindow = getMostRecentBrowserWindow();
let doc = browserWindow.document;
let label = "unique-widget-label";
@@ -939,6 +984,7 @@ exports.testWidgetMove = function testWidgetMove(assert, done) {
else {
assert.equal(origMessage, message, "Got message after node move");
widget.destroy();
+ loader.unload();
done();
}
}
@@ -953,16 +999,17 @@ consider the content change a navigation change, so doesn't load
the new content.
*/
exports.testWidgetWithPound = function testWidgetWithPound(assert, done) {
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+
function getWidgetContent(widget) {
- let windowUtils = require("sdk/deprecated/window-utils");
- let browserWindow = windowUtils.activeBrowserWindow;
+ let browserWindow = getMostRecentBrowserWindow();
let doc = browserWindow.document;
let widgetNode = doc.querySelector('toolbaritem[label="' + widget.label + '"]');
assert.ok(widgetNode, 'found widget node in the front-end');
return widgetNode.firstChild.contentDocument.body.innerHTML;
}
- let widgets = require("sdk/widget");
let count = 0;
let widget = widgets.Widget({
id: "1",
@@ -977,6 +1024,7 @@ exports.testWidgetWithPound = function testWidgetWithPound(assert, done) {
else {
assert.equal(getWidgetContent(widget), "foo#", "content updated to pound?");
widget.destroy();
+ loader.unload();
done();
}
}
@@ -984,7 +1032,10 @@ exports.testWidgetWithPound = function testWidgetWithPound(assert, done) {
};
exports.testContentScriptOptionsOption = function(assert, done) {
- let widget = require("sdk/widget").Widget({
+ let { loader } = LoaderWithHookedConsole(module);
+ const { Widget } = loader.require("sdk/widget");
+
+ let widget = Widget({
id: "widget-script-options",
label: "fooz",
content: "fooz",
@@ -998,26 +1049,34 @@ exports.testContentScriptOptionsOption = function(assert, done) {
assert.equal( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' );
assert.equal( msg[1].c, 'string', 'string in contentScriptOptions' );
widget.destroy();
+ loader.unload();
done();
}
});
};
exports.testOnAttachWithoutContentScript = function(assert, done) {
- let widget = require("sdk/widget").Widget({
+ let { loader } = LoaderWithHookedConsole(module);
+ const { Widget } = loader.require("sdk/widget");
+
+ let widget = Widget({
id: "onAttachNoCS",
label: "onAttachNoCS",
content: "onAttachNoCS",
onAttach: function (view) {
assert.pass("received attach event");
widget.destroy();
+ loader.unload();
done();
}
});
};
exports.testPostMessageOnAttach = function(assert, done) {
- let widget = require("sdk/widget").Widget({
+ let { loader } = LoaderWithHookedConsole(module);
+ const { Widget } = loader.require("sdk/widget");
+
+ let widget = Widget({
id: "onAttach",
label: "onAttach",
content: "onAttach",
@@ -1031,15 +1090,19 @@ exports.testPostMessageOnAttach = function(assert, done) {
onMessage: function (msg) {
assert.equal( msg, "ok", "postMessage works on `attach` event");
widget.destroy();
+ loader.unload();
done();
}
});
};
exports.testPostMessageOnLocationChange = function(assert, done) {
+ let { loader } = LoaderWithHookedConsole(module);
+ const { Widget } = loader.require("sdk/widget");
+
let attachEventCount = 0;
let messagesCount = 0;
- let widget = require("sdk/widget").Widget({
+ let widget = Widget({
id: "onLocationChange",
label: "onLocationChange",
content: "onLocationChange",
@@ -1065,6 +1128,7 @@ exports.testPostMessageOnLocationChange = function(assert, done) {
assert.equal(msg, "ok",
"We receive the message sent to the 2nd document");
widget.destroy();
+ loader.unload();
done();
}
}
@@ -1072,10 +1136,13 @@ exports.testPostMessageOnLocationChange = function(assert, done) {
};
exports.testSVGWidget = function(assert, done) {
+ let { loader } = LoaderWithHookedConsole(module);
+ const { Widget } = loader.require("sdk/widget");
+
// use of capital SVG here is intended, that was failing..
let SVG_URL = fixtures.url("mofo_logo.SVG");
- let widget = require("sdk/widget").Widget({
+ let widget = Widget({
id: "mozilla-svg-logo",
label: "moz foundation logo",
contentURL: SVG_URL,
@@ -1084,16 +1151,19 @@ exports.testSVGWidget = function(assert, done) {
widget.destroy();
assert.equal(data.count, 1, 'only one image');
assert.equal(data.src, SVG_URL, 'only one image');
+ loader.unload();
done();
}
});
};
exports.testReinsertion = function(assert, done) {
+ let { loader } = LoaderWithHookedConsole(module);
+ const { Widget } = loader.require("sdk/widget");
const WIDGETID = "test-reinsertion";
- let windowUtils = require("sdk/deprecated/window-utils");
- let browserWindow = windowUtils.activeBrowserWindow;
- let widget = require("sdk/widget").Widget({
+ let browserWindow = getMostRecentBrowserWindow();
+
+ let widget = Widget({
id: "test-reinsertion",
label: "test reinsertion",
content: "Test",
@@ -1105,8 +1175,39 @@ exports.testReinsertion = function(assert, done) {
openNewWindowTab("about:blank", { inNewWindow: true, onLoad: function(e) {
assert.equal(e.target.defaultView.document.getElementById(realWidgetId), null);
- close(e.target.defaultView).then(done);
+ close(e.target.defaultView).then(_ => {
+ loader.unload();
+ done();
+ });
}});
};
+exports.testWideWidget = function testWideWidget(assert) {
+ let { loader } = LoaderWithHookedConsole(module);
+ const widgets = loader.require("sdk/widget");
+ const { document, CustomizableUI, gCustomizeMode, setTimeout } = getMostRecentBrowserWindow();
+
+ let wideWidget = widgets.Widget({
+ id: "my-wide-widget",
+ label: "wide-wdgt",
+ content: "foo",
+ width: 200
+ });
+
+ let widget = widgets.Widget({
+ id: "my-regular-widget",
+ label: "reg-wdgt",
+ content: "foo"
+ });
+
+ let wideWidgetNode = document.querySelector("toolbaritem[label=wide-wdgt]");
+ let widgetNode = document.querySelector("toolbaritem[label=reg-wdgt]");
+
+ assert.equal(wideWidgetNode, null,
+ "Wide Widget are not added to UI");
+
+ assert.notEqual(widgetNode, null,
+ "regular size widget are in the UI");
+};
+
require("sdk/test").run(exports);
diff --git a/b2g/simulator/build_xpi.py b/b2g/simulator/build_xpi.py
index 847b7aaf8f7a..6db8483391d3 100644
--- a/b2g/simulator/build_xpi.py
+++ b/b2g/simulator/build_xpi.py
@@ -131,7 +131,7 @@ def main(platform):
])
# Ship b2g-desktop, but prevent its gaia profile to be shipped in the xpi
- add_dir_to_zip(xpi_path, os.path.join(distdir, "b2g"), "b2g", ("gaia"))
+ add_dir_to_zip(xpi_path, os.path.join(distdir, "b2g"), "b2g", ("gaia", "B2G.app/Contents/MacOS/gaia"))
# Then ship our own gaia profile
add_dir_to_zip(xpi_path, os.path.join(gaia_path, "profile"), "profile")
diff --git a/b2g/simulator/lib/simulator-process.js b/b2g/simulator/lib/simulator-process.js
index d8657a82cf6b..1836c56ab081 100644
--- a/b2g/simulator/lib/simulator-process.js
+++ b/b2g/simulator/lib/simulator-process.js
@@ -141,7 +141,7 @@ exports.SimulatorProcess = Class({
let bin = URL.toFilename(BIN_URL);
let executables = {
WINNT: "b2g-bin.exe",
- Darwin: "Contents/MacOS/b2g-bin",
+ Darwin: "B2G.app/Contents/MacOS/b2g-bin",
Linux: "b2g-bin",
};
diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul
index 8c0bdbd285b5..96b98f50e802 100644
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1182,15 +1182,14 @@
#include tab-shape.inc.svg
-#ifndef XP_UNIX
-
+#ifndef XP_MACOSX
+
-
+
-#endif
-#ifdef XP_MACOSX
+#else
diff --git a/browser/base/content/test/general/browser_popupNotification.js b/browser/base/content/test/general/browser_popupNotification.js
index fa66929b9efd..326422ff8c72 100644
--- a/browser/base/content/test/general/browser_popupNotification.js
+++ b/browser/base/content/test/general/browser_popupNotification.js
@@ -529,7 +529,12 @@ var tests = [
this.notifyObj.addOptions({dismissed: true});
this.notification = showNotification(this.notifyObj);
- EventUtils.synthesizeMouse(button, 1, 1, {});
+ // This test places a normal button in the notification area, which has
+ // standard GTK styling and dimensions. Due to the clip-path, this button
+ // gets clipped off, which makes it necessary to synthesize the mouse click
+ // a little bit downward. To be safe, I adjusted the x-offset with the same
+ // amount.
+ EventUtils.synthesizeMouse(button, 4, 4, {});
},
onShown: function(popup) {
checkPopup(popup, this.notifyObj);
diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js
index 43ea8cdd1e20..825fae8267e5 100644
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -49,6 +49,7 @@ const PanelUI = {
this.menuButton.addEventListener("keypress", this);
this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
+ CustomizableUI.addListener(this);
this._initialized = true;
},
@@ -69,10 +70,6 @@ const PanelUI = {
},
uninit: function() {
- if (!this._eventListenersAdded) {
- return;
- }
-
for (let event of this.kEvents) {
this.panel.removeEventListener(event, this);
}
@@ -80,6 +77,7 @@ const PanelUI = {
this.menuButton.removeEventListener("mousedown", this);
this.menuButton.removeEventListener("keypress", this);
window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
+ CustomizableUI.removeListener(this);
this._overlayScrollListenerBoundFn = null;
},
@@ -183,6 +181,7 @@ const PanelUI = {
handleEvent: function(aEvent) {
switch (aEvent.type) {
case "popupshowing":
+ this._adjustLabelsForAutoHyphens();
// Fall through
case "popupshown":
// Fall through
@@ -370,6 +369,26 @@ const PanelUI = {
"browser");
},
+ onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer, aWasRemoval) {
+ if (aContainer != this.contents) {
+ return;
+ }
+ if (aWasRemoval) {
+ aNode.removeAttribute("auto-hyphens");
+ }
+ },
+
+ onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, aIsRemoval) {
+ if (aContainer != this.contents) {
+ return;
+ }
+ if (!aIsRemoval &&
+ (this.panel.state == "open" ||
+ document.documentElement.hasAttribute("customizing"))) {
+ this._adjustLabelsForAutoHyphens(aNode);
+ }
+ },
+
/**
* Signal that we're about to make a lot of changes to the contents of the
* panels all at once. For performance, we ignore the mutations.
@@ -389,6 +408,22 @@ const PanelUI = {
this.multiView.ignoreMutations = false;
},
+ _adjustLabelsForAutoHyphens: function(aNode) {
+ let toolbarButtons = aNode ? [aNode] :
+ this.contents.querySelectorAll(".toolbarbutton-1");
+ for (let node of toolbarButtons) {
+ let label = node.getAttribute("label");
+ if (!label) {
+ continue;
+ }
+ if (label.contains("\u00ad")) {
+ node.setAttribute("auto-hyphens", "off");
+ } else {
+ node.removeAttribute("auto-hyphens");
+ }
+ }
+ },
+
/**
* Sets the anchor node into the open or closed state, depending
* on the state of the panel.
diff --git a/browser/components/customizableui/test/browser.ini b/browser/components/customizableui/test/browser.ini
index 424fb4850bad..a08b044936dd 100644
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -68,6 +68,7 @@ skip-if = os == "linux"
[browser_948985_non_removable_defaultArea.js]
[browser_952963_areaType_getter_no_area.js]
[browser_956602_remove_special_widget.js]
+[browser_962884_opt_in_disable_hyphens.js]
[browser_963639_customizing_attribute_non_customizable_toolbar.js]
[browser_967000_button_charEncoding.js]
[browser_967000_button_feeds.js]
diff --git a/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js b/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js
new file mode 100644
index 000000000000..a686e3699892
--- /dev/null
+++ b/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js
@@ -0,0 +1,67 @@
+/* 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";
+
+add_task(function() {
+ const kNormalLabel = "Character Encoding";
+ CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_NAVBAR);
+ let characterEncoding = document.getElementById("characterencoding-button");
+ const kOriginalLabel = characterEncoding.getAttribute("label");
+ characterEncoding.setAttribute("label", "\u00ad" + kNormalLabel);
+ CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_PANEL);
+
+ yield PanelUI.show();
+
+ is(characterEncoding.getAttribute("auto-hyphens"), "off",
+ "Hyphens should be disabled if the character is present in the label");
+ let multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text");
+ let multilineTextCS = getComputedStyle(multilineText);
+ is(multilineTextCS.MozHyphens, "manual", "-moz-hyphens should be set to manual when the character is present.")
+
+ let hiddenPanelPromise = promisePanelHidden(window);
+ PanelUI.toggle();
+ yield hiddenPanelPromise;
+
+ characterEncoding.setAttribute("label", kNormalLabel);
+
+ yield PanelUI.show();
+
+ isnot(characterEncoding.getAttribute("auto-hyphens"), "off",
+ "Hyphens should not be disabled if the character is not present in the label");
+ multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text");
+ let multilineTextCS = getComputedStyle(multilineText);
+ is(multilineTextCS.MozHyphens, "auto", "-moz-hyphens should be set to auto by default.")
+
+ hiddenPanelPromise = promisePanelHidden(window);
+ PanelUI.toggle();
+ yield hiddenPanelPromise;
+
+ characterEncoding.setAttribute("label", "\u00ad" + kNormalLabel);
+ CustomizableUI.removeWidgetFromArea("characterencoding-button");
+ yield startCustomizing();
+
+ isnot(characterEncoding.getAttribute("auto-hyphens"), "off",
+ "Hyphens should not be disabled when the widget is in the palette");
+
+ gCustomizeMode.addToPanel(characterEncoding);
+ is(characterEncoding.getAttribute("auto-hyphens"), "off",
+ "Hyphens should be disabled if the character is present in the label in customization mode");
+ let multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text");
+ let multilineTextCS = getComputedStyle(multilineText);
+ is(multilineTextCS.MozHyphens, "manual", "-moz-hyphens should be set to manual when the character is present in customization mode.")
+
+ yield endCustomizing();
+
+ CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_NAVBAR);
+ ok(!characterEncoding.hasAttribute("auto-hyphens"),
+ "Removing the widget from the panel should remove the auto-hyphens attribute");
+
+ characterEncoding.setAttribute("label", kOriginalLabel);
+});
+
+add_task(function asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/preferences/in-content/advanced.xul b/browser/components/preferences/in-content/advanced.xul
index 9231fe4972c9..2bef7b1e6547 100644
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -128,6 +128,15 @@
#endif
+
+
diff --git a/browser/components/preferences/in-content/applications.xul b/browser/components/preferences/in-content/applications.xul
index 4c6507d11f8b..b1bc9e82a3fc 100644
--- a/browser/components/preferences/in-content/applications.xul
+++ b/browser/components/preferences/in-content/applications.xul
@@ -55,7 +55,19 @@
-
+
+
+
+
+
diff --git a/browser/components/preferences/in-content/main.xul b/browser/components/preferences/in-content/main.xul
index f9e1276277a4..be7d4fd52fa6 100644
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -85,6 +85,14 @@
#endif
+
+
diff --git a/browser/components/preferences/in-content/preferences.js b/browser/components/preferences/in-content/preferences.js
index 89cac4227626..7630cca7a1d1 100644
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -32,10 +32,8 @@ function init_all() {
let categories = document.getElementById("categories");
categories.addEventListener("select", event => gotoPref(event.target.value));
- window.addEventListener("popstate", event => selectCategory(event.state));
if (history.length > 1 && history.state) {
- updateCommands();
selectCategory(history.state);
} else {
history.replaceState("paneGeneral", document.title);
@@ -49,38 +47,9 @@ function selectCategory(name) {
}
function gotoPref(page) {
- if (history.state != page) {
- window.history.pushState(page, document.title);
- }
-
- updateCommands();
+ window.history.replaceState(page, document.title);
search(page, "data-category");
}
-
-function cmd_back() {
- window.history.back();
-}
-
-function cmd_forward() {
- window.history.forward();
-}
-
-function updateCommands() {
- document.getElementById("back-btn").disabled = !canGoBack();
- document.getElementById("forward-btn").disabled = !canGoForward();
-}
-
-function canGoBack() {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .canGoBack;
-}
-
-function canGoForward() {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .canGoForward;
-}
function search(aQuery, aAttribute) {
let elements = document.getElementById("mainPrefPane").children;
diff --git a/browser/components/preferences/in-content/preferences.xul b/browser/components/preferences/in-content/preferences.xul
index 783a23f9de84..1321a41e58cc 100644
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -74,15 +74,6 @@
src="chrome://browser/locale/preferences/applicationManager.properties"/>
-
-
diff --git a/browser/components/preferences/in-content/privacy.xul b/browser/components/preferences/in-content/privacy.xul
index b3900e053ec9..e49cb3d12b93 100644
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -65,6 +65,15 @@
+
+
diff --git a/browser/components/preferences/in-content/security.xul b/browser/components/preferences/in-content/security.xul
index 995855dc0514..e5f10e317f27 100644
--- a/browser/components/preferences/in-content/security.xul
+++ b/browser/components/preferences/in-content/security.xul
@@ -30,6 +30,15 @@
+
+
diff --git a/browser/components/preferences/in-content/sync.xul b/browser/components/preferences/in-content/sync.xul
index e1689f17804e..34c0aad0e7d8 100644
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -28,6 +28,15 @@
+
+
diff --git a/browser/components/sessionstore/src/SessionSaver.jsm b/browser/components/sessionstore/src/SessionSaver.jsm
index c6120599dad8..7eabb7863efe 100644
--- a/browser/components/sessionstore/src/SessionSaver.jsm
+++ b/browser/components/sessionstore/src/SessionSaver.jsm
@@ -23,6 +23,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
// Minimal interval between two save operations (in milliseconds).
XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
@@ -41,14 +43,6 @@ XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
return Services.prefs.getIntPref(PREF);
});
-// Wrap a string as a nsISupports.
-function createSupportsString(data) {
- let string = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- string.data = data;
- return string;
-}
-
// Notify observers about a given topic with a given subject.
function notify(subject, topic) {
Services.obs.notifyObservers(subject, topic, "");
@@ -192,6 +186,14 @@ let SessionSaverInternal = {
// Cancel any pending timeouts.
this.cancel();
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Don't save (or even collect) anything in permanent private
+ // browsing mode
+
+ this.updateLastSaveTime();
+ return Promise.resolve();
+ }
+
stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
let state = SessionStore.getCurrentState(forceUpdateAllWindows);
PrivacyFilter.filterPrivateWindowsAndTabs(state);
@@ -242,19 +244,13 @@ let SessionSaverInternal = {
* Write the given state object to disk.
*/
_writeState: function (state) {
+ // Inform observers
+ notify(null, "sessionstore-state-write");
+
stopWatchStart("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS", "WRITE_STATE_LONGEST_OP_MS");
let data = JSON.stringify(state);
stopWatchFinish("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
- // Give observers a chance to modify session data.
- data = this._notifyObserversBeforeStateWrite(data);
-
- // Don't touch the file if an observer has deleted all state data.
- if (!data) {
- stopWatchCancel("WRITE_STATE_LONGEST_OP_MS");
- return Promise.resolve();
- }
-
// We update the time stamp before writing so that we don't write again
// too soon, if saving is requested before the write completes. Without
// this update we may save repeatedly if actions cause a runDelayed
@@ -275,14 +271,4 @@ let SessionSaverInternal = {
return promise;
},
-
- /**
- * Notify sessionstore-state-write observer and give them a
- * chance to modify session data before we'll write it to disk.
- */
- _notifyObserversBeforeStateWrite: function (data) {
- let stateString = createSupportsString(data);
- notify(stateString, "sessionstore-state-write");
- return stateString.data;
- }
};
diff --git a/browser/components/sessionstore/src/SessionStore.jsm b/browser/components/sessionstore/src/SessionStore.jsm
index 269087840289..b7bf3519f549 100644
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -1058,16 +1058,26 @@ let SessionStoreInternal = {
// recently something was closed.
winData.closedAt = Date.now();
- // Save the window if it has multiple tabs or a single saveable tab and
- // it's not private.
+ // Save non-private windows if they have at
+ // least one saveable tab or are the last window.
if (!winData.isPrivate) {
// Remove any open private tabs the window may contain.
PrivacyFilter.filterPrivateTabs(winData);
- let hasSingleTabToSave =
- winData.tabs.length == 1 && this._shouldSaveTabState(winData.tabs[0]);
+ // Determine whether the window has any tabs worth saving.
+ let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState);
- if (hasSingleTabToSave || winData.tabs.length > 1) {
+ // When closing windows one after the other until Firefox quits, we
+ // will move those closed in series back to the "open windows" bucket
+ // before writing to disk. If however there is only a single window
+ // with tabs we deem not worth saving then we might end up with a
+ // random closed or even a pop-up window re-opened. To prevent that
+ // we explicitly allow saving an "empty" window state.
+ let isLastWindow =
+ Object.keys(this._windows).length == 1 &&
+ !this._closedWindows.some(win => win._shouldRestore || false);
+
+ if (hasSaveableTabs || isLastWindow) {
// we don't want to save the busy state
delete winData.busy;
diff --git a/browser/components/sessionstore/test/browser.ini b/browser/components/sessionstore/test/browser.ini
index 23ad9553a596..931ce3c3c0cf 100644
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -98,7 +98,6 @@ skip-if = true
[browser_394759_purge.js]
[browser_423132.js]
[browser_447951.js]
-[browser_448741.js]
[browser_454908.js]
[browser_456342.js]
[browser_461634.js]
diff --git a/browser/components/sessionstore/test/browser_394759_perwindowpb.js b/browser/components/sessionstore/test/browser_394759_perwindowpb.js
index 6e5d389620f8..da8fa4091217 100644
--- a/browser/components/sessionstore/test/browser_394759_perwindowpb.js
+++ b/browser/components/sessionstore/test/browser_394759_perwindowpb.js
@@ -75,15 +75,13 @@ function test() {
// Wait for the sessionstore.js file to be written before going on.
// Note: we don't wait for the complete event, since if asyncCopy fails we
// would timeout.
- Services.obs.addObserver(function (aSubject, aTopic, aData) {
- Services.obs.removeObserver(arguments.callee, aTopic);
- info("sessionstore.js is being written");
-
+ waitForSaveState(function(writing) {
+ ok(writing, "sessionstore.js is being written");
closedWindowCount = ss.getClosedWindowCount();
is(closedWindowCount, 0, "Correctly set window count");
executeSoon(aCallback);
- }, "sessionstore-state-write", false);
+ });
// Remove the sessionstore.js file before setting the interval to 0
let profilePath = Services.dirsvc.get("ProfD", Ci.nsIFile);
diff --git a/browser/components/sessionstore/test/browser_448741.js b/browser/components/sessionstore/test/browser_448741.js
deleted file mode 100644
index 8ed6f66c8a19..000000000000
--- a/browser/components/sessionstore/test/browser_448741.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-function test() {
- /** Test for Bug 448741 **/
-
- waitForExplicitFinish();
-
- let uniqueName = "bug 448741";
- let uniqueValue = "as good as unique: " + Date.now();
-
- // set a unique value on a new, blank tab
- var tab = gBrowser.addTab();
- tab.linkedBrowser.stop();
- ss.setTabValue(tab, uniqueName, uniqueValue);
- let valueWasCleaned = false;
-
- // prevent our value from being written to disk
- function cleaningObserver(aSubject, aTopic, aData) {
- ok(aTopic == "sessionstore-state-write", "observed correct topic?");
- ok(aSubject instanceof Ci.nsISupportsString, "subject is a string?");
- ok(aSubject.data.indexOf(uniqueValue) > -1, "data contains our value?");
-
- // find the data for the newly added tab and delete it
- let state = JSON.parse(aSubject.data);
- state.windows.forEach(function (winData) {
- winData.tabs.forEach(function (tabData) {
- if (tabData.extData && uniqueName in tabData.extData &&
- tabData.extData[uniqueName] == uniqueValue) {
- delete tabData.extData[uniqueName];
- valueWasCleaned = true;
- }
- });
- });
-
- ok(valueWasCleaned, "found and removed the specific tab value");
- aSubject.data = JSON.stringify(state);
- Services.obs.removeObserver(cleaningObserver, aTopic);
- }
-
- // make sure that all later observers don't see that value any longer
- function checkingObserver(aSubject, aTopic, aData) {
- ok(valueWasCleaned && aSubject instanceof Ci.nsISupportsString,
- "ready to check the cleaned state?");
- ok(aSubject.data.indexOf(uniqueValue) == -1, "data no longer contains our value?");
-
- // clean up
- gBrowser.removeTab(tab);
- Services.obs.removeObserver(checkingObserver, aTopic);
- if (gPrefService.prefHasUserValue("browser.sessionstore.interval"))
- gPrefService.clearUserPref("browser.sessionstore.interval");
- finish();
- }
-
- // last added observers are invoked first
- Services.obs.addObserver(checkingObserver, "sessionstore-state-write", false);
- Services.obs.addObserver(cleaningObserver, "sessionstore-state-write", false);
-
- // trigger an immediate save operation
- gPrefService.setIntPref("browser.sessionstore.interval", 0);
-}
diff --git a/browser/components/sessionstore/test/browser_600545.js b/browser/components/sessionstore/test/browser_600545.js
index a5a81f5cbfbe..ff7c33707905 100644
--- a/browser/components/sessionstore/test/browser_600545.js
+++ b/browser/components/sessionstore/test/browser_600545.js
@@ -20,11 +20,12 @@ function testBug600545() {
Services.prefs.clearUserPref("browser.sessionstore.interval");
});
- // This tests the following use case:
- // When multiple windows are open and browser.sessionstore.resume_from_crash
- // preference is false, tab session data for non-active window is stripped for
- // non-pinned tabs. This occurs after "sessionstore-state-write" fires which
- // will only fire in this case if there is at least one pinned tab.
+ // This tests the following use case: When multiple windows are open
+ // and browser.sessionstore.resume_from_crash preference is false,
+ // tab session data for non-active window is stripped for non-pinned
+ // tabs. This occurs after "sessionstore-state-write-complete"
+ // fires which will only fire in this case if there is at least one
+ // pinned tab.
let state = { windows: [
{
tabs: [
diff --git a/browser/components/sessionstore/test/browser_625016.js b/browser/components/sessionstore/test/browser_625016.js
index 1f1c2d2b3d2a..29085c72f55a 100644
--- a/browser/components/sessionstore/test/browser_625016.js
+++ b/browser/components/sessionstore/test/browser_625016.js
@@ -1,125 +1,86 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
-let newWin;
-let newTab;
-
-function test() {
+add_task(function* setup() {
/** Test for Bug 625016 - Restore windows closed in succession to quit (non-OSX only) **/
- // We'll test this by opening a new window, waiting for the save event, then
- // closing that window. We'll observe the "sessionstore-state-write" notification
- // and check that the state contains no _closedWindows. We'll then add a new
- // tab and make sure that the state following that was reset and the closed
- // window is now in _closedWindows.
+ // We'll test this by opening a new window, waiting for the save
+ // event, then closing that window. We'll observe the
+ // "sessionstore-state-write-complete" notification and check that
+ // the state contains no _closedWindows. We'll then add a new tab
+ // and make sure that the state following that was reset and the
+ // closed window is now in _closedWindows.
- waitForExplicitFinish();
requestLongerTimeout(2);
- // We speed up the interval between session saves to ensure that the test
- // runs quickly.
- Services.prefs.setIntPref("browser.sessionstore.interval", 4000);
- registerCleanupFunction(function () {
- Services.prefs.clearUserPref("browser.sessionstore.interval");
- });
+ yield forceSaveState();
- waitForSaveState(setup);
-}
-
-function setup() {
// We'll clear all closed windows to make sure our state is clean
// forgetClosedWindow doesn't trigger a delayed save
while (ss.getClosedWindowCount()) {
ss.forgetClosedWindow(0);
}
is(ss.getClosedWindowCount(), 0, "starting with no closed windows");
+});
- // Open a new window, which should trigger a save event soon.
- waitForSaveState(onSaveState);
- newWin = openDialog(location, "_blank", "chrome,all,dialog=no", "about:rights");
-}
-
-function onSaveState() {
+add_task(function* new_window() {
+ let newWin;
try {
- ss.getWindowValue(newWin, "foobar");
- } catch (e) {
- // The window is untracked which means that the saveState() call isn't the
- // one we're waiting for. It's most likely been triggered by an async
- // collection running in the background.
- waitForSaveState(onSaveState);
- return;
+ newWin = yield promiseNewWindowLoaded();
+ let tab = newWin.gBrowser.addTab("http://example.com/browser_625016.js?" + Math.random());
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ // Double check that we have no closed windows
+ is(ss.getClosedWindowCount(), 0, "no closed windows on first save");
+
+ yield promiseWindowClosed(newWin);
+ newWin = null;
+
+ let state = JSON.parse((yield promiseSaveFileContents()));
+ is(state.windows.length, 2,
+ "observe1: 2 windows in data written to disk");
+ is(state._closedWindows.length, 0,
+ "observe1: no closed windows in data written to disk");
+
+ // The API still treats the closed window as closed, so ensure that window is there
+ is(ss.getClosedWindowCount(), 1,
+ "observe1: 1 closed window according to API");
+ } finally {
+ if (newWin) {
+ yield promiseWindowClosed(newWin);
+ }
+ yield forceSaveState();
}
-
- // Double check that we have no closed windows
- is(ss.getClosedWindowCount(), 0, "no closed windows on first save");
-
- Services.obs.addObserver(observe1, "sessionstore-state-write", false);
-
- // Now close the new window, which should trigger another save event
- newWin.close();
-}
-
-function observe1(aSubject, aTopic, aData) {
- info("observe1: " + aTopic);
- switch (aTopic) {
- case "sessionstore-state-write":
- aSubject.QueryInterface(Ci.nsISupportsString);
- let state = JSON.parse(aSubject.data);
- is(state.windows.length, 2,
- "observe1: 2 windows in data being writted to disk");
- is(state._closedWindows.length, 0,
- "observe1: no closed windows in data being writted to disk");
-
- // The API still treats the closed window as closed, so ensure that window is there
- is(ss.getClosedWindowCount(), 1,
- "observe1: 1 closed window according to API");
- Services.obs.removeObserver(observe1, "sessionstore-state-write");
- Services.obs.addObserver(observe1, "sessionstore-state-write-complete", false);
- break;
- case "sessionstore-state-write-complete":
- Services.obs.removeObserver(observe1, "sessionstore-state-write-complete");
- openTab();
- break;
- }
-}
-
-function observe2(aSubject, aTopic, aData) {
- info("observe2: " + aTopic);
- switch (aTopic) {
- case "sessionstore-state-write":
- aSubject.QueryInterface(Ci.nsISupportsString);
- let state = JSON.parse(aSubject.data);
- is(state.windows.length, 1,
- "observe2: 1 window in data being writted to disk");
- is(state._closedWindows.length, 1,
- "observe2: 1 closed window in data being writted to disk");
-
- // The API still treats the closed window as closed, so ensure that window is there
- is(ss.getClosedWindowCount(), 1,
- "observe2: 1 closed window according to API");
- Services.obs.removeObserver(observe2, "sessionstore-state-write");
- Services.obs.addObserver(observe2, "sessionstore-state-write-complete", false);
- break;
- case "sessionstore-state-write-complete":
- Services.obs.removeObserver(observe2, "sessionstore-state-write-complete");
- done();
- break;
- }
-}
+});
// We'll open a tab, which should trigger another state save which would wipe
// the _shouldRestore attribute from the closed window
-function openTab() {
- Services.obs.addObserver(observe2, "sessionstore-state-write", false);
- newTab = gBrowser.addTab("about:mozilla");
-}
+add_task(function* new_tab() {
+ let newTab;
+ try {
+ newTab = gBrowser.addTab("about:mozilla");
-function done() {
- gBrowser.removeTab(newTab);
+ let state = JSON.parse((yield promiseSaveFileContents()));
+ is(state.windows.length, 1,
+ "observe2: 1 window in data being written to disk");
+ is(state._closedWindows.length, 1,
+ "observe2: 1 closed window in data being written to disk");
+
+ // The API still treats the closed window as closed, so ensure that window is there
+ is(ss.getClosedWindowCount(), 1,
+ "observe2: 1 closed window according to API");
+ } finally {
+ gBrowser.removeTab(newTab);
+ }
+});
+
+
+add_task(function* done() {
// The API still represents the closed window as closed, so we can clear it
// with the API, but just to make sure...
- is(ss.getClosedWindowCount(), 1, "1 closed window according to API");
- ss.forgetClosedWindow(0);
- executeSoon(finish);
-}
-
+// is(ss.getClosedWindowCount(), 1, "1 closed window according to API");
+ while (ss.getClosedWindowCount()) {
+ ss.forgetClosedWindow(0);
+ }
+ Services.prefs.clearUserPref("browser.sessionstore.interval");
+});
diff --git a/browser/components/sessionstore/test/browser_819510_perwindowpb.js b/browser/components/sessionstore/test/browser_819510_perwindowpb.js
index 5b83fa2e8da6..fc4e93a0003d 100644
--- a/browser/components/sessionstore/test/browser_819510_perwindowpb.js
+++ b/browser/components/sessionstore/test/browser_819510_perwindowpb.js
@@ -162,15 +162,9 @@ function waitForWindowClose(aWin, aCallback) {
}
function forceWriteState(aCallback) {
- Services.obs.addObserver(function observe(aSubject, aTopic, aData) {
- if (aTopic == "sessionstore-state-write") {
- Services.obs.removeObserver(observe, aTopic);
- Services.prefs.clearUserPref("browser.sessionstore.interval");
- aSubject.QueryInterface(Ci.nsISupportsString);
- aCallback(JSON.parse(aSubject.data));
- }
- }, "sessionstore-state-write", false);
- Services.prefs.setIntPref("browser.sessionstore.interval", 0);
+ return promiseSaveFileContents().then(function(data) {
+ aCallback(JSON.parse(data));
+ });
}
function testOnWindow(aIsPrivate, aCallback) {
diff --git a/browser/components/sessionstore/test/browser_833286_atomic_backup.js b/browser/components/sessionstore/test/browser_833286_atomic_backup.js
index 7f482273cbf3..ab7f68f5c497 100644
--- a/browser/components/sessionstore/test/browser_833286_atomic_backup.js
+++ b/browser/components/sessionstore/test/browser_833286_atomic_backup.js
@@ -24,43 +24,9 @@ let gDecoder = new TextDecoder();
let gSSData;
let gSSBakData;
-// Wait for a state write to complete and then execute a callback.
-function waitForSaveStateComplete(aSaveStateCallback) {
- let topic = "sessionstore-state-write-complete";
- function observer() {
- Services.prefs.clearUserPref(PREF_SS_INTERVAL);
- Services.obs.removeObserver(observer, topic);
- executeSoon(function taskCallback() {
- Task.spawn(aSaveStateCallback);
- });
- }
- Services.obs.addObserver(observer, topic, false);
-}
-
-// Register next test callback and trigger state saving change.
-function nextTest(testFunc) {
- waitForSaveStateComplete(testFunc);
-
- // We set the interval for session store state saves to be zero
- // to cause a save ASAP.
- Services.prefs.setIntPref(PREF_SS_INTERVAL, 0);
-}
-
-registerCleanupFunction(function() {
- // Cleaning up after the test: removing the sessionstore.bak file.
- Task.spawn(function cleanupTask() {
- yield OS.File.remove(backupPath);
- });
-});
-
-function test() {
- waitForExplicitFinish();
- nextTest(testAfterFirstWrite);
-}
-
-function testAfterFirstWrite() {
+add_task(function* testAfterFirstWrite() {
// Ensure sessionstore.bak is not created. We start with a clean
// profile so there was nothing to move to sessionstore.bak before
// initially writing sessionstore.js
@@ -78,10 +44,10 @@ function testAfterFirstWrite() {
// and a backup would not be triggered again.
yield OS.File.move(path, backupPath);
- nextTest(testReadBackup);
-}
+ yield forceSaveState();
+});
-function testReadBackup() {
+add_task(function* testReadBackup() {
// Ensure sessionstore.bak is finally created.
let ssExists = yield OS.File.exists(path);
let ssBackupExists = yield OS.File.exists(backupPath);
@@ -114,10 +80,10 @@ function testReadBackup() {
is(ssDataRead, gSSBakData,
"SessionFile.read read sessionstore.bak correctly.");
- nextTest(testBackupUnchanged);
-}
+ yield forceSaveState();
+});
-function testBackupUnchanged() {
+add_task(function* testBackupUnchanged() {
// Ensure sessionstore.bak is backed up only once.
// Read sessionstore.bak data.
@@ -125,6 +91,9 @@ function testBackupUnchanged() {
let ssBakData = gDecoder.decode(array);
// Ensure the sessionstore.bak did not change.
is(ssBakData, gSSBakData, "sessionstore.bak is unchanged.");
+});
- executeSoon(finish);
-}
+add_task(function* cleanup() {
+ // Cleaning up after the test: removing the sessionstore.bak file.
+ yield OS.File.remove(backupPath);
+});
diff --git a/browser/components/sessionstore/test/head.js b/browser/components/sessionstore/test/head.js
index f07f03e32856..68d8d2279b1c 100644
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -243,12 +243,7 @@ function waitForTopic(aTopic, aTimeout, aCallback) {
/**
* Wait until session restore has finished collecting its data and is
- * getting ready to write that data ("sessionstore-state-write").
- *
- * This function is meant to be called immediately after the code
- * that will trigger the saving.
- *
- * Note that this does not wait for the disk write to be complete.
+ * has written that data ("sessionstore-state-write-complete").
*
* @param {function} aCallback If sessionstore-state-write is sent
* within buffering interval + 100 ms, the callback is passed |true|,
@@ -257,7 +252,7 @@ function waitForTopic(aTopic, aTimeout, aCallback) {
function waitForSaveState(aCallback) {
let timeout = 100 +
Services.prefs.getIntPref("browser.sessionstore.interval");
- return waitForTopic("sessionstore-state-write", timeout, aCallback);
+ return waitForTopic("sessionstore-state-write-complete", timeout, aCallback);
}
function promiseSaveState() {
let deferred = Promise.defer();
@@ -272,22 +267,30 @@ function promiseSaveState() {
function forceSaveState() {
let promise = promiseSaveState();
const PREF = "browser.sessionstore.interval";
+ let original = Services.prefs.getIntPref(PREF);
// Set interval to an arbitrary non-0 duration
// to ensure that setting it to 0 will notify observers
Services.prefs.setIntPref(PREF, 1000);
Services.prefs.setIntPref(PREF, 0);
return promise.then(
function onSuccess(x) {
- Services.prefs.clearUserPref(PREF);
+ Services.prefs.setIntPref(PREF, original);
return x;
},
function onError(x) {
- Services.prefs.clearUserPref(PREF);
+ Services.prefs.setIntPref(PREF, original);
throw x;
}
);
}
+function promiseSaveFileContents() {
+ let promise = forceSaveState();
+ return promise.then(function() {
+ return OS.File.read(OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"), { encoding: "utf-8" });
+ });
+}
+
function whenBrowserLoaded(aBrowser, aCallback = next, ignoreSubFrames = true) {
aBrowser.addEventListener("load", function onLoad(event) {
if (!ignoreSubFrames || event.target == aBrowser.contentDocument) {
@@ -321,6 +324,11 @@ function whenWindowLoaded(aWindow, aCallback = next) {
});
}, false);
}
+function promiseWindowLoaded(aWindow) {
+ let deferred = Promise.defer();
+ whenWindowLoaded(aWindow, deferred.resolve);
+ return deferred.promise;
+}
function whenTabRestored(aTab, aCallback = next) {
aTab.addEventListener("SSTabRestored", function onRestored(aEvent) {
diff --git a/browser/components/tabview/test/browser.ini b/browser/components/tabview/test/browser.ini
index c224dd04e803..e6a62e33e25c 100644
--- a/browser/components/tabview/test/browser.ini
+++ b/browser/components/tabview/test/browser.ini
@@ -110,6 +110,7 @@ skip-if = true # Bug 922422
[browser_tabview_bug641802.js]
[browser_tabview_bug642793.js]
[browser_tabview_bug643392.js]
+skip-if = os == 'linux'&&debug # bug 989083
[browser_tabview_bug644097.js]
[browser_tabview_bug648882.js]
skip-if = true # Bug 752862
diff --git a/browser/components/tabview/test/browser_tabview_bug628061.js b/browser/components/tabview/test/browser_tabview_bug628061.js
index 6f8b2111436a..2704094fd889 100644
--- a/browser/components/tabview/test/browser_tabview_bug628061.js
+++ b/browser/components/tabview/test/browser_tabview_bug628061.js
@@ -6,13 +6,13 @@ const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionSto
let state = {
windows: [{
tabs: [{
- entries: [{ url: "about:blank" }],
+ entries: [{ url: "about:robots" }],
hidden: true,
- extData: {"tabview-tab": '{"url":"about:blank","groupID":1,"bounds":{"left":20,"top":20,"width":20,"height":20}}'}
+ extData: {"tabview-tab": '{"url":"about:robots","groupID":1,"bounds":{"left":20,"top":20,"width":20,"height":20}}'}
},{
- entries: [{ url: "about:blank" }],
+ entries: [{ url: "about:robots" }],
hidden: false,
- extData: {"tabview-tab": '{"url":"about:blank","groupID":2,"bounds":{"left":20,"top":20,"width":20,"height":20}}'},
+ extData: {"tabview-tab": '{"url":"about:robots","groupID":2,"bounds":{"left":20,"top":20,"width":20,"height":20}}'},
}],
selected: 2,
extData: {
diff --git a/browser/components/tabview/test/browser_tabview_bug697390.js b/browser/components/tabview/test/browser_tabview_bug697390.js
index 7146def31148..5ca1ed8bd5af 100644
--- a/browser/components/tabview/test/browser_tabview_bug697390.js
+++ b/browser/components/tabview/test/browser_tabview_bug697390.js
@@ -4,13 +4,13 @@
let state = {
windows: [{
tabs: [{
- entries: [{ url: "about:blank" }],
+ entries: [{ url: "about:robots" }],
hidden: true,
- extData: {"tabview-tab": '{"url":"about:blank","groupID":1,"bounds":{"left":120,"top":20,"width":20,"height":20}}'}
+ extData: {"tabview-tab": '{"url":"about:robots","groupID":1,"bounds":{"left":120,"top":20,"width":20,"height":20}}'}
},{
- entries: [{ url: "about:blank" }],
+ entries: [{ url: "about:robots" }],
hidden: false,
- extData: {"tabview-tab": '{"url":"about:blank","groupID":2,"bounds":{"left":20,"top":20,"width":20,"height":20}}'},
+ extData: {"tabview-tab": '{"url":"about:robots","groupID":2,"bounds":{"left":20,"top":20,"width":20,"height":20}}'},
}],
selected: 2,
extData: {
diff --git a/browser/components/tabview/test/browser_tabview_bug707466.js b/browser/components/tabview/test/browser_tabview_bug707466.js
index 7b7259f4ecc9..f7c6dfddc660 100644
--- a/browser/components/tabview/test/browser_tabview_bug707466.js
+++ b/browser/components/tabview/test/browser_tabview_bug707466.js
@@ -8,23 +8,23 @@ function test() {
let newState = {
windows: [{
tabs: [{
- entries: [{ url: "about:blank" }],
+ entries: [{ url: "about:robots" }],
hidden: true,
attributes: {},
extData: {
"tabview-tab":
'{"bounds":{"left":21,"top":29,"width":204,"height":153},' +
- '"userSize":null,"url":"about:blank","groupID":1,' +
+ '"userSize":null,"url":"about:robots","groupID":1,' +
'"imageData":null,"title":null}'
}
},{
- entries: [{ url: "about:blank" }],
+ entries: [{ url: "about:robots" }],
hidden: false,
attributes: {},
extData: {
"tabview-tab":
'{"bounds":{"left":315,"top":29,"width":111,"height":84},' +
- '"userSize":null,"url":"about:blank","groupID":2,' +
+ '"userSize":null,"url":"about:robots","groupID":2,' +
'"imageData":null,"title":null}'
},
}],
diff --git a/browser/config/mozconfigs/win32/debug b/browser/config/mozconfigs/win32/debug
index 8427117e9cb5..79f70129b862 100644
--- a/browser/config/mozconfigs/win32/debug
+++ b/browser/config/mozconfigs/win32/debug
@@ -3,7 +3,6 @@
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
ac_add_options --enable-signmar
-ac_add_options --enable-metro
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
diff --git a/browser/config/mozconfigs/win32/l10n-mozconfig b/browser/config/mozconfigs/win32/l10n-mozconfig
index 0f1ba950d8d2..4113d990b887 100644
--- a/browser/config/mozconfigs/win32/l10n-mozconfig
+++ b/browser/config/mozconfigs/win32/l10n-mozconfig
@@ -3,7 +3,6 @@
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
ac_add_options --with-l10n-base=../../l10n
-ac_add_options --enable-metro
ac_add_options --with-windows-version=601
export MOZILLA_OFFICIAL=1
diff --git a/browser/config/mozconfigs/win64/common-opt b/browser/config/mozconfigs/win64/common-opt
index 04fb96a59fd8..9afb969c3e58 100644
--- a/browser/config/mozconfigs/win64/common-opt
+++ b/browser/config/mozconfigs/win64/common-opt
@@ -5,7 +5,6 @@
ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
ac_add_options --enable-update-packaging
ac_add_options --enable-jemalloc
-ac_add_options --enable-metro
if [ -f /c/builds/gapi.data ]; then
_gapi_keyfile=/c/builds/gapi.data
else
diff --git a/browser/config/mozconfigs/win64/debug b/browser/config/mozconfigs/win64/debug
index aff6627fe6ee..0ca4e4fbacd5 100644
--- a/browser/config/mozconfigs/win64/debug
+++ b/browser/config/mozconfigs/win64/debug
@@ -6,7 +6,6 @@ ac_add_options --host=x86_64-pc-mingw32
ac_add_options --enable-debug
ac_add_options --enable-trace-malloc
ac_add_options --enable-signmar
-ac_add_options --enable-metro
# Needed to enable breakpad in application.ini
export MOZILLA_OFFICIAL=1
diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js
index 758410db1982..733471f9b573 100644
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -2175,22 +2175,6 @@ Object.defineProperties(window, {
}
});
-/**
- * Helper method for parsing a resource URI, like
- * `resource://gre/modules/commonjs/sdk/tabs.js`, and pulling out `sdk/tabs.js`
- * if it's in the SDK, or `null` otherwise.
- *
- * @param string url
- * @return string|null
- */
-function getSDKModuleName(url) {
- let match = (url || "").match(/^resource:\/\/gre\/modules\/commonjs\/(.*)/);
- if (match) {
- return match[1];
- }
- return null;
-}
-
/**
* Helper method for debugging.
* @param string
diff --git a/browser/devtools/debugger/debugger-panes.js b/browser/devtools/debugger/debugger-panes.js
index 52334fbeccdf..9085e97c3515 100644
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -11,6 +11,11 @@ const SAMPLE_SIZE = 50; // no of lines
const INDENT_COUNT_THRESHOLD = 5; // percentage
const CHARACTER_LIMIT = 250; // line character limit
+// Maps known URLs to friendly source group names
+const KNOWN_SOURCE_GROUPS = {
+ "Add-on SDK": "resource://gre/modules/commonjs/",
+};
+
/**
* Functions handling the sources UI.
*/
@@ -49,6 +54,15 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
showArrows: true
});
+ // Sort known source groups towards the end of the list
+ this.widget.groupSortPredicate = function(a, b) {
+ if ((a in KNOWN_SOURCE_GROUPS) == (b in KNOWN_SOURCE_GROUPS)) {
+ return a.localeCompare(b);
+ }
+
+ return (a in KNOWN_SOURCE_GROUPS) ? 1 : -1;
+ };
+
this.emptyText = L10N.getStr("noSourcesText");
this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
@@ -132,12 +146,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
let group = SourceUtils.getSourceGroup(url);
let unicodeUrl = NetworkHelper.convertToUnicode(unescape(fullUrl));
- let sdkModuleName = getSDKModuleName(url);
- if (sdkModuleName) {
- label = sdkModuleName;
- group = "Add-on SDK";
- }
-
let contents = document.createElement("label");
contents.className = "plain dbg-source-item";
contents.setAttribute("value", label);
@@ -1560,7 +1568,17 @@ let SourceUtils = {
return cachedLabel;
}
- let sourceLabel = this.trimUrl(aUrl);
+ let sourceLabel = null;
+
+ for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
+ if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
+ sourceLabel = aUrl.substring(KNOWN_SOURCE_GROUPS[name].length);
+ }
+ }
+
+ if (!sourceLabel) {
+ sourceLabel = this.trimUrl(aUrl);
+ }
let unicodeLabel = NetworkHelper.convertToUnicode(unescape(sourceLabel));
this._labelsCache.set(aUrl, unicodeLabel);
return unicodeLabel;
@@ -1583,14 +1601,20 @@ let SourceUtils = {
try {
// Use an nsIURL to parse all the url path parts.
- let url = aUrl.split(" -> ").pop();
- var uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
+ var uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
} catch (e) {
// This doesn't look like a url, or nsIURL can't handle it.
return "";
}
let groupLabel = uri.prePath;
+
+ for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
+ if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
+ groupLabel = name;
+ }
+ }
+
let unicodeLabel = NetworkHelper.convertToUnicode(unescape(groupLabel));
this._groupsCache.set(aUrl, unicodeLabel)
return unicodeLabel;
diff --git a/browser/devtools/debugger/test/browser_dbg_addon-modules.js b/browser/devtools/debugger/test/browser_dbg_addon-modules.js
index 611552dd0103..bac6fb41b97a 100644
--- a/browser/devtools/debugger/test/browser_dbg_addon-modules.js
+++ b/browser/devtools/debugger/test/browser_dbg_addon-modules.js
@@ -5,7 +5,18 @@
const ADDON4_URL = EXAMPLE_URL + "addon4.xpi";
-let gAddon, gClient, gThreadClient, gDebugger, gSources;
+let gAddon, gClient, gThreadClient, gDebugger, gSources, gTitle;
+
+function onMessage(event) {
+ try {
+ let json = JSON.parse(event.data);
+ switch (json.name) {
+ case "toolbox-title":
+ gTitle = json.data.value;
+ break;
+ }
+ } catch(e) { Cu.reportError(e); }
+}
function test() {
Task.spawn(function () {
@@ -18,6 +29,8 @@ function test() {
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
+ window.addEventListener("message", onMessage);
+
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
@@ -43,6 +56,7 @@ function test() {
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
+ window.removeEventListener("message", onMessage);
finish();
});
}
@@ -62,7 +76,7 @@ function testSources(expectSecondModule) {
gThreadClient.getSources(({sources}) => {
ok(sources.length, "retrieved sources");
- sources.forEach(source => {
+ for (let source of sources) {
let url = source.url.split(" -> ").pop();
let { label, group } = gSources.getItemByValue(source.url).attachment;
@@ -81,12 +95,19 @@ function testSources(expectSecondModule) {
} else {
ok(false, "Saw an unexpected source: " + url);
}
- });
+ }
ok(foundAddonModule, "found JS module for the addon in the list");
is(foundAddonModule2, expectSecondModule, "saw the second addon module");
ok(foundAddonBootstrap, "found bootstrap script for the addon in the list");
+ is(gTitle, "Debugger - Test add-on with JS Modules", "Saw the right toolbox title.");
+
+ let groups = gDebugger.document.querySelectorAll(".side-menu-widget-group-title .name");
+ is(groups[0].value, "jar:", "Add-on bootstrap should be the first group");
+ is(groups[1].value, "resource://browser_dbg_addon4", "Add-on code should be the second group");
+ is(groups.length, 2, "Should be only two groups.");
+
deferred.resolve();
});
diff --git a/browser/devtools/debugger/test/browser_dbg_addon-sources.js b/browser/devtools/debugger/test/browser_dbg_addon-sources.js
index 667d45392e4c..3a455a63f26a 100644
--- a/browser/devtools/debugger/test/browser_dbg_addon-sources.js
+++ b/browser/devtools/debugger/test/browser_dbg_addon-sources.js
@@ -6,7 +6,18 @@
const ADDON3_URL = EXAMPLE_URL + "addon3.xpi";
-let gAddon, gClient, gThreadClient, gDebugger, gSources;
+let gAddon, gClient, gThreadClient, gDebugger, gSources, gTitle;
+
+function onMessage(event) {
+ try {
+ let json = JSON.parse(event.data);
+ switch (json.name) {
+ case "toolbox-title":
+ gTitle = json.data.value;
+ break;
+ }
+ } catch(e) { Cu.reportError(e); }
+}
function test() {
Task.spawn(function () {
@@ -19,6 +30,8 @@ function test() {
let iframe = document.createElement("iframe");
document.documentElement.appendChild(iframe);
+ window.addEventListener("message", onMessage);
+
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
@@ -37,6 +50,7 @@ function test() {
yield closeConnection();
yield debuggerPanel._toolbox.destroy();
iframe.remove();
+ window.removeEventListener("message", onMessage);
finish();
});
}
@@ -56,7 +70,7 @@ function testSources() {
gThreadClient.getSources(({sources}) => {
ok(sources.length, "retrieved sources");
- sources.forEach(source => {
+ for (let source of sources) {
let url = source.url.split(" -> ").pop();
info(source.url + "\n\n\n" + url);
let { label, group } = gSources.getItemByValue(source.url).attachment;
@@ -80,7 +94,7 @@ function testSources() {
} else {
ok(false, "Saw an unexpected source: " + url);
}
- });
+ }
ok(foundAddonModule, "found code for the addon in the list");
ok(foundAddonBootstrap, "found bootstrap for the addon in the list");
@@ -88,6 +102,14 @@ function testSources() {
// built-in browser SDK modules
ok(foundSDKModule > 10, "SDK modules are listed");
+ is(gTitle, "Debugger - browser_dbg_addon3", "Saw the right toolbox title.");
+
+ let groups = gDebugger.document.querySelectorAll(".side-menu-widget-group-title .name");
+ is(groups[0].value, "jar:", "Add-on bootstrap should be the first group");
+ is(groups[1].value, "resource://jid1-ami3akps3baaeg-at-jetpack", "Add-on code should be the second group");
+ is(groups[2].value, "Add-on SDK", "Add-on SDK should be the third group");
+ is(groups.length, 3, "Should be only three groups.");
+
deferred.resolve();
});
diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js
index 1258ff9dd753..cffddc5b4468 100644
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -504,9 +504,9 @@ function initDebugger(aTarget, aWindow) {
function initAddonDebugger(aClient, aUrl, aFrame) {
info("Initializing an addon debugger panel.");
- return getAddonActorForUrl(aClient, aUrl).then(({actor}) => {
+ return getAddonActorForUrl(aClient, aUrl).then((addonActor) => {
let targetOptions = {
- form: { addonActor: actor },
+ form: { addonActor: addonActor.actor, title: addonActor.name },
client: aClient,
chrome: true
};
diff --git a/browser/devtools/framework/toolbox-process-window.js b/browser/devtools/framework/toolbox-process-window.js
index dd158846717c..992724fd2758 100644
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -37,7 +37,7 @@ function connect() {
if (addonID) {
gClient.listAddons(({addons}) => {
let addonActor = addons.filter(addon => addon.id === addonID).pop();
- openToolbox({ addonActor: addonActor.actor });
+ openToolbox({ addonActor: addonActor.actor, title: addonActor.name });
});
} else {
gClient.listTabs(openToolbox);
diff --git a/browser/devtools/framework/toolbox.js b/browser/devtools/framework/toolbox.js
index d166f286ae1e..c3f7c8593235 100644
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -878,7 +878,9 @@ Toolbox.prototype = {
*/
focusConsoleInput: function() {
let hud = this.getPanel("webconsole").hud;
- hud.jsterm.inputNode.focus();
+ if (hud && hud.jsterm) {
+ hud.jsterm.inputNode.focus();
+ }
},
/**
@@ -964,7 +966,7 @@ Toolbox.prototype = {
toolName = toolboxStrings("toolbox.defaultTitle");
}
let title = toolboxStrings("toolbox.titleTemplate",
- toolName, this.target.url);
+ toolName, this.target.url || this.target.name);
this._host.setTitle(title);
},
diff --git a/browser/devtools/markupview/markup-view.js b/browser/devtools/markupview/markup-view.js
index 05995e142b86..051d9e976542 100644
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -299,6 +299,9 @@ MarkupView.prototype = {
this.markNodeAsSelected(selection.nodeFront);
}
done();
+ }, (e) => {
+ console.error(e);
+ done();
});
} else {
this.unmarkSelectedNode();
@@ -863,8 +866,10 @@ MarkupView.prototype = {
let parent = node.parentNode();
if (!container.elt.parentNode) {
let parentContainer = this._containers.get(parent);
- parentContainer.childrenDirty = true;
- this._updateChildren(parentContainer, {expand: node});
+ if (parentContainer) {
+ parentContainer.childrenDirty = true;
+ this._updateChildren(parentContainer, {expand: node});
+ }
}
node = parent;
diff --git a/browser/devtools/shared/widgets/SideMenuWidget.jsm b/browser/devtools/shared/widgets/SideMenuWidget.jsm
index 3a082411b3ca..dd6ab4b2ff2b 100644
--- a/browser/devtools/shared/widgets/SideMenuWidget.jsm
+++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm
@@ -66,10 +66,15 @@ this.SideMenuWidget = function SideMenuWidget(aNode, aOptions={}) {
SideMenuWidget.prototype = {
/**
- * Specifies if groups in this container should be sorted alphabetically.
+ * Specifies if groups in this container should be sorted.
*/
sortedGroups: true,
+ /**
+ * The comparator used to sort groups.
+ */
+ groupSortPredicate: function(a, b) a.localeCompare(b),
+
/**
* Specifies that the container viewport should be "stuck" to the
* bottom. That is, the container is automatically scrolled down to
@@ -342,7 +347,7 @@ SideMenuWidget.prototype = {
});
this._groupsByName.set(aName, group);
- group.insertSelfAt(this.sortedGroups ? group.findExpectedIndexForSelf() : -1);
+ group.insertSelfAt(this.sortedGroups ? group.findExpectedIndexForSelf(this.groupSortPredicate) : -1);
return group;
},
@@ -484,14 +489,14 @@ SideMenuGroup.prototype = {
* @return number
* The expected index.
*/
- findExpectedIndexForSelf: function() {
+ findExpectedIndexForSelf: function(sortPredicate) {
let identifier = this.identifier;
let groupsArray = this._orderedGroupElementsArray;
for (let group of groupsArray) {
let name = group.getAttribute("name");
- if (name > identifier && // Insertion sort at its best :)
- !name.contains(identifier)) { // Least significat group should be last.
+ if (sortPredicate(name, identifier) > 0 && // Insertion sort at its best :)
+ !name.contains(identifier)) { // Least significant group should be last.
return groupsArray.indexOf(group);
}
}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js b/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
index 10f7d501c33d..6bdc45ed5416 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
@@ -45,7 +45,7 @@ function testMenuFilterButton(aCategory) {
// Turn all the filters off, if they were on.
let menuItem = firstMenuItem;
while (menuItem != null) {
- if (isChecked(menuItem)) {
+ if (menuItem.hasAttribute("prefKey") && isChecked(menuItem)) {
chooseMenuItem(menuItem);
}
menuItem = menuItem.nextSibling;
@@ -89,10 +89,12 @@ function testMenuFilterButton(aCategory) {
menuItem = firstMenuItem;
while (menuItem) {
let prefKey = menuItem.getAttribute("prefKey");
- ok(!isChecked(menuItem), "menu item " + prefKey + " for category " +
- aCategory + " is no longer checked after clicking the button");
- ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
- "off after clicking the button");
+ if (prefKey) {
+ ok(!isChecked(menuItem), "menu item " + prefKey + " for category " +
+ aCategory + " is no longer checked after clicking the button");
+ ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "off after clicking the button");
+ }
menuItem = menuItem.nextSibling;
}
@@ -127,7 +129,8 @@ function testMenuFilterButton(aCategory) {
while (menuItem) {
// The csslog menu item is already unchecked at this point.
// Make sure it is not selected. See bug 971798.
- if (menuItem.getAttribute("prefKey") != "csslog") {
+ prefKey = menuItem.getAttribute("prefKey");
+ if (prefKey && prefKey != "csslog") {
chooseMenuItem(menuItem);
}
menuItem = menuItem.nextSibling;
@@ -164,7 +167,7 @@ function testIsolateFilterButton(aCategory) {
aCategory + " should not be checked after isolating for " + aCategory);
ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages should be " +
"turned off after isolating for " + aCategory);
- } else {
+ } else if (prefKey) {
ok(isChecked(item), "menu item " + prefKey + " for category " +
aCategory + " is checked after isolating for " + aCategory);
ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
@@ -184,10 +187,12 @@ function testIsolateFilterButton(aCategory) {
menuItems = filterButton.querySelectorAll("menuitem");
Array.forEach(menuItems, (item) => {
let prefKey = item.getAttribute("prefKey");
- ok(!isChecked(item), "menu item " + prefKey + " for category " +
- aCategory + " is unchecked after isolating for " + aCategory);
- ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
- "turned off after isolating for " + aCategory);
+ if (prefKey) {
+ ok(!isChecked(item), "menu item " + prefKey + " for category " +
+ aCategory + " is unchecked after isolating for " + aCategory);
+ ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "turned off after isolating for " + aCategory);
+ }
});
// Turn all the filters on again by clicking the button.
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
index d5f27ea118fc..cb8786e5b28c 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
@@ -53,6 +53,7 @@ function runSelectionTests(aInspector) {
inspector.toolbox.once("picker-started", () => {
info("Picker mode started, now clicking on H1 to select that node");
executeSoon(() => {
+ h1.scrollIntoView();
EventUtils.synthesizeMouseAtCenter(h1, {}, content);
inspector.toolbox.once("picker-stopped", () => {
info("Picker mode stopped, H1 selected, now switching to the console");
diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js
index 4706cf204281..f9e618d8165c 100644
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -401,6 +401,11 @@ WebConsoleFrame.prototype = {
*/
setSaveRequestAndResponseBodies:
function WCF_setSaveRequestAndResponseBodies(aValue) {
+ if (!this.webConsoleClient) {
+ // Don't continue if the webconsole disconnected.
+ return promise.resolve(null);
+ }
+
let deferred = promise.defer();
let newValue = !!aValue;
let toSet = {
diff --git a/browser/experiments/Experiments.jsm b/browser/experiments/Experiments.jsm
index ece3d57a922e..aa96eab90a26 100644
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -104,7 +104,7 @@ function configureLogging() {
gLogger = Log.repository.getLogger("Browser.Experiments");
gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
}
- gLogger.level = gPrefs.get(PREF_LOGGING_LEVEL, 50);
+ gLogger.level = gPrefs.get(PREF_LOGGING_LEVEL, Log.Level.Warn);
let logDumping = gPrefs.get(PREF_LOGGING_DUMP, false);
if (logDumping != gLogDumping) {
@@ -226,6 +226,11 @@ Experiments.Policy.prototype = {
return UpdateChannel.get();
},
+ locale: function () {
+ let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
+ return chrome.getSelectedLocale("global");
+ },
+
/*
* @return Promise<> Resolved with the payload data.
*/
@@ -304,8 +309,10 @@ Experiments.Experiments.prototype = {
AddonManager.addAddonListener(this);
- this._loadTask = Task.spawn(this._loadFromCache.bind(this)).then(
+ this._loadTask = Task.spawn(this._loadFromCache.bind(this));
+ this._loadTask.then(
() => {
+ gLogger.trace("Experiments::_loadTask finished ok");
this._loadTask = null;
this._run();
},
@@ -386,7 +393,7 @@ Experiments.Experiments.prototype = {
},
_telemetryStatusChanged: function () {
- _toggleExperimentsEnabled(gExperimentsEnabled);
+ this._toggleExperimentsEnabled(gExperimentsEnabled);
},
/**
@@ -473,10 +480,16 @@ Experiments.Experiments.prototype = {
},
_run: function() {
+ gLogger.trace("Experiments::_run");
this._checkForShutdown();
if (!this._mainTask) {
- this._mainTask = Task.spawn(this._main.bind(this)).then(
- null,
+ this._mainTask = Task.spawn(this._main.bind(this));
+ this._mainTask.then(
+ () => {
+ gLogger.trace("Experiments::_main finished, scheduling next run");
+ this._mainTask = null;
+ this._scheduleNextRun();
+ },
(e) => {
gLogger.error("Experiments::_main caught error: " + e);
this._mainTask = null;
@@ -488,6 +501,7 @@ Experiments.Experiments.prototype = {
_main: function*() {
do {
+ gLogger.trace("Experiments::_main iteration");
yield this._loadTask;
if (this._refresh) {
yield this._loadManifest();
@@ -500,11 +514,10 @@ Experiments.Experiments.prototype = {
// while we were running, go again right now.
}
while (this._refresh || this._terminateReason);
- this._mainTask = null;
- this._scheduleNextRun();
},
_loadManifest: function*() {
+ gLogger.trace("Experiments::_loadManifest");
let uri = Services.urlFormatter.formatURLPref(PREF_BRANCH + PREF_MANIFEST_URI);
this._checkForShutdown();
@@ -652,6 +665,7 @@ Experiments.Experiments.prototype = {
* Part of the main task to save the cache to disk, called from _main.
*/
_saveToCache: function* () {
+ gLogger.trace("Experiments::_saveToCache");
let path = this._cacheFilePath;
let textData = JSON.stringify({
version: CACHE_VERSION,
@@ -670,6 +684,7 @@ Experiments.Experiments.prototype = {
* Task function, load the cached experiments manifest file from disk.
*/
_loadFromCache: function*() {
+ gLogger.trace("Experiments::_loadFromCache");
let path = this._cacheFilePath;
try {
let result = yield loadJSONAsync(path, { compression: "lz4" });
@@ -706,7 +721,7 @@ Experiments.Experiments.prototype = {
* array in the manifest
*/
_updateExperiments: function (manifestObject) {
- gLogger.trace("Experiments::updateExperiments() - experiments: " + JSON.stringify(manifestObject));
+ gLogger.trace("Experiments::_updateExperiments() - experiments: " + JSON.stringify(manifestObject));
if (manifestObject.version !== MANIFEST_VERSION) {
gLogger.warning("Experiments::updateExperiments() - unsupported version " + manifestObject.version);
@@ -1149,9 +1164,8 @@ Experiments.ExperimentEntry.prototype = {
let app = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
let runtime = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULRuntime);
- let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
- let locale = chrome.getSelectedLocale("global");
+ let locale = this._policy.locale();
let channel = this._policy.updatechannel();
let data = this._manifestData;
@@ -1161,6 +1175,7 @@ Experiments.ExperimentEntry.prototype = {
let startSec = (this.startDate || 0) / 1000;
gLogger.trace("ExperimentEntry::isApplicable() - now=" + now
+ + ", randomValue=" + this._randomValue
+ ", data=" + JSON.stringify(this._manifestData));
// Not applicable if it already ran.
@@ -1183,11 +1198,11 @@ Experiments.ExperimentEntry.prototype = {
{ name: "endTime",
condition: () => now < data.endTime },
{ name: "maxStartTime",
- condition: () => !data.maxStartTime || now <= (data.maxStartTime - minActive) },
+ condition: () => !data.maxStartTime || now <= data.maxStartTime },
{ name: "maxActiveSeconds",
condition: () => !this._startDate || now <= (startSec + maxActive) },
{ name: "appName",
- condition: () => !data.name || data.appName.indexOf(app.name) != -1 },
+ condition: () => !data.appName || data.appName.indexOf(app.name) != -1 },
{ name: "minBuildID",
condition: () => !data.minBuildID || app.platformBuildID >= data.minBuildID },
{ name: "maxBuildID",
@@ -1201,9 +1216,9 @@ Experiments.ExperimentEntry.prototype = {
{ name: "locale",
condition: () => !data.locale || data.locale.indexOf(locale) != -1 },
{ name: "sample",
- condition: () => !data.sample || this._randomValue <= data.sample },
+ condition: () => data.sample === undefined || this._randomValue <= data.sample },
{ name: "version",
- condition: () => !data.version || data.appVersion.indexOf(app.version) != -1 },
+ condition: () => !data.version || data.version.indexOf(app.version) != -1 },
{ name: "minVersion",
condition: () => !data.minVersion || versionCmp.compare(app.version, data.minVersion) >= 0 },
{ name: "maxVersion",
diff --git a/browser/experiments/Experiments.manifest b/browser/experiments/Experiments.manifest
index 15bffb461310..0fb6929fe62a 100644
--- a/browser/experiments/Experiments.manifest
+++ b/browser/experiments/Experiments.manifest
@@ -3,6 +3,6 @@ contract @mozilla.org/browser/experiments-service;1 {f7800463-3b97-47f9-9341-b76
category update-timer ExperimentsService @mozilla.org/browser/experiments-service;1,getService,experiments-update-timer,experiments.manifest.fetchIntervalSeconds,86400
category profile-after-change ExperimentsService @mozilla.org/browser/experiments-service;1
-category healthreport-js-provider-default ExperimentsProvider resource://gre/browser/modules/Experiments/Experiments.jsm
+category healthreport-js-provider-default ExperimentsProvider resource:///modules/experiments/Experiments.jsm
diff --git a/browser/experiments/test/xpcshell/head.js b/browser/experiments/test/xpcshell/head.js
index c178fa2ddab7..6ea7484f0dd8 100644
--- a/browser/experiments/test/xpcshell/head.js
+++ b/browser/experiments/test/xpcshell/head.js
@@ -28,6 +28,8 @@ const EXPERIMENT2_XPI_NAME = "experiment-2.xpi";
const EXPERIMENT3_ID = "test-experiment-3@tests.mozilla.org";
const EXPERIMENT4_ID = "test-experiment-4@tests.mozilla.org";
+const DEFAULT_BUILDID = "2014060601";
+
const FAKE_EXPERIMENTS_1 = [
{
id: "id1",
@@ -169,7 +171,7 @@ function createAppInfo(options) {
let platformVersion = options.platformVersion || "1.0";
let date = options.date || new Date();
- let buildID = "" + date.getYear() + date.getMonth() + date.getDate() + "01";
+ let buildID = options.buildID || DEFAULT_BUILDID;
gAppInfo = {
// nsIXULAppInfo
diff --git a/browser/experiments/test/xpcshell/test_activate.js b/browser/experiments/test/xpcshell/test_activate.js
index 0c2b77266981..ba1ce3fc0ded 100644
--- a/browser/experiments/test/xpcshell/test_activate.js
+++ b/browser/experiments/test/xpcshell/test_activate.js
@@ -63,6 +63,7 @@ add_task(function* test_setup() {
gReporter = yield getReporter("json_payload_simple");
yield gReporter.collectMeasurements();
let payload = yield gReporter.getJSONPayload(true);
+ do_register_cleanup(() => gReporter._shutdown());
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
@@ -131,8 +132,3 @@ add_task(function* test_startStop() {
Assert.equal(maybeStop, true, "Experiment should have been stopped.");
Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
});
-
-add_task(function* shutdown() {
- yield gReporter._shutdown();
- yield removeCacheFile();
-});
diff --git a/browser/experiments/test/xpcshell/test_api.js b/browser/experiments/test/xpcshell/test_api.js
index 6a1a6c34b795..356c0ddb52f0 100644
--- a/browser/experiments/test/xpcshell/test_api.js
+++ b/browser/experiments/test/xpcshell/test_api.js
@@ -75,6 +75,7 @@ add_task(function* test_setup() {
gReporter = yield getReporter("json_payload_simple");
yield gReporter.collectMeasurements();
let payload = yield gReporter.getJSONPayload(true);
+ do_register_cleanup(() => gReporter._shutdown());
gPolicy = new Experiments.Policy();
patchPolicy(gPolicy, {
@@ -1278,9 +1279,3 @@ add_task(function* test_unexpectedUninstall() {
yield experiments.uninit();
yield removeCacheFile();
});
-
-
-add_task(function* shutdown() {
- yield gReporter._shutdown();
- yield removeCacheFile();
-});
diff --git a/browser/experiments/test/xpcshell/test_cache.js b/browser/experiments/test/xpcshell/test_cache.js
index de011b57f2c8..fa157b98e5f3 100644
--- a/browser/experiments/test/xpcshell/test_cache.js
+++ b/browser/experiments/test/xpcshell/test_cache.js
@@ -76,6 +76,7 @@ add_task(function* test_setup() {
gReporter = yield getReporter("json_payload_simple");
yield gReporter.collectMeasurements();
let payload = yield gReporter.getJSONPayload(true);
+ do_register_cleanup(() => gReporter._shutdown());
gPolicy = new Experiments.Policy();
patchPolicy(gPolicy, {
@@ -264,8 +265,3 @@ add_task(function* test_cache() {
yield experiments.uninit();
yield removeCacheFile();
});
-
-add_task(function* shutdown() {
- yield gReporter._shutdown();
- yield removeCacheFile();
-});
diff --git a/browser/experiments/test/xpcshell/test_conditions.js b/browser/experiments/test/xpcshell/test_conditions.js
index 4ca1d315ad79..592f744be36a 100644
--- a/browser/experiments/test/xpcshell/test_conditions.js
+++ b/browser/experiments/test/xpcshell/test_conditions.js
@@ -61,10 +61,13 @@ add_task(function* test_setup() {
gReporter = yield getReporter("json_payload_simple");
yield gReporter.collectMeasurements();
let payload = yield gReporter.getJSONPayload(true);
+ do_register_cleanup(() => gReporter._shutdown());
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
+ locale: () => "en-US",
healthReportPayload: () => Promise.resolve(payload),
+ random: () => 0.5,
});
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
@@ -108,10 +111,81 @@ const sanityFilter = function filter(c) {
add_task(function* test_simpleFields() {
let testData = [
// "expected applicable?", failure reason or null, manifest data
+
+ // misc. environment
+
+ [false, ["appName"], {appName: []}],
+ [false, ["appName"], {appName: ["foo", gAppInfo.name + "-invalid"]}],
+ [true, null, {appName: ["not-an-app-name", gAppInfo.name]}],
+
+ [false, ["os"], {os: []}],
[false, ["os"], {os: ["42", "abcdef"]}],
- [true, null, {os: [gAppInfo.OS, "plan9"]}],
+ [true, null, {os: [gAppInfo.OS, "plan9"]}],
+
+ [false, ["channel"], {channel: []}],
+ [false, ["channel"], {channel: ["foo", gPolicy.updatechannel() + "-invalid"]}],
+ [true, null, {channel: ["not-a-channel", gPolicy.updatechannel()]}],
+
+ [false, ["locale"], {locale: []}],
+ [false, ["locale"], {locale: ["foo", gPolicy.locale + "-invalid"]}],
+ [true, null, {locale: ["not-a-locale", gPolicy.locale()]}],
+
+ // version
+
+ [false, ["version"], {version: []}],
+ [false, ["version"], {version: ["-1", gAppInfo.version + "-invalid", "asdf", "0,4", "99.99", "0.1.1.1"]}],
+ [true, null, {version: ["99999999.999", "-1", gAppInfo.version]}],
+
+ [false, ["minVersion"], {minVersion: "1.0.1"}],
+ [true, null, {minVersion: "1.0b1"}],
+ [true, null, {minVersion: "1.0"}],
+ [true, null, {minVersion: "0.9"}],
+
+ [false, ["maxVersion"], {maxVersion: "0.1"}],
+ [false, ["maxVersion"], {maxVersion: "0.9.9"}],
+ [false, ["maxVersion"], {maxVersion: "1.0b1"}],
+ [true, ["maxVersion"], {maxVersion: "1.0"}],
+ [true, ["maxVersion"], {maxVersion: "1.7pre"}],
+
+ // build id
+
+ [false, ["buildIDs"], {buildIDs: []}],
[false, ["buildIDs"], {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID + "-invalid"]}],
- [true, null, {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID]}],
+ [true, null, {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID]}],
+
+ [true, null, {minBuildID: "2014060501"}],
+ [true, null, {minBuildID: "2014060601"}],
+ [false, ["minBuildID"], {minBuildID: "2014060701"}],
+
+ [false, ["maxBuildID"], {maxBuildID: "2014010101"}],
+ [true, null, {maxBuildID: "2014060601"}],
+ [true, null, {maxBuildID: "2014060901"}],
+
+ // sample
+
+ [false, ["sample"], {sample: -1 }],
+ [false, ["sample"], {sample: 0.0}],
+ [false, ["sample"], {sample: 0.1}],
+ [true, null, {sample: 0.5}],
+ [true, null, {sample: 0.6}],
+ [true, null, {sample: 1.0}],
+ [true, null, {sample: 0.5}],
+
+ // experiment control
+
+ [false, ["disabled"], {disabled: true}],
+ [true, null, {disabled: false}],
+
+ [false, ["frozen"], {frozen: true}],
+ [true, null, {frozen: false}],
+
+ [false, null, {frozen: true, disabled: true}],
+ [false, null, {frozen: true, disabled: false}],
+ [false, null, {frozen: false, disabled: true}],
+ [true, null, {frozen: false, disabled: false}],
+
+ // jsfilter
+
[true, null, {jsfilter: "function filter(c) { return true; }"}],
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { return false; }"}],
[true, null, {jsfilter: "function filter(c) { return 123; }"}], // truthy
@@ -150,18 +224,73 @@ add_task(function* test_simpleFields() {
});
add_task(function* test_times() {
- let baseDate = new Date(2014, 5, 6, 12);
- let baseTimeSec = baseDate.getTime() / 1000;
+ let now = new Date(2014, 5, 6, 12);
+ let nowSec = now.getTime() / 1000;
let testData = [
// "expected applicable?", rejection reason or null, fake now date, manifest data
- [false, "maxStartTime", baseDate,
- {maxStartTime: baseTimeSec - 10 * SEC_IN_ONE_DAY}],
- [false, "endTime", baseDate,
- {startTime: baseTimeSec - 10 * SEC_IN_ONE_DAY,
- endTime: baseTimeSec - 5 * SEC_IN_ONE_DAY}],
- [true, null, baseDate,
- {startTime: baseTimeSec - 5 * SEC_IN_ONE_DAY,
- endTime: baseTimeSec + 10 * SEC_IN_ONE_DAY}],
+
+ // start time
+
+ [true, null, now,
+ {startTime: nowSec - 5 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {startTime: nowSec ,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [false, "startTime", now,
+ {startTime: nowSec + 5 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+
+ // end time
+
+ [false, "endTime", now,
+ {startTime: nowSec - 5 * SEC_IN_ONE_DAY,
+ endTime: nowSec - 10 * SEC_IN_ONE_DAY}],
+ [false, "endTime", now,
+ {startTime: nowSec - 5 * SEC_IN_ONE_DAY,
+ endTime: nowSec - 5 * SEC_IN_ONE_DAY}],
+
+ // max start time
+
+ [false, "maxStartTime", now,
+ {maxStartTime: nowSec - 15 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [false, "maxStartTime", now,
+ {maxStartTime: nowSec - 1 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [false, "maxStartTime", now,
+ {maxStartTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxStartTime: nowSec,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxStartTime: nowSec + 1 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+
+ // max active seconds
+
+ [true, null, now,
+ {maxActiveSeconds: 5 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxActiveSeconds: 15 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxActiveSeconds: 20 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
];
for (let i=0; i gReporter._shutdown());
gPolicy = new Experiments.Policy();
let dummyTimer = { cancel: () => {}, clear: () => {} };
@@ -353,8 +354,3 @@ add_task(function* test_telemetryBasics() {
yield experiments.uninit();
yield removeCacheFile();
});
-
-add_task(function* shutdown() {
- yield gReporter._shutdown();
- yield removeCacheFile();
-});
diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css
index d7f329f787c8..61c751b0e506 100644
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -16,7 +16,7 @@
%define forwardTransitionLength 150ms
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
-%define conditionalForwardWithUrlbarWidth 40
+%define conditionalForwardWithUrlbarWidth 30
#menubar-items {
-moz-box-orient: vertical; /* for flex hack */
@@ -676,23 +676,98 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-moz-margin-start: -4px;
+ margin-top: 3px;
+ margin-bottom: 3px;
}
-#forward-button[disabled] {
- transform: scale(0);
- opacity: 0;
- pointer-events: none;
+#back-button {
+ padding-top: 3px;
+ padding-bottom: 3px;
+ -moz-padding-start: 5px;
+ -moz-padding-end: 0;
+ position: relative;
+ z-index: 1;
+ border-radius: 0 10000px 10000px 0;
}
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
- transition: @forwardTransitionLength@ ease-out;
+#back-button:-moz-locale-dir(rtl) {
+ border-radius: 10000px 0 0 10000px;
+}
+
+#back-button > menupopup {
+ margin-top: -1px;
+}
+
+#back-button > .toolbarbutton-icon {
+ border-radius: 10000px;
+ background-clip: padding-box;
+ padding: 6px;
+ border: none;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(210,54%,20%,.25),
+ 0 1px 0 hsla(210,54%,20%,.35);
+ background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
+ transition-property: background-color, box-shadow;
+ transition-duration: 250ms;
+}
+
+#back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
+ background-color: hsla(210,48%,96%,.75);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(210,54%,20%,.3),
+ 0 1px 0 hsla(210,54%,20%,.4),
+ 0 0 4px hsla(210,54%,20%,.2);
+}
+
+#back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
+#back-button[open="true"] > .toolbarbutton-icon {
+ background-color: hsla(210,54%,20%,.15);
+ box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
+ 0 0 1px hsla(210,54%,20%,.2) inset,
+ 0 0 0 1px hsla(210,54%,20%,.4),
+ 0 1px 0 hsla(210,54%,20%,.2);
+ transition: none;
+}
+
+#main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
+ box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
+ 0 1px 0 hsla(210,54%,20%,.65) !important;
+ transition: none;
}
#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
-#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+#forward-button:-moz-locale-dir(rtl) {
transform: scaleX(-1);
}
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+ opacity: 0;
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+ transition: opacity @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
+ visibility: hidden;
+}
+
+#forward-button {
+ padding: 0;
+}
+
+#forward-button > .toolbarbutton-icon {
+ background-clip: padding-box;
+ clip-path: url("chrome://browser/content/browser.xul#keyhole-forward-clip-path");
+ margin-left: -6px;
+ border-left-style: none;
+ border-radius: 0;
+ padding: 2px 3px 2px 9px;
+ border: 1px solid #9a9a9a;
+}
+
/* tabview menu item */
#menu_tabview {
@@ -801,9 +876,21 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
/* Location bar */
+#urlbar,
+.searchbar-textbox {
+ -moz-appearance: none;
+ padding: 1px;
+ border: 1px solid ThreeDShadow;
+ border-radius: 2px;
+}
+
+#urlbar[focused],
+.searchbar-textbox[focused] {
+ border-color: Highlight;
+}
+
#urlbar {
- -moz-appearance: textfield;
- padding: 0;
+ background-color: -moz-field;
}
.urlbar-textbox-container {
@@ -825,26 +912,59 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
- -moz-padding-start: @conditionalForwardWithUrlbarWidth@px;
+ padding-left: @conditionalForwardWithUrlbarWidth@px;
-moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
position: relative;
pointer-events: none;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
+ -moz-border-start: none;
+ margin-left: 0;
pointer-events: all;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
- transition: margin-left @forwardTransitionLength@ ease-out,
- margin-right @forwardTransitionLength@ ease-out;
+ transition: margin-left @forwardTransitionLength@ ease-out;
}
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
+ /* Work with margin-top to align the clip-path correctly. */
+ margin-top: 5px;
+ clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
+ margin-top: -4px;
margin-left: -@conditionalForwardWithUrlbarWidth@px;
}
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
- margin-right: -@conditionalForwardWithUrlbarWidth@px;
+
+@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
+ /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+ transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
+@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
+ /* when switching tabs, or when not hovered anymore, trigger a new transition
+ * to hide the forward button immediately */
+ margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
+ /* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
+ transform: scaleX(-1);
}
#urlbar-icons {
@@ -884,41 +1004,66 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
min-width: calc(54px + 11ch);
}
-%include ../shared/identity-block.inc.css
+/* identity box */
-#page-proxy-favicon {
- margin-top: 2px;
- margin-bottom: 2px;
- -moz-margin-start: 4px;
- -moz-margin-end: 3px;
- -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-#identity-box:hover > #page-proxy-favicon {
- -moz-image-region: rect(0, 32px, 16px, 16px);
-}
-
-#identity-box:hover:active > #page-proxy-favicon,
-#identity-box[open=true] > #page-proxy-favicon {
- -moz-image-region: rect(0, 48px, 16px, 32px);
-}
-
-/* Identity indicator */
#identity-box {
padding: 1px;
- margin: -1px;
- -moz-margin-end: 0;
font-size: .9em;
}
#identity-box:-moz-locale-dir(ltr) {
- border-top-left-radius: 2.5px;
- border-bottom-left-radius: 2.5px;
+ border-top-left-radius: 1.5px;
+ border-bottom-left-radius: 1.5px;
}
#identity-box:-moz-locale-dir(rtl) {
- border-top-right-radius: 2.5px;
- border-bottom-right-radius: 2.5px;
+ border-top-right-radius: 1.5px;
+ border-bottom-right-radius: 1.5px;
+}
+
+#notification-popup-box:not([hidden]) + #identity-box {
+ -moz-padding-start: 10px;
+ border-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
+ border-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+ padding-left: 5px;
+ transition: padding-left;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+ padding-right: 5px;
+ transition: padding-right;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
+ /* forward button hiding is delayed when hovered */
+ transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
+@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+ /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
+ padding-left: 5.01px;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+ /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
+ padding-right: 5.01px;
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
+#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
+ -moz-margin-end: 4px;
+}
+
+#identity-box.verifiedIdentity:not(:-moz-lwtheme) {
+ background-color: #fff;
}
#identity-box:-moz-focusring {
@@ -931,10 +1076,27 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
-moz-padding-end: 5px;
}
-#urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
-#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
- background-color: #fff;
- -moz-margin-end: 4px;
+%include ../shared/identity-block.inc.css
+
+#page-proxy-favicon {
+ margin-top: 1px;
+ margin-bottom: 1px;
+ -moz-margin-start: 3px;
+ -moz-margin-end: 2px;
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box > #page-proxy-favicon {
+ -moz-margin-end: 1px;
+}
+
+#identity-box:hover > #page-proxy-favicon {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#identity-box:hover:active > #page-proxy-favicon,
+#identity-box[open=true] > #page-proxy-favicon {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
}
/* Identity popup icons */
diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn
index 53583cf31e94..100012e93ab0 100644
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -105,6 +105,7 @@ browser.jar:
skin/classic/browser/feeds/audioFeedIcon16.png (feeds/feedIcon16.png)
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+ skin/classic/browser/fonts/ClearSans-Regular.ttf (../shared/ClearSans-Regular.ttf)
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/controls.png (newtab/controls.png)
skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
@@ -134,6 +135,12 @@ browser.jar:
#endif
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
+ skin/classic/browser/preferences/in-content/check.png (preferences/in-content/check.png)
+ skin/classic/browser/preferences/in-content/icons.png (preferences/in-content/icons.png)
+ skin/classic/browser/preferences/in-content/header.png (preferences/in-content/header.png)
+ skin/classic/browser/preferences/in-content/dropdown.png (preferences/in-content/dropdown.png)
+ skin/classic/browser/preferences/in-content/sorter.png (preferences/in-content/sorter.png)
+ skin/classic/browser/preferences/in-content/dropdown-disabled.png (preferences/in-content/dropdown-disabled.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)
diff --git a/browser/themes/linux/preferences/in-content/check.png b/browser/themes/linux/preferences/in-content/check.png
new file mode 100644
index 000000000000..a9438df08b46
Binary files /dev/null and b/browser/themes/linux/preferences/in-content/check.png differ
diff --git a/browser/themes/linux/preferences/in-content/dropdown-disabled.png b/browser/themes/linux/preferences/in-content/dropdown-disabled.png
new file mode 100644
index 000000000000..9e0a4718265c
Binary files /dev/null and b/browser/themes/linux/preferences/in-content/dropdown-disabled.png differ
diff --git a/browser/themes/linux/preferences/in-content/dropdown.png b/browser/themes/linux/preferences/in-content/dropdown.png
new file mode 100644
index 000000000000..9843b82f473f
Binary files /dev/null and b/browser/themes/linux/preferences/in-content/dropdown.png differ
diff --git a/browser/themes/linux/preferences/in-content/header.png b/browser/themes/linux/preferences/in-content/header.png
new file mode 100644
index 000000000000..de33b7fb4f6d
Binary files /dev/null and b/browser/themes/linux/preferences/in-content/header.png differ
diff --git a/browser/themes/linux/preferences/in-content/icons.png b/browser/themes/linux/preferences/in-content/icons.png
new file mode 100644
index 000000000000..cf6a16a51989
Binary files /dev/null and b/browser/themes/linux/preferences/in-content/icons.png differ
diff --git a/browser/themes/linux/preferences/in-content/preferences.css b/browser/themes/linux/preferences/in-content/preferences.css
index eef87516d661..c79fadb6db6e 100644
--- a/browser/themes/linux/preferences/in-content/preferences.css
+++ b/browser/themes/linux/preferences/in-content/preferences.css
@@ -2,174 +2,89 @@
- 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/. */
-@import url("chrome://global/skin/inContentUI.css");
+%include ../../../shared/in-content/preferences.css
-@namespace html "http://www.w3.org/1999/xhtml";
-
-#header {
- margin-bottom: 18px;
-}
-
-caption {
- font-size: 1.667rem;
-}
-
-.main-content {
- max-width: 800px;
-}
-
-prefpane > .content-box {
- overflow: auto;
-}
-
-/* Category List */
-
-#categories {
+button > .button-box,
+menulist > .menulist-label-box {
-moz-appearance: none;
- border: none;
- -moz-margin-end: -1px;
- background-color: transparent;
- position: relative;
- margin-top: 41px;
}
-.category {
+button[type="menu"] > .button-box > .button-menu-dropmarker {
+ -moz-appearance: none !important;
+}
+
+menulist:not([editable="true"]) > .menulist-dropmarker {
+ display: -moz-box;
+ margin-top: 6px;
+ margin-bottom: 6px;
+}
+
+checkbox {
+ -moz-binding: url("chrome://global/content/bindings/checkbox.xml#checkbox");
+}
+
+.checkbox-check {
+ max-height: 23px;
+}
+
+checkbox:hover::before,
+checkbox[checked]::before {
+ max-height: 10px;
+ margin-top: 7px;
+ margin-bottom: 6px;
+ -moz-margin-end: -19px;
+ -moz-margin-start: 4px;
+ background-repeat: no-repeat;
+}
+
+radio {
+ -moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
+ margin: 7px 0;
+}
+
+.radio-check {
+ max-height: 23px;
+}
+
+.radio-label-box {
-moz-appearance: none;
- border-width: 1px;
- -moz-border-end-width: 0;
- border-style: solid;
- border-color: transparent;
- padding: 9px 4px 10px;
- -moz-padding-end: 8px;
- -moz-box-align: center;
- overflow: hidden;
- min-height: 0;
- color: WindowText;
- height: 52px;
}
-.category:-moz-locale-dir(ltr) {
- border-top-left-radius: 5px;
- border-bottom-left-radius: 5px;
-}
-
-.category:-moz-locale-dir(rtl) {
- border-top-right-radius: 5px;
- border-bottom-right-radius: 5px;
-}
-
-.category[selected] {
- background-color: -moz-Field;
- color: -moz-FieldText;
- border-color: ThreeDShadow;
-}
-
-.category-name {
- font-size: 1.5rem;
- -moz-padding-end: 24px;
-}
-
-/* Maximize the size of the viewport when the window is small */
-@media (max-width: 800px) {
- .category-name {
- display: none;
- }
-}
-
-.category-icon {
- width: 32px;
- height: 32px;
- margin: 0 6px;
+radio:hover::before,
+radio[selected]::before {
+ max-height: 11px;
+ margin-top: 6px;
+ margin-bottom: 6px;
+ -moz-margin-end: -17px;
-moz-margin-start: 6px;
- -moz-margin-end: 5px;
- list-style-image: url("chrome://browser/skin/preferences/Options.png");
}
-#category-general > .category-icon {
- -moz-image-region: rect(0, 32px, 32px, 0);
-}
-
-#category-content > .category-icon {
- -moz-image-region: rect(0, 96px, 32px, 64px)
-}
-
-#category-application > .category-icon {
- -moz-image-region: rect(0, 128px, 32px, 96px)
-}
-
-#category-privacy > .category-icon {
- -moz-image-region: rect(0, 160px, 32px, 128px)
-}
-
-#category-security > .category-icon {
- -moz-image-region: rect(0, 192px, 32px, 160px)
-}
-
-#category-advanced > .category-icon {
- -moz-image-region: rect(0, 224px, 32px, 192px)
-}
-
-%ifdef MOZ_SERVICES_SYNC
-#category-sync > .category-icon {
- list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
-}
-%endif
-
-/* Applications Pane Styles */
-
-#applications-content {
- padding: 15px;
-}
-
-#handlersView {
+.numberbox-input-box {
-moz-appearance: none;
- border: 1px solid ThreeDShadow;
- overflow-y: auto;
+ border-width: 0;
}
-/* XXX This style is for bug 740213 and should be removed once that
- bug has a solution. */
-description > html|a {
- cursor: pointer;
+spinbuttons {
+ -moz-appearance: none;
}
-/* XXX Styles Below can be removed once bug 660726 lands */
-.nav-button {
- min-width: 0;
+.actionsMenu {
+ font-family: "Clear Sans", sans-serif;
+ font-size: 1.25rem;
+ line-height: 22px;
}
-#back-btn:-moz-locale-dir(ltr) {
- list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar");
+.actionsMenu > .menulist-label-box > .menulist-icon {
+ margin-top: 1px;
+ -moz-margin-start: 1px;
+ -moz-margin-end: 6px;
}
-#forward-btn:-moz-locale-dir(ltr) {
- list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar");
+.actionsMenu > .menulist-label-box > .menulist-label {
+ margin-top: 2px !important;
}
-#back-btn:-moz-locale-dir(rtl) {
- list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=toolbar");
-}
-
-#forward-btn:-moz-locale-dir(rtl) {
- list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar");
-}
-
-#back-btn[disabled="true"]:-moz-locale-dir(ltr) {
- list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar&state=disabled");
-}
-
-#forward-btn[disabled="true"]:-moz-locale-dir(ltr) {
- list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar&state=disabled");
-}
-
-#back-btn[disabled="true"]:-moz-locale-dir(rtl) {
- list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=toolbar&state=disabled");
-}
-
-#forward-btn[disabled="true"]:-moz-locale-dir(rtl) {
- list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar&state=disabled");
-}
-
-.header-button .toolbarbutton-text {
- display: none;
+menulist.actionsMenu > .menulist-dropmarker {
+ margin-top: 11px;
+ margin-bottom: 11px;
}
diff --git a/browser/themes/linux/preferences/in-content/sorter.png b/browser/themes/linux/preferences/in-content/sorter.png
new file mode 100644
index 000000000000..3e5661fe169b
Binary files /dev/null and b/browser/themes/linux/preferences/in-content/sorter.png differ
diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css
index 835b0351642c..e3f12cb7f1f9 100644
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -502,7 +502,7 @@ toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open],[bu
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 {
+toolbar .toolbarbutton-1[type="menu-button"][open]:not([disabled]) > .toolbarbutton-menubutton-dropmarker {
background: hsla(0,0%,0%,.02) linear-gradient(hsla(0,0%,0%,.12), hsla(0,0%,0%,0)) border-box;
border-color: hsla(0,0%,0%,.3);
box-shadow: 0 1px 0 hsla(0,0%,100%,.5),
diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn
index 1aed85aae480..6f6dd1a49d01 100644
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -168,6 +168,7 @@ browser.jar:
skin/classic/browser/feeds/videoFeedIcon16.png (feeds/feedIcon16.png)
skin/classic/browser/feeds/audioFeedIcon.png (feeds/feedIcon.png)
skin/classic/browser/feeds/audioFeedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/fonts/ClearSans-Regular.ttf (../shared/ClearSans-Regular.ttf)
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/controls.png (newtab/controls.png)
skin/classic/browser/newtab/controls@2x.png (newtab/controls@2x.png)
@@ -223,6 +224,14 @@ browser.jar:
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
+ skin/classic/browser/preferences/in-content/check.png (preferences/in-content/check.png)
+ skin/classic/browser/preferences/in-content/check@2x.png (preferences/in-content/check@2x.png)
+ skin/classic/browser/preferences/in-content/icons.png (preferences/in-content/icons.png)
+ skin/classic/browser/preferences/in-content/icons@2x.png (preferences/in-content/icons@2x.png)
+ skin/classic/browser/preferences/in-content/header.png (preferences/in-content/icons@2x.png)
+ skin/classic/browser/preferences/in-content/sorter.png (preferences/in-content/sorter.png)
+ skin/classic/browser/preferences/in-content/dropdown.png (preferences/in-content/dropdown.png)
+ skin/classic/browser/preferences/in-content/dropdown-disabled.png (preferences/in-content/dropdown-disabled.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)
diff --git a/browser/themes/osx/preferences/in-content/check.png b/browser/themes/osx/preferences/in-content/check.png
new file mode 100644
index 000000000000..a9438df08b46
Binary files /dev/null and b/browser/themes/osx/preferences/in-content/check.png differ
diff --git a/browser/themes/osx/preferences/in-content/check@2x.png b/browser/themes/osx/preferences/in-content/check@2x.png
new file mode 100644
index 000000000000..6ba07a2a8ffb
Binary files /dev/null and b/browser/themes/osx/preferences/in-content/check@2x.png differ
diff --git a/browser/themes/osx/preferences/in-content/dropdown-disabled.png b/browser/themes/osx/preferences/in-content/dropdown-disabled.png
new file mode 100644
index 000000000000..9e0a4718265c
Binary files /dev/null and b/browser/themes/osx/preferences/in-content/dropdown-disabled.png differ
diff --git a/browser/themes/osx/preferences/in-content/dropdown.png b/browser/themes/osx/preferences/in-content/dropdown.png
new file mode 100644
index 000000000000..9843b82f473f
Binary files /dev/null and b/browser/themes/osx/preferences/in-content/dropdown.png differ
diff --git a/browser/themes/osx/preferences/in-content/icons.png b/browser/themes/osx/preferences/in-content/icons.png
new file mode 100644
index 000000000000..cf6a16a51989
Binary files /dev/null and b/browser/themes/osx/preferences/in-content/icons.png differ
diff --git a/browser/themes/osx/preferences/in-content/icons@2x.png b/browser/themes/osx/preferences/in-content/icons@2x.png
new file mode 100644
index 000000000000..e85db40ad874
Binary files /dev/null and b/browser/themes/osx/preferences/in-content/icons@2x.png differ
diff --git a/browser/themes/osx/preferences/in-content/preferences.css b/browser/themes/osx/preferences/in-content/preferences.css
index 6ad6a35e2338..997e482f7625 100644
--- a/browser/themes/osx/preferences/in-content/preferences.css
+++ b/browser/themes/osx/preferences/in-content/preferences.css
@@ -2,182 +2,134 @@
- 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/. */
-%include ../../shared.inc
+%include ../../../shared/in-content/preferences.css
-@import url("chrome://global/skin/inContentUI.css");
-
-@namespace html "http://www.w3.org/1999/xhtml";
-
-#header {
- margin-bottom: 18px;
+menulist:not([editable="true"]) > .menulist-dropmarker {
+ display: -moz-box;
+ margin-top: 1px;
+ margin-bottom: 1px;
}
-caption {
- font-size: 1.667rem;
+checkbox:hover::before,
+checkbox[checked]::before {
+ margin-bottom: -2px;
+ -moz-margin-end: -20px;
+ -moz-margin-start: 5px;
}
-.main-content {
- max-width: 800px;
+radio:hover::before,
+radio[selected]::before {
+ -moz-margin-end: -18px;
+ -moz-margin-start: 7px;
}
-prefpane > .content-box {
- overflow: auto;
-}
-
-/* Category List */
-
-#categories {
+.numberbox-input-box {
-moz-appearance: none;
- border: none;
- -moz-margin-end: -1px;
- background-color: transparent;
- position: relative;
- margin-top: 31px;
+ border-width: 0;
}
-.category {
+spinbuttons {
-moz-appearance: none;
- color: #252F3B;
- border-width: 1px;
- border-style: solid;
- border-color: transparent;
- padding: 10px 4px;
- -moz-padding-end: 8px;
- -moz-box-align: center;
- overflow: hidden;
- min-height: 0;
- height: 52px;
}
-.category:-moz-locale-dir(ltr) {
- border-top-left-radius: 5px;
- border-bottom-left-radius: 5px;
+.spinbuttons-up {
+ margin-top: 0 !important;
+ border-radius: 4px 4px 0 0;
}
-.category:-moz-locale-dir(rtl) {
- border-top-right-radius: 5px;
- border-bottom-right-radius: 5px;
+.spinbuttons-down {
+ margin-bottom: 0 !important;
+ border-radius: 0 0 4px 4px;
}
-.category[selected] {
- background-color: rgba(255, 255, 255, 0.35);
- color: -moz-dialogtext;
- border-color: rgba(50, 65, 92, 0.4);
- -moz-border-end-color: #C9CFD7;
+.spinbuttons-button > .button-box {
+ -moz-padding-start: 2px !important;
+ -moz-padding-end: 3px !important;
}
-.category-name {
- font-size: 1.5rem;
- -moz-padding-end: 24px;
-}
-
-/* Maximize the size of the viewport when the window is small */
-@media (max-width: 800px) {
- .category-name {
- display: none;
- }
-}
-
-.category-icon {
- width: 32px;
- height: 32px;
- -moz-margin-start: 6px;
- list-style-image: url("chrome://browser/skin/preferences/Options.png");
-}
-
-#category-general > .category-icon {
- -moz-image-region: rect(0, 32px, 32px, 0);
-}
-
-#category-content > .category-icon {
- -moz-image-region: rect(0, 96px, 32px, 64px)
-}
-
-#category-application > .category-icon {
- -moz-image-region: rect(0, 128px, 32px, 96px)
-}
-
-#category-privacy > .category-icon {
- -moz-image-region: rect(0, 160px, 32px, 128px)
-}
-
-#category-security > .category-icon {
- -moz-image-region: rect(0, 192px, 32px, 160px)
-}
-
-#category-advanced > .category-icon {
- -moz-image-region: rect(0, 224px, 32px, 192px)
-}
-
-%ifdef MOZ_SERVICES_SYNC
-#category-sync > .category-icon {
- list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
-}
-%endif
-
-/* Applications Pane Styles */
-
-#applications-content {
- padding: 15px;
-}
-
-#handlersView {
- -moz-appearance: none;
- border: 1px solid rgba(60,73,97,0.5);
- overflow-y: auto;
-}
-
-/* XXX This style is for bug 740213 and should be removed once that
- bug has a solution. */
-description > html|a {
- cursor: pointer;
-}
-
-/* XXX Styles Below can be removed once bug 660726 lands */
-.nav-button {
- list-style-image: url(chrome://mozapps/skin/extensions/navigation.png);
-}
-
-#back-btn:-moz-locale-dir(ltr),
-#forward-btn:-moz-locale-dir(rtl) {
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- border-right: none;
- -moz-image-region: rect(0, 20px, 20px, 0);
- padding-right: 3px;
-}
-
-#back-btn:-moz-locale-dir(rtl),
-#forward-btn:-moz-locale-dir(ltr) {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- -moz-image-region: rect(0, 40px, 20px, 20px);
- padding-left: 3px;
-}
-
-.header-button {
- -moz-appearance: none;
- padding: 0 4px;
- margin: 0;
- height: 22px;
- border: 1px solid rgba(60,73,97,0.5);
- border-radius: @toolbarbuttonCornerRadius@;
- box-shadow: inset 0 1px rgba(255,255,255,0.25), 0 1px rgba(255,255,255,0.25);
- background: linear-gradient(rgba(255,255,255,0.45), rgba(255,255,255,0));
- background-clip: padding-box;
-}
-
-.header-button .toolbarbutton-text {
+.spinbuttons-button > .button-box > .button-text {
display: none;
}
-.header-button[disabled="true"] .toolbarbutton-icon {
- opacity: 0.4;
+.actionsMenu > .menulist-label-box > .menulist-icon {
+ margin-top: 2px;
+ -moz-margin-start: 2px;
+ -moz-margin-end: 8px !important;
}
-.header-button:not([disabled="true"]):active:hover,
-.header-button[open="true"] {
- border-color: rgba(45,54,71,0.7);
- box-shadow: inset 0 0 4px rgb(45,54,71), 0 1px rgba(255,255,255,0.25);
- background-image: linear-gradient(rgba(45,54,71,0.6), rgba(45,54,71,0));
+description {
+ font-size: 1.25rem;
+ line-height: 22px;
+}
+
+@media (min-resolution: 2dppx) {
+ checkbox:hover::before,
+ checkbox[checked]::before {
+ background-size: cover;
+ background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 30, 30, 0);
+ }
+
+ checkbox[checked]::before {
+ background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 60, 30, 30);
+ }
+
+ .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons@2x.png");
+ }
+
+ #category-general > .category-icon {
+ -moz-image-region: rect(0, 48px, 48px, 0);
+ }
+
+ #category-general[selected] > .category-icon {
+ -moz-image-region: rect(48px, 48px, 96px, 0);
+ }
+
+ #category-content > .category-icon {
+ -moz-image-region: rect(0, 96px, 48px, 48px);
+ }
+
+ #category-content[selected] > .category-icon {
+ -moz-image-region: rect(48px, 96px, 96px, 48px);
+ }
+
+ #category-application > .category-icon {
+ -moz-image-region: rect(0, 144px, 48px, 96px);
+ }
+
+ #category-application[selected] > .category-icon {
+ -moz-image-region: rect(48px, 144px, 96px, 96px);
+ }
+
+ #category-privacy > .category-icon {
+ -moz-image-region: rect(0, 192px, 48px, 144px);
+ }
+
+ #category-privacy[selected] > .category-icon {
+ -moz-image-region: rect(48px, 192px, 96px, 144px);
+ }
+
+ #category-security > .category-icon {
+ -moz-image-region: rect(0, 240px, 48px, 192px);
+ }
+
+ #category-security[selected] > .category-icon {
+ -moz-image-region: rect(48px, 240px, 96px, 192px);
+ }
+
+ #category-sync > .category-icon {
+ -moz-image-region: rect(0, 288px, 48px, 240px);
+ }
+
+ #category-sync[selected] > .category-icon {
+ -moz-image-region: rect(48px, 288px, 96px, 240px);
+ }
+
+ #category-advanced > .category-icon {
+ -moz-image-region: rect(0, 336px, 48px, 288px);
+ }
+
+ #category-advanced[selected] > .category-icon {
+ -moz-image-region: rect(48px, 336px, 96px, 288px);
+ }
}
diff --git a/browser/themes/osx/preferences/in-content/sorter.png b/browser/themes/osx/preferences/in-content/sorter.png
new file mode 100644
index 000000000000..3e5661fe169b
Binary files /dev/null and b/browser/themes/osx/preferences/in-content/sorter.png differ
diff --git a/browser/themes/shared/ClearSans-Regular.ttf b/browser/themes/shared/ClearSans-Regular.ttf
new file mode 100644
index 000000000000..fe686f8d2a13
Binary files /dev/null and b/browser/themes/shared/ClearSans-Regular.ttf differ
diff --git a/browser/themes/shared/customizableui/panelUIOverlay.inc.css b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
index 0435f21ebb7a..550585f1b933 100644
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -163,11 +163,15 @@ panelmultiview[nosubviews=true] > .panel-viewcontainer > .panel-viewstack > .pan
.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text,
.panelUI-grid .toolbarbutton-1 > .toolbarbutton-multiline-text {
- -moz-hyphens: auto;
line-height: 1.1;
max-height: 2.2em;
}
+.panelUI-grid .toolbarbutton-1:not([auto-hyphens="off"]) > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text,
+.panelUI-grid .toolbarbutton-1:not([auto-hyphens="off"]) > .toolbarbutton-multiline-text {
+ -moz-hyphens: auto;
+}
+
.panelUI-grid:not([customize-transitioning]) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text,
.panelUI-grid:not([customize-transitioning]) .toolbarbutton-1 > .toolbarbutton-multiline-text {
position: absolute;
diff --git a/browser/themes/shared/in-content/preferences.css b/browser/themes/shared/in-content/preferences.css
new file mode 100644
index 000000000000..d24b09df8524
--- /dev/null
+++ b/browser/themes/shared/in-content/preferences.css
@@ -0,0 +1,751 @@
+%if 0
+/* - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+@namespace html "http://www.w3.org/1999/xhtml";
+
+@font-face {
+ font-family: "Clear Sans";
+ src: url("chrome://browser/skin/fonts/ClearSans-Regular.ttf");
+}
+
+page {
+ -moz-appearance: none;
+ background-image: linear-gradient(#FFFFFF, #EDEDED 100px);
+}
+
+caption {
+ -moz-appearance: none;
+ margin: 0;
+}
+
+.caption-text {
+ font-size: 1.3rem;
+ font-weight: bold;
+ line-height: 22px;
+ margin: 0 !important;
+}
+
+.main-content {
+ max-width: 800px;
+}
+
+prefpane > .content-box {
+ overflow: auto;
+}
+
+prefpane {
+ padding: 40px 48px 48px;
+ font-family: "Clear Sans", sans-serif;
+ font-size: 1.25rem;
+ line-height: 22px;
+ color: #424E5A;
+}
+
+prefpane > .content-box {
+ overflow: auto;
+}
+
+/* groupboxes */
+
+groupbox {
+ -moz-appearance: none;
+ border: none;
+ margin-top: 15px;
+ margin-bottom: 15px;
+ -moz-margin-start: 60px;
+ -moz-padding-start: 0;
+ font-size: 1.25rem;
+}
+
+groupbox label {
+ -moz-margin-start: 0;
+}
+
+/* tabpanels and tabs */
+
+tabpanels {
+ -moz-appearance: none;
+ font-size: 1.25rem;
+ line-height: 22px;
+ color: #424E5A;
+ border: none;
+ padding: 0;
+ background-color: transparent;
+}
+
+tabs {
+ -moz-margin-start: 60px;
+ margin-bottom: 15px;
+ border-top: 2px solid;
+ border-bottom: 2px solid;
+ -moz-border-top-colors: #BBBBBB #F9F9F9;
+ -moz-border-bottom-colors: #F9F9F9 #BBBBBB;
+}
+
+.tabs-left,
+.tabs-right {
+ border-bottom: none;
+}
+
+tab {
+ -moz-appearance: none;
+ margin-top: 0;
+ padding: 0;
+ -moz-margin-end: 30px;
+ min-height: 60px;
+ background-color: transparent;
+ border-width: 0;
+ border-bottom: 3px solid transparent;
+}
+
+tab[selected] {
+ border-bottom-color: #FF9500;
+}
+
+.tab-text {
+ font-size: 1.3rem;
+ line-height: 22px;
+ color: #737980;
+ border: 1px solid transparent;
+ border-radius: 5px;
+}
+
+tab:not([selected]):hover > .tab-middle > .tab-text {
+ background-color: rgba(255,255,255,0.5);
+ border-color: #FFFFFF;
+}
+
+tab:not([selected]):hover:active > .tab-middle > .tab-text {
+ background-color: rgba(0,0,0,0.03);
+}
+
+tab[selected] > .tab-middle > .tab-text {
+ font-weight: bold;
+ color: #424E5A;
+}
+
+/* buttons and menulists */
+
+button,
+menulist {
+ -moz-appearance: none;
+ height: 30px;
+ max-height: 30px;
+ color: #737980;
+ line-height: 20px;
+ text-shadow: 0 1px 1px #FEFFFE;
+ border: 1px solid rgba(23,50,77,0.4);
+ -moz-border-top-colors: none !important;
+ -moz-border-right-colors: none !important;
+ -moz-border-bottom-colors: none !important;
+ -moz-border-left-colors: none !important;
+ border-radius: 5px;
+ box-shadow: 0 1px 1px 0 #FFFFFF, inset 0 2px 2px 0 #FFFFFF;
+ background-color: #F1F1F1;
+ background-image: linear-gradient(#FFFFFF, rgba(255,255,255,0.1));
+}
+
+button:not([disabled]):hover,
+menulist:not([disabled]):hover {
+ background-image: linear-gradient(#FFFFFF, rgba(255,255,255,0.6));
+}
+
+button:not([disabled]):hover:active,
+menulist[open="true"]:not([disabled]) {
+ background-image: linear-gradient(rgba(255,255,255,0.1),
+ rgba(255,255,255,0.6));
+}
+
+button[disabled],
+menulist[disabled] {
+ background-image: linear-gradient(rgba(255,255,255,0.5),
+ rgba(255,255,255,0.1));
+ border-color: rgba(23,50,77,0.25);
+ color: rgba(115,121,128,0.5);
+ text-shadow: 0 1px 1px #FFFFFF;
+}
+
+button > .button-box,
+menulist > .menulist-label-box {
+ padding-right: 10px !important;
+ padding-left: 10px !important;
+}
+
+button[type="menu"] > .button-box > .button-menu-dropmarker {
+ -moz-appearance: none;
+ margin: 1px 0;
+ -moz-margin-start: 10px;
+ padding: 0;
+ width: 10px;
+ height: 15px;
+ border: none;
+ background-color: transparent;
+ list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown.png")
+}
+
+.spinbuttons-button {
+ -moz-margin-start: 10px !important;
+ -moz-margin-end: 2px !important;
+}
+
+.spinbuttons-up {
+ margin-top: 2px !important;
+ border-radius: 4px 4px 0 0;
+}
+
+.spinbuttons-down {
+ margin-bottom: 2px !important;
+ border-radius: 0 0 4px 4px;
+}
+
+.spinbuttons-button > .button-box {
+ padding: 1px 5px 2px !important;
+}
+
+.spinbuttons-up > .button-box > .button-icon {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
+}
+
+.spinbuttons-up[disabled] > .button-box > .button-icon {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
+}
+
+.spinbuttons-down > .button-box > .button-icon {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+}
+
+.spinbuttons-down[disabled] > .button-box > .button-icon {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
+}
+
+menulist:not([editable="true"]) > .menulist-dropmarker {
+ -moz-appearance: none;
+ -moz-margin-end: 10px;
+ padding: 0;
+ border: none;
+ background-color: transparent;
+ list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown.png")
+}
+
+menulist[disabled]:not([editable="true"]) > .menulist-dropmarker {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown-disabled.png")
+}
+
+menulist > menupopup,
+button[type="menu"] > menupopup {
+ -moz-appearance: none;
+ border: 1px solid rgba(23,50,77,0.4);
+ border-radius: 5px;
+ background-color: #FFFFFF;
+}
+
+menulist > menupopup menu,
+menulist > menupopup menuitem,
+button[type="menu"] > menupopup menu,
+button[type="menu"] > menupopup menuitem {
+ -moz-appearance: none;
+ font-size: 1.25rem;
+ line-height: 22px;
+ height: 40px;
+ color: #737980;
+}
+
+menulist > menupopup > menu[_moz-menuactive="true"],
+menulist > menupopup > menuitem[_moz-menuactive="true"],
+button[type="menu"] > menupopup > menu[_moz-menuactive="true"],
+button[type="menu"] > menupopup > menuitem[_moz-menuactive="true"] {
+ color: #FFFFFF;
+ background-image: linear-gradient(#4CB1FF, #1792E5);
+}
+
+menulist > menupopup menuseparator,
+button[type="menu"] > menupopup menuseparator {
+ -moz-appearance: none;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ padding: 0;
+ border-top: 1px solid rgba(23,50,77,0.4);
+ border-bottom: none;
+}
+
+/* textboxes */
+
+textbox {
+ -moz-appearance: none;
+ height: 30px;
+ color: #737980;
+ line-height: 20px;
+ text-shadow: 0 1px 1px #FEFFFE;
+ padding-right: 10px;
+ padding-left: 10px;
+ border: 1px solid rgba(23,50,77,0.4);
+ -moz-border-top-colors: none !important;
+ -moz-border-right-colors: none !important;
+ -moz-border-bottom-colors: none !important;
+ -moz-border-left-colors: none !important;
+ border-radius: 5px;
+ box-shadow: 0 1px 1px 0 #FFFFFF, inset 0 2px 2px 0 rgba(0,0,0,0.03);
+ background-color: #F1F1F1;
+ background-image: linear-gradient(#FFFFFF, rgba(255,255,255,0.8));
+}
+
+textbox[focused] {
+ color: #424E5A;
+ border-color: #0096DC;
+ box-shadow: 0 0 2px 2px rgba(0,150,220,0.35), inset 0 0 2px 0 #0096DC;
+}
+
+textbox[disabled] {
+ color: rgba(115,121,128,0.5);
+ border-color: rgba(23,50,77,0.25);
+ background-image: linear-gradient(rgba(255,255,255,0.5), rgba(255,255,255,0.4));
+}
+
+/* Links */
+
+.text-link,
+.inline-link,
+html|a.inline-link {
+ font-size: 1.25rem;
+ line-height: 22px;
+ color: #0096DC;
+}
+
+.text-link:hover,
+.inline-link:hover {
+ color: #4CB1FF;
+ text-decoration: none;
+}
+
+.text-link:hover:active,
+.inline-link:hover:active {
+ color: #FF9500;
+ text-decoration: none;
+}
+
+/* Checkboxes and radio buttons */
+
+checkbox {
+ margin: 7px 0;
+}
+
+.checkbox-check {
+ -moz-appearance: none;
+ width: 23px;
+ height: 23px;
+ border-radius: 2px;
+ border: 1px solid rgba(23,50,77,0.40);
+ -moz-margin-end: 10px;
+ background-color: #f1f1f1;
+ background-image: linear-gradient(#ffffff 0%, rgba(255,255,255,0.80) 100%);
+ box-shadow: 0 1px 1px 0 #ffffff, inset 0 2px 0 0 rgba(0,0,0,0.03);
+}
+
+.checkbox-check[checked] {
+ border-color: #0096dc;
+ box-shadow: 0 0 2px 2px rgba(0,150,220,0.35), inset 0 0 2px 0 #0096dc;
+}
+
+.checkbox-label-box {
+ -moz-margin-start: -1px; /* negative margin for the transparent border */
+ -moz-padding-start: 0;
+}
+
+checkbox:hover::before,
+checkbox[checked]::before {
+ position: absolute;
+ content: "";
+ width: 15px;
+ height: 10px;
+ background-image: url("chrome://browser/skin/preferences/in-content/check.png");
+}
+
+checkbox[checked]::before {
+ background-position: -15px 0;
+}
+
+.radio-check {
+ -moz-appearance: none;
+ width: 23px;
+ height: 23px;
+ border: 1px solid rgba(23,50,77,0.40);
+ border-radius: 50%;
+ -moz-margin-end: 10px;
+ background-color: #f1f1f1;
+ background-image: linear-gradient(#ffffff 0%, rgba(255,255,255,0.80) 100%);
+ box-shadow: 0 1px 1px 0 #ffffff, inset 0 2px 0 0 rgba(0,0,0,0.03);
+}
+
+.radio-check[selected] {
+ border-color: #0096dc;
+ box-shadow: 0 0 2px 2px rgba(0,150,220,0.35), inset 0 0 2px 0 #0096dc;
+}
+
+.radio-label-box {
+ -moz-margin-start: -1px; /* negative margin for the transparent border */
+ -moz-margin-end: 10px;
+ -moz-padding-start: 0;
+}
+
+radio:hover::before,
+radio[selected]::before {
+ position: absolute;
+ content: "";
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ margin-bottom: 1px;
+ background-image: linear-gradient(rgba(76,177,255,0.25) 0%, rgba(23,146,229,0.25) 100%);
+}
+
+radio[selected]::before {
+ background-image: linear-gradient(#4cb1ff 0%, #1792e5 100%);
+}
+
+/* Category List */
+
+#categories {
+ -moz-appearance: none;
+ background-color: #424e5a;
+ -moz-border-end: 1px solid rgba(0,0,0,0.20);
+ padding-top: 39px;
+ margin: 0;
+}
+
+.category {
+ -moz-appearance: none;
+ color: rgba(241,241,241,0.70);
+ border: 1px solid transparent;
+ -moz-border-end-width: 0;
+ -moz-padding-start: 14px;
+ -moz-padding-end: 21px;
+ margin-bottom: -1px;
+ min-height: 40px;
+}
+
+.category:hover {
+ background-color: rgba(255,255,255,0.15);
+ border-color: rgba(255,255,255,0.20);
+}
+
+.category[selected] {
+ background-color: rgba(0,0,0,0.20);
+ border-color: rgba(255,255,255,0.20);
+ -moz-border-start-width: 3px;
+ -moz-border-start-color: #ff9500;
+ -moz-padding-start: 12px;
+ color: #f1f1f1;
+}
+
+.category-name {
+ line-height: 22px;
+ font-family: "Clear Sans", sans-serif;
+ font-size: 1.25rem;
+ padding-bottom: 2px;
+ -moz-padding-start: 9px;
+ margin: 0;
+}
+
+.category-icon {
+ width: 24px;
+ height: 24px;
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.png");
+}
+
+#category-general > .category-icon {
+ -moz-image-region: rect(0, 24px, 24px, 0);
+}
+
+#category-general[selected] > .category-icon {
+ -moz-image-region: rect(24px, 24px, 48px, 0);
+}
+
+#category-content > .category-icon {
+ -moz-image-region: rect(0, 48px, 24px, 24px);
+}
+
+#category-content[selected] > .category-icon {
+ -moz-image-region: rect(24px, 48px, 48px, 24px);
+}
+
+#category-application > .category-icon {
+ -moz-image-region: rect(0, 72px, 24px, 48px);
+}
+
+#category-application[selected] > .category-icon {
+ -moz-image-region: rect(24px, 72px, 48px, 48px);
+}
+
+#category-privacy > .category-icon {
+ -moz-image-region: rect(0, 96px, 24px, 72px);
+}
+
+#category-privacy[selected] > .category-icon {
+ -moz-image-region: rect(24px, 96px, 48px, 72px);
+}
+
+#category-security > .category-icon {
+ -moz-image-region: rect(0, 120px, 24px, 96px);
+}
+
+#category-security[selected] > .category-icon {
+ -moz-image-region: rect(24px, 120px, 48px, 96px);
+}
+
+#category-sync > .category-icon {
+ -moz-image-region: rect(0, 144px, 24px, 120px);
+}
+
+#category-sync[selected] > .category-icon {
+ -moz-image-region: rect(24px, 144px, 48px, 120px);
+}
+
+#category-advanced > .category-icon {
+ -moz-image-region: rect(0, 168px, 24px, 144px);
+}
+
+#category-advanced[selected] > .category-icon {
+ -moz-image-region: rect(24px, 168px, 48px, 144px);
+}
+
+/* header */
+
+.header {
+ margin-bottom: 15px;
+}
+
+.header-icon {
+ width: 40px;
+ max-height: 40px;
+ -moz-margin-end: 20px;
+ list-style-image: url("chrome://browser/skin/preferences/in-content/header.png");
+}
+
+.header-name {
+ font-size: 2.5rem;
+ font-weight: normal;
+ line-height: 40px;
+ margin: 0;
+}
+
+#header-general > .header-icon {
+ -moz-image-region: rect(3px, 45px, 45px, 3px);
+}
+
+#header-content > .header-icon {
+ -moz-image-region: rect(3px, 93px, 45px, 51px);
+}
+
+#header-application > .header-icon {
+ -moz-image-region: rect(3px, 141px, 45px, 99px);
+}
+
+#header-privacy > .header-icon {
+ -moz-image-region: rect(3px, 189px, 45px, 147px);
+}
+
+#header-security > .header-icon {
+ -moz-image-region: rect(3px, 237px, 45px, 195px);
+}
+
+#header-sync > .header-icon {
+ -moz-image-region: rect(3px, 285px, 45px, 243px);
+}
+
+#header-advanced > .header-icon {
+ -moz-image-region: rect(3px, 333px, 45px, 291px);
+}
+
+.indent {
+ margin-top: 7px;
+ margin-bottom: 7px;
+}
+
+/* General Pane */
+
+filefield {
+ -moz-appearance: none;
+ background-color: transparent;
+ border: none;
+ padding: 0;
+}
+
+.fileFieldContentBox {
+ background-color: transparent;
+}
+
+.fileFieldIcon {
+ -moz-margin-start: 10px;
+ -moz-margin-end: 0;
+}
+
+.fileFieldLabel {
+ -moz-margin-start: -26px;
+ -moz-padding-start: 36px;
+}
+
+#chooseFolder {
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+/* Applications Pane Styles */
+
+#applications-content {
+ -moz-margin-start: 60px;
+ padding: 15px;
+}
+
+#handlersView {
+ -moz-appearance: none;
+ font-size: 1.25rem;
+ line-height: 22px;
+ color: #737980;
+ border: 1px solid rgba(23,50,77,0.4);
+ border-radius: 5px;
+ background-color: #F1F1F1;
+ overflow-y: auto;
+}
+
+#typeColumn,
+#actionColumn {
+ -moz-appearance: none;
+ font-family: "Clear Sans", sans-serif;
+ line-height: 20px;
+ color: #737980;
+ height: 36px;
+ padding: 0 10px;
+ background-color: #F7F7F7;
+ border: 1px solid #CCCCCC;
+ -moz-border-top-colors: none;
+ -moz-border-right-colors: none;
+ -moz-border-bottom-colors: none;
+ -moz-border-left-colors: none;
+ text-shadow: 0 1px 1px #FFFFFF;
+}
+
+#typeColumn:-moz-locale-dir(ltr),
+#actionColumn:-moz-locale-dir(rtl) {
+ border-top-left-radius: 5px;
+}
+
+#typeColumn:-moz-locale-dir(rtl),
+#actionColumn:-moz-locale-dir(ltr) {
+ border-top-right-radius: 5px;
+}
+
+#typeColumn:hover,
+#actionColumn:hover {
+ border-color: #737980;
+}
+
+#typeColumn:hover:active,
+#actionColumn:hover:active {
+ padding: 0 10px;
+ border-color: #0096DC;
+ box-shadow: 0 0 2px 2px rgba(0,150,220,0.35), inset 0 0 2px 0 #0096DC;
+}
+
+#typeColumn > .treecol-sortdirection[sortDirection=ascending],
+#actionColumn > .treecol-sortdirection[sortDirection=ascending],
+#typeColumn > .treecol-sortdirection[sortDirection=descending],
+#actionColumn > .treecol-sortdirection[sortDirection=descending] {
+ -moz-appearance: none;
+ list-style-image: url("chrome://browser/skin/preferences/in-content/sorter.png");
+}
+
+#typeColumn > .treecol-sortdirection[sortDirection=descending],
+#actionColumn > .treecol-sortdirection[sortDirection=descending] {
+ transform: scaleY(-1);
+}
+
+#handlersView > richlistitem {
+ min-height: 40px !important;
+}
+
+.typeIcon {
+ -moz-margin-start: 10px !important;
+ -moz-margin-end: 9px !important;
+}
+
+.actionIcon {
+ -moz-margin-start: 11px !important;
+ -moz-margin-end: 8px !important;
+}
+
+.actionsMenu {
+ height: 40px;
+ max-height: 40px;
+}
+
+.actionsMenu > menupopup > menuitem {
+ -moz-padding-start: 10px !important;
+}
+
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ -moz-margin-end: 8px !important;
+}
+
+/* XXX This style is for bug 740213 and should be removed once that
+ bug has a solution. */
+description > html|a {
+ cursor: pointer;
+}
+
+/* Content Pane */
+
+#defaultFontSize {
+ min-width: 5.5em;
+}
+
+/* Security Pane */
+
+/* Add margins to this buttons to unsqueeze the checkboxed in same hbox */
+#addonExceptions,
+#cookieExceptions,
+#passwordExceptions,
+#changeMasterPassword {
+ margin-top: 4px;
+ margin-bottom: 5px;
+}
+
+/* Sync Pane */
+
+#syncEnginesList {
+ -moz-appearance: none;
+ color: #737980;
+ padding: 10px;
+ border: 1px solid rgba(23,50,77,0.4);
+ border-radius: 5px;
+ background-color: #F1F1F1;
+}
+
+/* Advanced Pane */
+
+#advancedPrefs {
+ padding-bottom: 0; /* no padding needed in inContent prefs */
+}
+
+#encryptionPanel {
+ margin-top: 15px;
+ -moz-margin-start: 60px;
+}
+
+#offlineAppsList {
+ -moz-appearance: none;
+ color: #737980;
+ padding: 2px;
+ border: 1px solid rgba(23,50,77,0.4);
+ border-radius: 5px;
+ background-color: #F1F1F1;
+}
+
+#telemetryLearnMore,
+#FHRLearnMore,
+#crashReporterLearnMore {
+ /* center the links */
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css
index 951811dae761..2d394d3c3be0 100644
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -799,7 +799,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
border-color: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
0 0 1px hsla(210,54%,20%,.2) inset,
- /* allows windows-keyhole-forward-clip-path to be used for non-hover as well as hover: */
+ /* allows keyhole-forward-clip-path to be used for non-hover as well as hover: */
0 1px 0 hsla(210,54%,20%,0),
0 0 2px hsla(210,54%,20%,0);
text-shadow: none;
@@ -853,7 +853,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
#forward-button > .toolbarbutton-icon {
background-clip: padding-box !important;
/*mask: url(keyhole-forward-mask.svg#mask); XXX: this regresses twinopen */
- clip-path: url(chrome://browser/content/browser.xul#windows-keyhole-forward-clip-path) !important;
+ clip-path: url(chrome://browser/content/browser.xul#keyhole-forward-clip-path) !important;
margin-left: -6px !important;
border-left-style: none !important;
border-radius: 0 !important;
@@ -1156,7 +1156,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
- clip-path: url("chrome://browser/content/browser.xul#windows-urlbar-back-button-clip-path");
+ clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn
index 2a82cafeef18..cdeec87bf999 100644
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -126,6 +126,7 @@ browser.jar:
skin/classic/browser/feeds/videoFeedIcon16.png (feeds/feedIcon16.png)
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+ skin/classic/browser/fonts/ClearSans-Regular.ttf (../shared/ClearSans-Regular.ttf)
skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/browser/newtab/controls.png (newtab/controls.png)
skin/classic/browser/places/places.css (places/places.css)
@@ -159,6 +160,14 @@ browser.jar:
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
+ skin/classic/browser/preferences/in-content/check.png (preferences/in-content/check.png)
+ skin/classic/browser/preferences/in-content/check@2x.png (preferences/in-content/check@2x.png)
+ skin/classic/browser/preferences/in-content/icons.png (preferences/in-content/icons.png)
+ skin/classic/browser/preferences/in-content/icons@2x.png (preferences/in-content/icons@2x.png)
+ skin/classic/browser/preferences/in-content/header.png (preferences/in-content/icons@2x.png)
+ skin/classic/browser/preferences/in-content/sorter.png (preferences/in-content/sorter.png)
+ skin/classic/browser/preferences/in-content/dropdown.png (preferences/in-content/dropdown.png)
+ skin/classic/browser/preferences/in-content/dropdown-disabled.png (preferences/in-content/dropdown-disabled.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)
@@ -465,6 +474,7 @@ browser.jar:
skin/classic/aero/browser/feeds/videoFeedIcon16.png (feeds/feedIcon16-aero.png)
skin/classic/aero/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/aero/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+ skin/classic/aero/browser/fonts/ClearSans-Regular.ttf (../shared/ClearSans-Regular.ttf)
skin/classic/aero/browser/newtab/newTab.css (newtab/newTab.css)
skin/classic/aero/browser/newtab/controls.png (newtab/controls.png)
* skin/classic/aero/browser/places/places.css (places/places-aero.css)
@@ -498,6 +508,14 @@ browser.jar:
skin/classic/aero/browser/preferences/saveFile.png (preferences/saveFile-aero.png)
* skin/classic/aero/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
+ skin/classic/aero/browser/preferences/in-content/check.png (preferences/in-content/check.png)
+ skin/classic/aero/browser/preferences/in-content/check@2x.png (preferences/in-content/check@2x.png)
+ skin/classic/aero/browser/preferences/in-content/icons.png (preferences/in-content/icons.png)
+ skin/classic/aero/browser/preferences/in-content/icons@2x.png (preferences/in-content/icons@2x.png)
+ skin/classic/aero/browser/preferences/in-content/header.png (preferences/in-content/icons@2x.png)
+ skin/classic/aero/browser/preferences/in-content/sorter.png (preferences/in-content/sorter.png)
+ skin/classic/aero/browser/preferences/in-content/dropdown.png (preferences/in-content/dropdown.png)
+ skin/classic/aero/browser/preferences/in-content/dropdown-disabled.png (preferences/in-content/dropdown-disabled.png)
skin/classic/aero/browser/preferences/applications.css (preferences/applications.css)
skin/classic/aero/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/aero/browser/social/services-16.png (social/services-16.png)
diff --git a/browser/themes/windows/preferences/in-content/check.png b/browser/themes/windows/preferences/in-content/check.png
new file mode 100644
index 000000000000..a9438df08b46
Binary files /dev/null and b/browser/themes/windows/preferences/in-content/check.png differ
diff --git a/browser/themes/windows/preferences/in-content/check@2x.png b/browser/themes/windows/preferences/in-content/check@2x.png
new file mode 100644
index 000000000000..6ba07a2a8ffb
Binary files /dev/null and b/browser/themes/windows/preferences/in-content/check@2x.png differ
diff --git a/browser/themes/windows/preferences/in-content/dropdown-disabled.png b/browser/themes/windows/preferences/in-content/dropdown-disabled.png
new file mode 100644
index 000000000000..9e0a4718265c
Binary files /dev/null and b/browser/themes/windows/preferences/in-content/dropdown-disabled.png differ
diff --git a/browser/themes/windows/preferences/in-content/dropdown.png b/browser/themes/windows/preferences/in-content/dropdown.png
new file mode 100644
index 000000000000..9843b82f473f
Binary files /dev/null and b/browser/themes/windows/preferences/in-content/dropdown.png differ
diff --git a/browser/themes/windows/preferences/in-content/icons.png b/browser/themes/windows/preferences/in-content/icons.png
new file mode 100644
index 000000000000..cf6a16a51989
Binary files /dev/null and b/browser/themes/windows/preferences/in-content/icons.png differ
diff --git a/browser/themes/windows/preferences/in-content/icons@2x.png b/browser/themes/windows/preferences/in-content/icons@2x.png
new file mode 100644
index 000000000000..e85db40ad874
Binary files /dev/null and b/browser/themes/windows/preferences/in-content/icons@2x.png differ
diff --git a/browser/themes/windows/preferences/in-content/preferences.css b/browser/themes/windows/preferences/in-content/preferences.css
index 95f355eaf3c3..abb831a50368 100644
--- a/browser/themes/windows/preferences/in-content/preferences.css
+++ b/browser/themes/windows/preferences/in-content/preferences.css
@@ -2,194 +2,103 @@
- 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/. */
-@import url("chrome://global/skin/inContentUI.css");
+%include ../../../shared/in-content/preferences.css
-@namespace html "http://www.w3.org/1999/xhtml";
-
-#header {
- margin-bottom: 18px;
+menulist:not([editable="true"]) > .menulist-dropmarker {
+ margin-top: 1px;
+ margin-bottom: 1px;
}
-caption {
- font-size: 1.667rem;
+checkbox:hover::before,
+checkbox[checked]::before {
+ margin-bottom: -1px;
+ -moz-margin-end: -19px;
+ -moz-margin-start: 4px;
}
-.main-content {
- max-width: 800px;
+radio {
+ -moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
+ margin: 7px 0;
}
-prefpane > .content-box {
- overflow: auto;
+radio:hover::before,
+radio[selected]::before {
+ -moz-margin-end: -17px;
+ -moz-margin-start: 6px;
}
-/* Category List */
-
-#categories {
- -moz-appearance: none;
- border: none;
- -moz-margin-end: -1px;
- background-color: transparent;
- position: relative;
- margin-top: 31px;
+.actionsMenu > .menulist-label-box > .menulist-icon {
+ -moz-margin-end: 9px;
}
-.category {
- -moz-appearance: none;
- background-color: transparent;
- color: #252F3B;
- padding: 10px 4px;
- border-width: 1px;
- border-style: solid;
- border-color: transparent;
- -moz-padding-end: 8px;
- -moz-box-align: center;
- overflow: hidden;
- min-height: 0;
- height: 52px;
-}
+@media (min-resolution: 2dppx) {
+ checkbox:hover::before,
+ checkbox[checked]::before {
+ background-size: cover;
+ background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 30, 30, 0);
+ }
-.category:-moz-locale-dir(ltr) {
- border-top-left-radius: 5px;
- border-bottom-left-radius: 5px;
-}
+ checkbox[checked]::before {
+ background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 60, 30, 30);
+ }
-.category:-moz-locale-dir(rtl) {
- border-top-right-radius: 5px;
- border-bottom-right-radius: 5px;
-}
+ .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons@2x.png");
+ }
-.category[selected] {
- background-color: rgba(255, 255, 255, 0.4);
- color: #252F3B;
- border-color: #C3CEDF;
- -moz-border-end-color: #E2E9F2;
-}
+ #category-general > .category-icon {
+ -moz-image-region: rect(0, 48px, 48px, 0);
+ }
-.category-name {
- font-size: 1.5rem;
- -moz-padding-end: 24px;
-}
+ #category-general[selected] > .category-icon {
+ -moz-image-region: rect(48px, 48px, 96px, 0);
+ }
-/* Maximize the size of the viewport when the window is small */
-@media (max-width: 800px) {
- .category-name {
- display: none;
+ #category-content > .category-icon {
+ -moz-image-region: rect(0, 96px, 48px, 48px);
+ }
+
+ #category-content[selected] > .category-icon {
+ -moz-image-region: rect(48px, 96px, 96px, 48px);
+ }
+
+ #category-application > .category-icon {
+ -moz-image-region: rect(0, 144px, 48px, 96px);
+ }
+
+ #category-application[selected] > .category-icon {
+ -moz-image-region: rect(48px, 144px, 96px, 96px);
+ }
+
+ #category-privacy > .category-icon {
+ -moz-image-region: rect(0, 192px, 48px, 144px);
+ }
+
+ #category-privacy[selected] > .category-icon {
+ -moz-image-region: rect(48px, 192px, 96px, 144px);
+ }
+
+ #category-security > .category-icon {
+ -moz-image-region: rect(0, 240px, 48px, 192px);
+ }
+
+ #category-security[selected] > .category-icon {
+ -moz-image-region: rect(48px, 240px, 96px, 192px);
+ }
+
+ #category-sync > .category-icon {
+ -moz-image-region: rect(0, 288px, 48px, 240px);
+ }
+
+ #category-sync[selected] > .category-icon {
+ -moz-image-region: rect(48px, 288px, 96px, 240px);
+ }
+
+ #category-advanced > .category-icon {
+ -moz-image-region: rect(0, 336px, 48px, 288px);
+ }
+
+ #category-advanced[selected] > .category-icon {
+ -moz-image-region: rect(48px, 336px, 96px, 288px);
}
}
-
-.category-icon {
- width: 32px;
- height: 32px;
- margin: 0 6px;
- -moz-margin-start: 6px;
- -moz-margin-end: 5px;
- list-style-image: url("chrome://browser/skin/preferences/Options.png");
-}
-
-#category-general > .category-icon {
- -moz-image-region: rect(0, 32px, 32px, 0);
-}
-
-#category-content > .category-icon {
- -moz-image-region: rect(0, 96px, 32px, 64px)
-}
-
-#category-application > .category-icon {
- -moz-image-region: rect(0, 128px, 32px, 96px)
-}
-
-#category-privacy > .category-icon {
- -moz-image-region: rect(0, 160px, 32px, 128px)
-}
-
-#category-security > .category-icon {
- -moz-image-region: rect(0, 192px, 32px, 160px)
-}
-
-#category-advanced > .category-icon {
- -moz-image-region: rect(0, 224px, 32px, 192px)
-}
-
-%ifdef MOZ_SERVICES_SYNC
-#category-sync > .category-icon {
- list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
-}
-%endif
-
-/* Applications Pane Styles */
-
-#applications-content {
- padding: 15px;
-}
-
-#handlersView {
- -moz-appearance: none;
- border: 1px solid rgba(31,64,100,0.4);
- overflow-y: auto;
-}
-
-/* XXX This style is for bug 740213 and should be removed once that
- bug has a solution. */
-description > html|a {
- cursor: pointer;
-}
-
-/* XXX Styles Below can be removed once bug 660726 lands */
-.nav-button {
- list-style-image: url(chrome://mozapps/skin/extensions/navigation.png);
-}
-
-#forward-btn {
- -moz-border-start: none;
-}
-
-#back-btn:-moz-locale-dir(ltr),
-#forward-btn:-moz-locale-dir(rtl) {
- -moz-image-region: rect(0, 18px, 18px, 0);
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-#back-btn:-moz-locale-dir(rtl),
-#forward-btn:-moz-locale-dir(ltr) {
- -moz-image-region: rect(0, 36px, 18px, 18px);
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
-}
-
-.header-button[disabled="true"] {
- opacity: 0.8;
-}
-
-.header-button {
- -moz-appearance: none;
- padding: 1px 3px;
- color: #444;
- text-shadow: 0 0 3px white;
- background: linear-gradient(
- rgba(251,252,253,0.95),
- rgba(246,247,248,0) 49%,
- rgba(211,212,213,0.45) 51%,
- rgba(225,226,229, 0.3));
- background-clip: padding-box;
- border-radius: 2.5px;
- border: 1px solid rgba(31,64,100,0.4);
- border-top-color: rgba(31,64,100,0.3);
- box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.25) inset,
- 0 0 2px 1px rgba(255, 255, 255, 0.25) inset;
-}
-
-.header-button[disabled="true"] > .toolbarbutton-icon {
- opacity: 0.4;
-}
-
-.header-button:not([disabled="true"]):active:hover,
-.header-button[open="true"] {
- background-color: rgba(61, 76, 92, 0.2);
- border-color: rgba(39, 53, 68, 0.5);
- box-shadow: 0 0 3px 1px rgba(39, 53, 68, 0.2) inset;
-}
-
-.header-button > .toolbarbutton-text {
- display: none;
-}
diff --git a/browser/themes/windows/preferences/in-content/sorter.png b/browser/themes/windows/preferences/in-content/sorter.png
new file mode 100644
index 000000000000..3e5661fe169b
Binary files /dev/null and b/browser/themes/windows/preferences/in-content/sorter.png differ
diff --git a/mobile/android/base/home/DynamicPanel.java b/mobile/android/base/home/DynamicPanel.java
index 17164523c483..cf14d369a522 100644
--- a/mobile/android/base/home/DynamicPanel.java
+++ b/mobile/android/base/home/DynamicPanel.java
@@ -17,6 +17,7 @@ import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
import org.mozilla.gecko.home.PanelLayout.DatasetRequest;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
import android.app.Activity;
import android.content.ContentResolver;
@@ -31,6 +32,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
/**
* Fragment that displays dynamic content specified by a {@code PanelConfig}.
@@ -58,8 +60,21 @@ public class DynamicPanel extends HomeFragment
// Dataset ID to be used by the loader
private static final String DATASET_REQUEST = "dataset_request";
+ // The main view for this fragment. This contains the PanelLayout and PanelAuthLayout.
+ private FrameLayout mView;
+
// The panel layout associated with this panel
- private PanelLayout mLayout;
+ private PanelLayout mPanelLayout;
+
+ // The layout used to show authentication UI for this panel
+ private PanelAuthLayout mPanelAuthLayout;
+
+ // Cache used to keep track of whether or not the user has been authenticated.
+ private PanelAuthCache mPanelAuthCache;
+
+ // Hold a reference to the UiAsyncTask we use to check the state of the
+ // PanelAuthCache, so that we can cancel it if necessary.
+ private UiAsyncTask mAuthStateTask;
// The configuration associated with this panel
private PanelConfig mPanelConfig;
@@ -70,6 +85,17 @@ public class DynamicPanel extends HomeFragment
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
+ /*
+ * Different UI modes to display depending on the authentication state.
+ *
+ * PANEL: Layout to display panel data.
+ * AUTH: Authentication UI.
+ */
+ private enum UIMode {
+ PANEL,
+ AUTH
+ }
+
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
@@ -101,37 +127,38 @@ public class DynamicPanel extends HomeFragment
if (mPanelConfig == null) {
throw new IllegalStateException("Can't create a DynamicPanel without a PanelConfig");
}
+
+ mPanelAuthCache = new PanelAuthCache(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- switch(mPanelConfig.getLayoutType()) {
- case FRAME:
- final PanelDatasetHandler datasetHandler = new PanelDatasetHandler();
- mLayout = new FramePanelLayout(getActivity(), mPanelConfig, datasetHandler, mUrlOpenListener);
- break;
-
- default:
- throw new IllegalStateException("Unrecognized layout type in DynamicPanel");
- }
-
- Log.d(LOGTAG, "Created layout of type: " + mPanelConfig.getLayoutType());
-
- return mLayout;
+ mView = new FrameLayout(getActivity());
+ return mView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+
+ mPanelAuthCache.setOnChangeListener(new PanelAuthChangeListener());
GeckoAppShell.registerEventListener("HomePanels:RefreshDataset", this);
}
@Override
public void onDestroyView() {
super.onDestroyView();
- mLayout = null;
+ mView = null;
+ mPanelLayout = null;
+ mPanelAuthLayout = null;
+ mPanelAuthCache.setOnChangeListener(null);
GeckoAppShell.unregisterEventListener("HomePanels:RefreshDataset", this);
+
+ if (mAuthStateTask != null) {
+ mAuthStateTask.cancel(true);
+ mAuthStateTask = null;
+ }
}
@Override
@@ -159,7 +186,88 @@ public class DynamicPanel extends HomeFragment
@Override
protected void load() {
Log.d(LOGTAG, "Loading layout");
- mLayout.load();
+
+ if (requiresAuth()) {
+ mAuthStateTask = new UiAsyncTask(ThreadUtils.getBackgroundHandler()) {
+ @Override
+ public synchronized Boolean doInBackground(Void... params) {
+ return mPanelAuthCache.isAuthenticated(mPanelConfig.getId());
+ }
+
+ @Override
+ public void onPostExecute(Boolean isAuthenticated) {
+ mAuthStateTask = null;
+ setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
+ }
+ };
+ mAuthStateTask.execute();
+ } else {
+ setUIMode(UIMode.PANEL);
+ }
+ }
+
+ /**
+ * @return true if this panel requires authentication.
+ */
+ private boolean requiresAuth() {
+ return mPanelConfig.getAuthConfig() != null;
+ }
+
+ /**
+ * Lazily creates layout for panel data.
+ */
+ private void createPanelLayout() {
+ switch(mPanelConfig.getLayoutType()) {
+ case FRAME:
+ final PanelDatasetHandler datasetHandler = new PanelDatasetHandler();
+ mPanelLayout = new FramePanelLayout(getActivity(), mPanelConfig, datasetHandler, mUrlOpenListener);
+ break;
+
+ default:
+ throw new IllegalStateException("Unrecognized layout type in DynamicPanel");
+ }
+
+ Log.d(LOGTAG, "Created layout of type: " + mPanelConfig.getLayoutType());
+ mView.addView(mPanelLayout);
+ }
+
+ /**
+ * Lazily creates layout for authentication UI.
+ */
+ private void createPanelAuthLayout() {
+ mPanelAuthLayout = new PanelAuthLayout(getActivity(), mPanelConfig);
+ mView.addView(mPanelAuthLayout, 0);
+ }
+
+ private void setUIMode(UIMode mode) {
+ switch(mode) {
+ case PANEL:
+ if (mPanelAuthLayout != null) {
+ mPanelAuthLayout.setVisibility(View.GONE);
+ }
+ if (mPanelLayout == null) {
+ createPanelLayout();
+ }
+ mPanelLayout.setVisibility(View.VISIBLE);
+
+ if (canLoad()) {
+ mPanelLayout.load();
+ }
+ break;
+
+ case AUTH:
+ if (mPanelLayout != null) {
+ mPanelLayout.setVisibility(View.GONE);
+ }
+ if (mPanelAuthLayout == null) {
+ createPanelAuthLayout();
+ }
+ mPanelAuthLayout.setVisibility(View.VISIBLE);
+ break;
+
+ default:
+ throw new IllegalStateException("Unrecognized UIMode in DynamicPanel");
+ }
}
@Override
@@ -312,15 +420,17 @@ public class DynamicPanel extends HomeFragment
final DatasetRequest request = getRequestFromLoader(loader);
Log.d(LOGTAG, "Finished loader for request: " + request);
- mLayout.deliverDataset(request, cursor);
+ if (mPanelLayout != null) {
+ mPanelLayout.deliverDataset(request, cursor);
+ }
}
@Override
public void onLoaderReset(Loader loader) {
final DatasetRequest request = getRequestFromLoader(loader);
Log.d(LOGTAG, "Resetting loader for request: " + request);
- if (mLayout != null) {
- mLayout.releaseDataset(request.getDatasetId());
+ if (mPanelLayout != null) {
+ mPanelLayout.releaseDataset(request.getDatasetId());
}
}
@@ -329,4 +439,15 @@ public class DynamicPanel extends HomeFragment
return datasetLoader.getRequest();
}
}
+
+ private class PanelAuthChangeListener implements PanelAuthCache.OnChangeListener {
+ @Override
+ public void onChange(String panelId, boolean isAuthenticated) {
+ if (!mPanelConfig.getId().equals(panelId)) {
+ return;
+ }
+
+ setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
+ }
+ }
}
diff --git a/mobile/android/base/home/HomeConfig.java b/mobile/android/base/home/HomeConfig.java
index 4b8c8b5ef128..a6529907628b 100644
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -100,6 +100,7 @@ public final class HomeConfig {
private final String mId;
private final LayoutType mLayoutType;
private final List mViews;
+ private final AuthConfig mAuthConfig;
private final EnumSet mFlags;
private static final String JSON_KEY_TYPE = "type";
@@ -107,6 +108,7 @@ public final class HomeConfig {
private static final String JSON_KEY_ID = "id";
private static final String JSON_KEY_LAYOUT = "layout";
private static final String JSON_KEY_VIEWS = "views";
+ private static final String JSON_KEY_AUTH_CONFIG = "authConfig";
private static final String JSON_KEY_DEFAULT = "default";
private static final String JSON_KEY_DISABLED = "disabled";
@@ -147,6 +149,13 @@ public final class HomeConfig {
mViews = null;
}
+ final JSONObject jsonAuthConfig = json.optJSONObject(JSON_KEY_AUTH_CONFIG);
+ if (jsonAuthConfig != null) {
+ mAuthConfig = new AuthConfig(jsonAuthConfig);
+ } else {
+ mAuthConfig = null;
+ }
+
mFlags = EnumSet.noneOf(Flags.class);
if (json.optBoolean(JSON_KEY_DEFAULT, false)) {
@@ -170,6 +179,8 @@ public final class HomeConfig {
mViews = new ArrayList();
in.readTypedList(mViews, ViewConfig.CREATOR);
+ mAuthConfig = (AuthConfig) in.readParcelable(getClass().getClassLoader());
+
mFlags = (EnumSet) in.readSerializable();
validate();
@@ -188,6 +199,8 @@ public final class HomeConfig {
mViews.add(new ViewConfig(viewConfig));
}
}
+
+ mAuthConfig = panelConfig.mAuthConfig;
mFlags = panelConfig.mFlags.clone();
validate();
@@ -198,16 +211,17 @@ public final class HomeConfig {
}
public PanelConfig(PanelType type, String title, String id, EnumSet flags) {
- this(type, title, id, null, null, flags);
+ this(type, title, id, null, null, null, flags);
}
public PanelConfig(PanelType type, String title, String id, LayoutType layoutType,
- List views, EnumSet flags) {
+ List views, AuthConfig authConfig, EnumSet flags) {
mType = type;
mTitle = title;
mId = id;
mLayoutType = layoutType;
mViews = views;
+ mAuthConfig = authConfig;
mFlags = flags;
validate();
@@ -291,6 +305,10 @@ public final class HomeConfig {
}
}
+ public AuthConfig getAuthConfig() {
+ return mAuthConfig;
+ }
+
public JSONObject toJSON() throws JSONException {
final JSONObject json = new JSONObject();
@@ -315,6 +333,10 @@ public final class HomeConfig {
json.put(JSON_KEY_VIEWS, jsonViews);
}
+ if (mAuthConfig != null) {
+ json.put(JSON_KEY_AUTH_CONFIG, mAuthConfig.toJSON());
+ }
+
if (mFlags.contains(Flags.DEFAULT_PANEL)) {
json.put(JSON_KEY_DEFAULT, true);
}
@@ -356,6 +378,7 @@ public final class HomeConfig {
dest.writeString(mId);
dest.writeParcelable(mLayoutType, 0);
dest.writeTypedList(mViews);
+ dest.writeParcelable(mAuthConfig, 0);
dest.writeSerializable(mFlags);
}
@@ -714,7 +737,103 @@ public final class HomeConfig {
};
}
- /**
+ public static class AuthConfig implements Parcelable {
+ private final String mMessageText;
+ private final String mButtonText;
+ private final String mImageUrl;
+
+ private static final String JSON_KEY_MESSAGE_TEXT = "messageText";
+ private static final String JSON_KEY_BUTTON_TEXT = "buttonText";
+ private static final String JSON_KEY_IMAGE_URL = "imageUrl";
+
+ public AuthConfig(JSONObject json) throws JSONException, IllegalArgumentException {
+ mMessageText = json.optString(JSON_KEY_MESSAGE_TEXT);
+ mButtonText = json.optString(JSON_KEY_BUTTON_TEXT);
+ mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public AuthConfig(Parcel in) {
+ mMessageText = in.readString();
+ mButtonText = in.readString();
+ mImageUrl = in.readString();
+
+ validate();
+ }
+
+ public AuthConfig(AuthConfig authConfig) {
+ mMessageText = authConfig.mMessageText;
+ mButtonText = authConfig.mButtonText;
+ mImageUrl = authConfig.mImageUrl;
+
+ validate();
+ }
+
+ public AuthConfig(String messageText, String buttonText, String imageUrl) {
+ mMessageText = messageText;
+ mButtonText = buttonText;
+ mImageUrl = imageUrl;
+
+ validate();
+ }
+
+ private void validate() {
+ if (mMessageText == null) {
+ throw new IllegalArgumentException("Can't create AuthConfig with null message text");
+ }
+
+ if (mButtonText == null) {
+ throw new IllegalArgumentException("Can't create AuthConfig with null button text");
+ }
+ }
+
+ public String getMessageText() {
+ return mMessageText;
+ }
+
+ public String getButtonText() {
+ return mButtonText;
+ }
+
+ public String getImageUrl() {
+ return mImageUrl;
+ }
+
+ public JSONObject toJSON() throws JSONException {
+ final JSONObject json = new JSONObject();
+
+ json.put(JSON_KEY_MESSAGE_TEXT, mMessageText);
+ json.put(JSON_KEY_BUTTON_TEXT, mButtonText);
+ json.put(JSON_KEY_IMAGE_URL, mImageUrl);
+
+ return json;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mMessageText);
+ dest.writeString(mButtonText);
+ dest.writeString(mImageUrl);
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public AuthConfig createFromParcel(final Parcel in) {
+ return new AuthConfig(in);
+ }
+
+ @Override
+ public AuthConfig[] newArray(final int size) {
+ return new AuthConfig[size];
+ }
+ };
+ }
+ /**
* Immutable representation of the current state of {@code HomeConfig}.
* This is what HomeConfig returns from a load() call and takes as
* input to save a new state.
diff --git a/mobile/android/base/home/HomeFragment.java b/mobile/android/base/home/HomeFragment.java
index 065ecc7ff85b..2e66e22d7ec2 100644
--- a/mobile/android/base/home/HomeFragment.java
+++ b/mobile/android/base/home/HomeFragment.java
@@ -221,15 +221,17 @@ abstract class HomeFragment extends Fragment {
protected abstract void load();
+ protected boolean canLoad() {
+ return (mCanLoadHint && isVisible() && getUserVisibleHint());
+ }
+
protected void loadIfVisible() {
- if (!mCanLoadHint || !isVisible() || !getUserVisibleHint()) {
+ if (!canLoad() || mIsLoaded) {
return;
}
- if (!mIsLoaded) {
- load();
- mIsLoaded = true;
- }
+ load();
+ mIsLoaded = true;
}
private static class RemoveBookmarkTask extends UiAsyncTask {
diff --git a/mobile/android/base/home/PanelAuthCache.java b/mobile/android/base/home/PanelAuthCache.java
new file mode 100644
index 000000000000..848dedf42d8d
--- /dev/null
+++ b/mobile/android/base/home/PanelAuthCache.java
@@ -0,0 +1,81 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.home;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+/**
+ * Cache used to store authentication state of dynamic panels. The values
+ * in this cache are set in JS through the Home.panels API.
+ *
+ * {@code DynamicPanel} uses this cache to determine whether or not to
+ * show authentication UI for dynamic panels, including listening for
+ * changes in authentication state.
+ */
+class PanelAuthCache {
+ private static final String LOGTAG = "GeckoPanelAuthCache";
+
+ // Keep this in sync with the constant defined in Home.jsm
+ private static final String PREFS_PANEL_AUTH_PREFIX = "home_panels_auth_";
+
+ private final Context mContext;
+ private SharedPrefsListener mSharedPrefsListener;
+ private OnChangeListener mChangeListener;
+
+ public interface OnChangeListener {
+ public void onChange(String panelId, boolean isAuthenticated);
+ }
+
+ public PanelAuthCache(Context context) {
+ mContext = context;
+ }
+
+ private SharedPreferences getSharedPreferences() {
+ return PreferenceManager.getDefaultSharedPreferences(mContext);
+ }
+
+ private String getPanelAuthKey(String panelId) {
+ return PREFS_PANEL_AUTH_PREFIX + panelId;
+ }
+
+ public boolean isAuthenticated(String panelId) {
+ final SharedPreferences prefs = getSharedPreferences();
+ return prefs.getBoolean(getPanelAuthKey(panelId), false);
+ }
+
+ public void setOnChangeListener(OnChangeListener listener) {
+ final SharedPreferences prefs = getSharedPreferences();
+
+ if (mChangeListener != null) {
+ prefs.unregisterOnSharedPreferenceChangeListener(mSharedPrefsListener);
+ mSharedPrefsListener = null;
+ }
+
+ mChangeListener = listener;
+
+ if (mChangeListener != null) {
+ mSharedPrefsListener = new SharedPrefsListener();
+ prefs.registerOnSharedPreferenceChangeListener(mSharedPrefsListener);
+ }
+ }
+
+ private class SharedPrefsListener implements OnSharedPreferenceChangeListener {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (key.startsWith(PREFS_PANEL_AUTH_PREFIX)) {
+ final String panelId = key.substring(PREFS_PANEL_AUTH_PREFIX.length());
+ final boolean isAuthenticated = prefs.getBoolean(key, false);
+
+ Log.d(LOGTAG, "Auth state changed: panelId=" + panelId + ", isAuthenticated=" + isAuthenticated);
+ mChangeListener.onChange(panelId, isAuthenticated);
+ }
+ }
+ }
+}
diff --git a/mobile/android/base/home/PanelAuthLayout.java b/mobile/android/base/home/PanelAuthLayout.java
new file mode 100644
index 000000000000..870939a72cf5
--- /dev/null
+++ b/mobile/android/base/home/PanelAuthLayout.java
@@ -0,0 +1,64 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.home;
+
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.home.HomeConfig.AuthConfig;
+import org.mozilla.gecko.home.HomeConfig.PanelConfig;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.squareup.picasso.Picasso;
+
+class PanelAuthLayout extends LinearLayout {
+
+ public PanelAuthLayout(Context context, PanelConfig panelConfig) {
+ super(context);
+
+ final AuthConfig authConfig = panelConfig.getAuthConfig();
+ if (authConfig == null) {
+ throw new IllegalStateException("Can't create PanelAuthLayout without a valid AuthConfig");
+ }
+
+ setOrientation(LinearLayout.VERTICAL);
+ LayoutInflater.from(context).inflate(R.layout.panel_auth_layout, this);
+
+ final TextView messageView = (TextView) findViewById(R.id.message);
+ messageView.setText(authConfig.getMessageText());
+
+ final Button buttonView = (Button) findViewById(R.id.button);
+ buttonView.setText(authConfig.getButtonText());
+
+ final String panelId = panelConfig.getId();
+ buttonView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomePanels:Authenticate", panelId));
+ }
+ });
+
+ final ImageView imageView = (ImageView) findViewById(R.id.image);
+ final String imageUrl = authConfig.getImageUrl();
+
+ if (TextUtils.isEmpty(imageUrl)) {
+ // Use a default image if an image URL isn't specified.
+ imageView.setImageResource(R.drawable.icon_home_empty_firefox);
+ } else {
+ Picasso.with(getContext())
+ .load(imageUrl)
+ .into(imageView);
+ }
+ }
+}
diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build
index bbae46ce4cd5..3bd6f785230b 100644
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -248,6 +248,8 @@ gbjar.sources += [
'home/LastTabsPanel.java',
'home/MostRecentPanel.java',
'home/MultiTypeCursorAdapter.java',
+ 'home/PanelAuthCache.java',
+ 'home/PanelAuthLayout.java',
'home/PanelBackItemView.java',
'home/PanelGridView.java',
'home/PanelItemView.java',
diff --git a/mobile/android/base/resources/drawable/panel_auth_button.xml b/mobile/android/base/resources/drawable/panel_auth_button.xml
new file mode 100644
index 000000000000..4ee2a094a1eb
--- /dev/null
+++ b/mobile/android/base/resources/drawable/panel_auth_button.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/base/resources/layout/panel_auth_layout.xml b/mobile/android/base/resources/layout/panel_auth_layout.xml
new file mode 100644
index 000000000000..d70754827c69
--- /dev/null
+++ b/mobile/android/base/resources/layout/panel_auth_layout.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index 2c5bcf9e0497..f777cb79977d 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -131,7 +131,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
// Lazily-loaded JS modules that use observer notifications
[
- ["Home", ["HomePanels:Get"], "resource://gre/modules/Home.jsm"],
+ ["Home", ["HomePanels:Get", "HomePanels:Authenticate"], "resource://gre/modules/Home.jsm"],
].forEach(module => {
let [name, notifications, resource] = module;
XPCOMUtils.defineLazyModuleGetter(this, name, resource);
diff --git a/mobile/android/locales/en-US/chrome/browser.properties b/mobile/android/locales/en-US/chrome/browser.properties
index 7ad083362362..f2498754faa4 100644
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -330,10 +330,3 @@ browser.menu.context.video = Video
browser.menu.context.audio = Audio
browser.menu.context.tel = Phone
browser.menu.context.mailto = Mail
-#Tabs in context menus
-browser.menu.context.default = Link
-browser.menu.context.img = Image
-browser.menu.context.video = Video
-browser.menu.context.audio = Audio
-browser.menu.context.tel = Phone
-browser.menu.context.mailto = Mail
diff --git a/mobile/android/modules/Home.jsm b/mobile/android/modules/Home.jsm
index b036021c894d..272730a7a379 100644
--- a/mobile/android/modules/Home.jsm
+++ b/mobile/android/modules/Home.jsm
@@ -13,6 +13,9 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SharedPreferences.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
+// Keep this in sync with the constant defined in PanelAuthCache.java
+const PREFS_PANEL_AUTH_PREFIX = "home_panels_auth_";
+
// See bug 915424
function resolveGeckoURI(aURI) {
if (!aURI)
@@ -154,20 +157,10 @@ let HomeBanner = (function () {
});
})();
-function Panel(id, options) {
- this.id = id;
- this.title = options.title;
-
- if ("layout" in options)
- this.layout = options.layout;
-
- if ("views" in options)
- this.views = options.views;
-}
-
// We need this function to have access to the HomePanels
// private members without leaking it outside Home.jsm.
let handlePanelsGet;
+let handlePanelsAuthenticate;
let HomePanels = (function () {
// Holds the current set of registered panels that can be
@@ -201,23 +194,26 @@ let HomePanels = (function () {
INTENT: "intent"
});
- let _generatePanel = function(id) {
- let panel = new Panel(id, _registeredPanels[id]());
+ function Panel(id, options) {
+ this.id = id;
+ this.title = options.title;
+ this.layout = options.layout;
+ this.views = options.views;
- if (!panel.id || !panel.title) {
+ if (!this.id || !this.title) {
throw "Home.panels: Can't create a home panel without an id and title!";
}
- if (!panel.layout) {
+ if (!this.layout) {
// Use FRAME layout by default
- panel.layout = Layout.FRAME;
- } else if (!_valueExists(Layout, panel.layout)) {
- throw "Home.panels: Invalid layout for panel: panel.id = " + panel.id + ", panel.layout =" + panel.layout;
+ this.layout = Layout.FRAME;
+ } else if (!_valueExists(Layout, this.layout)) {
+ throw "Home.panels: Invalid layout for panel: panel.id = " + this.id + ", panel.layout =" + this.layout;
}
- for (let view of panel.views) {
+ for (let view of this.views) {
if (!_valueExists(View, view.type)) {
- throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
+ throw "Home.panels: Invalid view type: panel.id = " + this.id + ", view.type = " + view.type;
}
if (!view.itemType) {
@@ -229,22 +225,44 @@ let HomePanels = (function () {
view.itemType = Item.IMAGE;
}
} else if (!_valueExists(Item, view.itemType)) {
- throw "Home.panels: Invalid item type: panel.id = " + panel.id + ", view.itemType = " + view.itemType;
+ throw "Home.panels: Invalid item type: panel.id = " + this.id + ", view.itemType = " + view.itemType;
}
if (!view.itemHandler) {
// Use BROWSER item handler by default
view.itemHandler = ItemHandler.BROWSER;
} else if (!_valueExists(ItemHandler, view.itemHandler)) {
- throw "Home.panels: Invalid item handler: panel.id = " + panel.id + ", view.itemHandler = " + view.itemHandler;
+ throw "Home.panels: Invalid item handler: panel.id = " + this.id + ", view.itemHandler = " + view.itemHandler;
}
if (!view.dataset) {
- throw "Home.panels: No dataset provided for view: panel.id = " + panel.id + ", view.type = " + view.type;
+ throw "Home.panels: No dataset provided for view: panel.id = " + this.id + ", view.type = " + view.type;
}
}
- return panel;
+ if (options.authHandler) {
+ if (!options.authHandler.messageText) {
+ throw "Home.panels: Invalid authHandler messageText: panel.id = " + this.id;
+ }
+ if (!options.authHandler.buttonText) {
+ throw "Home.panels: Invalid authHandler buttonText: panel.id = " + this.id;
+ }
+
+ this.authConfig = {
+ messageText: options.authHandler.messageText,
+ buttonText: options.authHandler.buttonText
+ };
+
+ // Include optional image URL if it is specified.
+ if (options.authHandler.imageUrl) {
+ this.authConfig.imageUrl = options.authHandler.imageUrl;
+ }
+ }
+ }
+
+ let _generatePanel = function(id) {
+ let options = _registeredPanels[id]();
+ return new Panel(id, options);
};
handlePanelsGet = function(data) {
@@ -270,6 +288,18 @@ let HomePanels = (function () {
});
};
+ handlePanelsAuthenticate = function(id) {
+ // Generate panel options to get auth handler.
+ let options = _registeredPanels[id]();
+ if (!options.authHandler) {
+ throw "Home.panels: Invalid authHandler for panel.id = " + id;
+ }
+ if (!options.authHandler.authenticate || typeof options.authHandler.authenticate !== "function") {
+ throw "Home.panels: Invalid authHandler authenticate function: panel.id = " + this.id;
+ }
+ options.authHandler.authenticate();
+ };
+
// Helper function used to see if a value is in an object.
let _valueExists = function(obj, value) {
for (let key in obj) {
@@ -336,6 +366,14 @@ let HomePanels = (function () {
type: "HomePanels:Update",
panel: _generatePanel(id)
});
+ },
+
+ setAuthenticated: function(id, isAuthenticated) {
+ _assertPanelExists(id);
+
+ let authKey = PREFS_PANEL_AUTH_PREFIX + id;
+ let sharedPrefs = new SharedPreferences();
+ sharedPrefs.setBoolPref(authKey, isAuthenticated);
}
});
})();
@@ -351,6 +389,9 @@ this.Home = Object.freeze({
case "HomePanels:Get":
handlePanelsGet(JSON.parse(data));
break;
+ case "HomePanels:Authenticate":
+ handlePanelsAuthenticate(data);
+ break;
}
}
});
diff --git a/toolkit/devtools/server/actors/device.js b/toolkit/devtools/server/actors/device.js
index b8199929d9fd..acbae1a7a835 100644
--- a/toolkit/devtools/server/actors/device.js
+++ b/toolkit/devtools/server/actors/device.js
@@ -6,7 +6,7 @@ const {Cc, Ci, Cu, CC} = require("chrome");
const Services = require("Services");
const protocol = require("devtools/server/protocol");
const {method, RetVal} = protocol;
-const promise = require("sdk/core/promise");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const {LongStringActor} = require("devtools/server/actors/string");
Cu.import("resource://gre/modules/PermissionsTable.jsm")
diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js
index 4b97469ddd31..6ba65685da86 100644
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -55,7 +55,7 @@ const Services = require("Services");
const protocol = require("devtools/server/protocol");
const {Arg, Option, method, RetVal, types} = protocol;
const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
-const promise = require("sdk/core/promise");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const object = require("sdk/util/object");
const events = require("sdk/event/core");
const {Unknown} = require("sdk/platform/xpcom");
diff --git a/toolkit/devtools/server/actors/preference.js b/toolkit/devtools/server/actors/preference.js
index 719ae341c7ad..b534a267ad3d 100644
--- a/toolkit/devtools/server/actors/preference.js
+++ b/toolkit/devtools/server/actors/preference.js
@@ -5,7 +5,7 @@
const {Cc, Ci, Cu, CC} = require("chrome");
const protocol = require("devtools/server/protocol");
const {Arg, method, RetVal} = protocol;
-const promise = require("sdk/core/promise");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
Cu.import("resource://gre/modules/Services.jsm");
Cu.import('resource://gre/modules/devtools/dbg-server.jsm');
diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js
index b2e6aa30194a..4c74540fb613 100644
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -3703,8 +3703,10 @@ DebuggerServer.ObjectActorPreviewers.Object = [
let url;
if (aRawObj instanceof Ci.nsIDOMWindow && aRawObj.location) {
url = aRawObj.location.href;
- } else {
+ } else if (aRawObj.href) {
url = aRawObj.href;
+ } else {
+ return false;
}
aGrip.preview = {
@@ -4726,7 +4728,7 @@ update(AddonThreadActor.prototype, {
return id.value === this.addonID;
}
catch (e) {
- console.log("Unexpected URI " + uridescriptor.value);
+ DevToolsUtils.reportException("AddonThreadActor.prototype._checkGlobal", e);
}
}
diff --git a/toolkit/devtools/server/actors/string.js b/toolkit/devtools/server/actors/string.js
index 8fc1eda94ec6..0e20b1c26cbf 100644
--- a/toolkit/devtools/server/actors/string.js
+++ b/toolkit/devtools/server/actors/string.js
@@ -7,7 +7,7 @@
let {Cu} = require("chrome");
let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
-let promise = require("sdk/core/promise");
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
let {Class} = require("sdk/core/heritage");
let protocol = require("devtools/server/protocol");
diff --git a/toolkit/devtools/server/actors/styleeditor.js b/toolkit/devtools/server/actors/styleeditor.js
index 4a2df008ddab..c32e99a69137 100644
--- a/toolkit/devtools/server/actors/styleeditor.js
+++ b/toolkit/devtools/server/actors/styleeditor.js
@@ -12,7 +12,7 @@ Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
-const promise = require("sdk/core/promise");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const events = require("sdk/event/core");
const protocol = require("devtools/server/protocol");
const {Arg, Option, method, RetVal, types} = protocol;
diff --git a/toolkit/devtools/server/actors/styles.js b/toolkit/devtools/server/actors/styles.js
index 68e7b97b8a91..32485cf96637 100644
--- a/toolkit/devtools/server/actors/styles.js
+++ b/toolkit/devtools/server/actors/styles.js
@@ -4,9 +4,9 @@
"use strict";
-const {Cc, Ci} = require("chrome");
+const {Cc, Ci, Cu} = require("chrome");
const Services = require("Services");
-const promise = require("sdk/core/promise");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const protocol = require("devtools/server/protocol");
const {Arg, Option, method, RetVal, types} = protocol;
const events = require("sdk/event/core");
diff --git a/toolkit/devtools/server/actors/stylesheets.js b/toolkit/devtools/server/actors/stylesheets.js
index 33b19b42871c..f201a5b4cb9e 100644
--- a/toolkit/devtools/server/actors/stylesheets.js
+++ b/toolkit/devtools/server/actors/stylesheets.js
@@ -13,7 +13,7 @@ Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
Cu.import("resource://gre/modules/Task.jsm");
-const promise = require("sdk/core/promise");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const events = require("sdk/event/core");
const protocol = require("devtools/server/protocol");
const {Arg, Option, method, RetVal, types} = protocol;
@@ -129,7 +129,11 @@ let StyleSheetsActor = protocol.ActorClass({
// Recursively handle style sheets of the documents in iframes.
for (let iframe of doc.getElementsByTagName("iframe")) {
- documents.push(iframe.contentDocument);
+ if (iframe.contentDocument) {
+ // Sometimes, iframes don't have any document, like the
+ // one that are over deeply nested (bug 285395)
+ documents.push(iframe.contentDocument);
+ }
}
}
throw new Task.Result(actors);
diff --git a/toolkit/devtools/server/actors/webbrowser.js b/toolkit/devtools/server/actors/webbrowser.js
index 15c0260b4b33..9c4d14d80db0 100644
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -6,7 +6,8 @@
"use strict";
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let {Cu} = require("chrome");
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
/**
@@ -1125,6 +1126,7 @@ BrowserAddonActor.prototype = {
return {
actor: this.actorID,
id: this.id,
+ name: this._addon.name,
url: this.url
};
},
diff --git a/toolkit/devtools/server/child.js b/toolkit/devtools/server/child.js
index de759462e927..4d3826dcc4e5 100644
--- a/toolkit/devtools/server/child.js
+++ b/toolkit/devtools/server/child.js
@@ -9,6 +9,7 @@ let chromeGlobal = this;
// Encapsulate in its own scope to allows loading this frame script
// more than once.
(function () {
+ let Cu = Components.utils;
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
diff --git a/toolkit/devtools/server/main.js b/toolkit/devtools/server/main.js
index c8b431fdc1fe..7a36d1e4b09d 100644
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -39,6 +39,7 @@ Cu.import("resource://gre/modules/reflect.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
@@ -49,7 +50,9 @@ function loadSubScript(aURL)
.getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript(aURL, this);
} catch(e) {
- let errorStr = "Error loading: " + aURL + ": " + e + " - " + e.stack + "\n";
+ let errorStr = "Error loading: " + aURL + ":\n" +
+ (e.fileName ? "at " + e.fileName + " : " + e.lineNumber + "\n" : "") +
+ e + " - " + e.stack + "\n";
dump(errorStr);
Cu.reportError(errorStr);
throw e;