diff --git a/addon-sdk/source/examples/reading-data/lib/main.js b/addon-sdk/source/examples/reading-data/lib/main.js
index 71c8be6b6ad..468a497b12e 100644
--- a/addon-sdk/source/examples/reading-data/lib/main.js
+++ b/addon-sdk/source/examples/reading-data/lib/main.js
@@ -1,10 +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";
var self = require("sdk/self");
-var panels = require("sdk/panel");
-var widgets = require("sdk/widget");
+var { Panel } = require("sdk/panel");
+var { ToggleButton } = require("sdk/ui");
function replaceMom(html) {
return html.replace("World", "Mom");
@@ -21,20 +22,18 @@ exports.main = function(options, callbacks) {
helloHTML = replaceMom(helloHTML);
// ... and then create a panel that displays it.
- var myPanel = panels.Panel({
- contentURL: "data:text/html," + helloHTML
+ var myPanel = Panel({
+ contentURL: "data:text/html," + helloHTML,
+ onHide: handleHide
});
- // Load the URL of the sample image.
- var iconURL = self.data.url("mom.png");
-
// Create a widget that displays the image. We'll attach the panel to it.
// When you click the widget, the panel will pop up.
- widgets.Widget({
+ var button = ToggleButton({
id: "test-widget",
label: "Mom",
- contentURL: iconURL,
- panel: myPanel
+ icon: './mom.png',
+ onChange: handleChange
});
// If you run cfx with --static-args='{"quitWhenDone":true}' this program
@@ -42,3 +41,13 @@ exports.main = function(options, callbacks) {
if (options.staticArgs.quitWhenDone)
callbacks.quit();
}
+
+function handleChange(state) {
+ if (state.checked) {
+ myPanel.show({ position: button });
+ }
+}
+
+function handleHide() {
+ button.state('window', { checked: false });
+}
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 1e6a18e46bb..4e85f49de4a 100644
--- a/addon-sdk/source/examples/reading-data/tests/test-main.js
+++ b/addon-sdk/source/examples/reading-data/tests/test-main.js
@@ -3,9 +3,6 @@
* 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");
@@ -26,4 +23,3 @@ 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/lib/main.js b/addon-sdk/source/examples/reddit-panel/lib/main.js
index 95da781ca95..0d25ba65a43 100644
--- a/addon-sdk/source/examples/reddit-panel/lib/main.js
+++ b/addon-sdk/source/examples/reddit-panel/lib/main.js
@@ -1,26 +1,34 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
-var data = require("sdk/self").data;
+var { data } = require("sdk/self");
+var { ToggleButton } = require("sdk/ui");
+
+var base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
+ "AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
+ "N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
+ "bWRR9AAAAABJRU5ErkJggg%3D%3D";
var reddit_panel = require("sdk/panel").Panel({
width: 240,
height: 320,
contentURL: "http://www.reddit.com/.mobile?keep_extension=True",
contentScriptFile: [data.url("jquery-1.4.4.min.js"),
- data.url("panel.js")]
+ data.url("panel.js")],
+ onHide: handleHide
});
reddit_panel.port.on("click", function(url) {
require("sdk/tabs").open(url);
});
-require("sdk/widget").Widget({
+let button = ToggleButton({
id: "open-reddit-btn",
label: "Reddit",
- contentURL: "http://www.reddit.com/static/favicon.ico",
- panel: reddit_panel
+ icon: base64png,
+ onChange: handleChange
});
exports.main = function(options, callbacks) {
@@ -29,3 +37,13 @@ exports.main = function(options, callbacks) {
if (options.staticArgs.quitWhenDone)
callbacks.quit();
};
+
+function handleChange(state) {
+ if (state.checked) {
+ reddit_panel.show({ position: button });
+ }
+}
+
+function handleHide() {
+ button.state('window', { checked: false });
+}
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 f13003e7654..6d916d25c17 100644
--- a/addon-sdk/source/examples/reddit-panel/tests/test-main.js
+++ b/addon-sdk/source/examples/reddit-panel/tests/test-main.js
@@ -3,9 +3,6 @@
* 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");
@@ -23,4 +20,3 @@ 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/content/content-worker.js b/addon-sdk/source/lib/sdk/content/content-worker.js
index 23ddd5cafec..0d7d675c285 100644
--- a/addon-sdk/source/lib/sdk/content/content-worker.js
+++ b/addon-sdk/source/lib/sdk/content/content-worker.js
@@ -107,7 +107,9 @@ const ContentWorker = Object.freeze({
error: pipe.emit.bind(null, "console", "error"),
debug: pipe.emit.bind(null, "console", "debug"),
exception: pipe.emit.bind(null, "console", "exception"),
- trace: pipe.emit.bind(null, "console", "trace")
+ trace: pipe.emit.bind(null, "console", "trace"),
+ time: pipe.emit.bind(null, "console", "time"),
+ timeEnd: pipe.emit.bind(null, "console", "timeEnd")
});
},
diff --git a/addon-sdk/source/lib/sdk/content/mod.js b/addon-sdk/source/lib/sdk/content/mod.js
index 6ce0c7608d3..f46ac49b1f0 100644
--- a/addon-sdk/source/lib/sdk/content/mod.js
+++ b/addon-sdk/source/lib/sdk/content/mod.js
@@ -31,6 +31,9 @@ let detachFrom = method("detatchFrom");
exports.detachFrom = detachFrom;
function attach(modification, target) {
+ if (!modification)
+ return;
+
let window = getTargetWindow(target);
attachTo(modification, window);
@@ -42,6 +45,9 @@ function attach(modification, target) {
exports.attach = attach;
function detach(modification, target) {
+ if (!modification)
+ return;
+
if (target) {
let window = getTargetWindow(target);
detachFrom(modification, window);
diff --git a/addon-sdk/source/lib/sdk/loader/sandbox.js b/addon-sdk/source/lib/sdk/loader/sandbox.js
index 3d715ac178d..2edb71f90ce 100644
--- a/addon-sdk/source/lib/sdk/loader/sandbox.js
+++ b/addon-sdk/source/lib/sdk/loader/sandbox.js
@@ -67,3 +67,8 @@ function load(sandbox, uri) {
}
}
exports.load = load;
+
+/**
+ * Forces the given `sandbox` to be freed immediately.
+ */
+exports.nuke = Cu.nukeSandbox
diff --git a/addon-sdk/source/lib/sdk/page-mod.js b/addon-sdk/source/lib/sdk/page-mod.js
index b13a176f9df..f706b1e4c10 100644
--- a/addon-sdk/source/lib/sdk/page-mod.js
+++ b/addon-sdk/source/lib/sdk/page-mod.js
@@ -201,6 +201,10 @@ function createWorker (mod, window) {
contentScript: mod.contentScript,
contentScriptFile: mod.contentScriptFile,
contentScriptOptions: mod.contentScriptOptions,
+ // Bug 980468: Syntax errors from scripts can happen before the worker
+ // can set up an error handler. They are per-mod rather than per-worker
+ // so are best handled at the mod level.
+ onError: (e) => emit(mod, 'error', e)
});
workers.set(mod, worker);
pipe(worker, mod);
diff --git a/addon-sdk/source/lib/sdk/panel.js b/addon-sdk/source/lib/sdk/panel.js
index 274c2c50a62..5071ea828c4 100644
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -18,7 +18,7 @@ const { isPrivateBrowsingSupported } = require('./self');
const { isWindowPBSupported } = require('./private-browsing/utils');
const { Class } = require("./core/heritage");
const { merge } = require("./util/object");
-const { WorkerHost, detach, attach, destroy } = require("./content/utils");
+const { WorkerHost } = require("./content/utils");
const { Worker } = require("./content/worker");
const { Disposable } = require("./core/disposable");
const { WeakReference } = require('./core/reference');
@@ -34,6 +34,8 @@ const { getNodeView, getActiveView } = require("./view/core");
const { isNil, isObject, isNumber } = require("./lang/type");
const { getAttachEventType } = require("./content/utils");
const { number, boolean, object } = require('./deprecated/api-utils');
+const { Style } = require("./stylesheet/style");
+const { attach, detach } = require("./content/mod");
let isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
some(value => isNumber(value) && !isNaN(value));
@@ -63,7 +65,16 @@ let displayContract = contract({
position: position
});
-let panelContract = contract(merge({}, displayContract.rules, loaderContract.rules));
+let panelContract = contract(merge({
+ // contentStyle* / contentScript* are sharing the same validation constraints,
+ // so they can be mostly reused, except for the messages.
+ contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
+ msg: 'The `contentStyle` option must be a string or an array of strings.'
+ }),
+ contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
+ msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
+ })
+}, displayContract.rules, loaderContract.rules));
function isDisposed(panel) !views.has(panel);
@@ -72,12 +83,13 @@ let panels = new WeakMap();
let models = new WeakMap();
let views = new WeakMap();
let workers = new WeakMap();
+let styles = new WeakMap();
-function viewFor(panel) views.get(panel)
-function modelFor(panel) models.get(panel)
-function panelFor(view) panels.get(view)
-function workerFor(panel) workers.get(panel)
-
+const viewFor = (panel) => views.get(panel);
+const modelFor = (panel) => models.get(panel);
+const panelFor = (view) => panels.get(view);
+const workerFor = (panel) => workers.get(panel);
+const styleFor = (panel) => styles.get(panel);
// Utility function takes `panel` instance and makes sure it will be
// automatically hidden as soon as other panel is shown.
@@ -125,6 +137,12 @@ const Panel = Class({
}, panelContract(options));
models.set(this, model);
+ if (model.contentStyle || model.contentStyleFile) {
+ styles.set(this, Style({
+ uri: model.contentStyleFile,
+ source: model.contentStyle
+ }));
+ }
// Setup view
let view = domPanel.make();
@@ -148,7 +166,8 @@ const Panel = Class({
this.hide();
off(this);
- destroy(workerFor(this));
+ workerFor(this).destroy();
+ detach(styleFor(this));
domPanel.dispose(viewFor(this));
@@ -177,7 +196,7 @@ const Panel = Class({
domPanel.setURL(viewFor(this), model.contentURL);
// Detach worker so that messages send will be queued until it's
// reatached once panel content is ready.
- detach(workerFor(this));
+ workerFor(this).detach();
},
/* Public API: Panel.isShowing */
@@ -262,12 +281,25 @@ let hides = filter(panelEvents, ({type}) => type === "popuphidden");
let ready = filter(panelEvents, ({type, target}) =>
getAttachEventType(modelFor(panelFor(target))) === type);
+// Styles should be always added as soon as possible, and doesn't makes them
+// depends on `contentScriptWhen`
+let start = filter(panelEvents, ({type}) => type === "document-element-inserted");
+
// Forward panel show / hide events to panel's own event listeners.
on(shows, "data", ({target}) => emit(panelFor(target), "show"));
on(hides, "data", ({target}) => emit(panelFor(target), "hide"));
-on(ready, "data", function({target}) {
- let worker = workerFor(panelFor(target));
- attach(worker, domPanel.getContentDocument(target).defaultView);
+on(ready, "data", ({target}) => {
+ let panel = panelFor(target);
+ let window = domPanel.getContentDocument(target).defaultView;
+
+ workerFor(panel).attach(window);
+});
+
+on(start, "data", ({target}) => {
+ let panel = panelFor(target);
+ let window = domPanel.getContentDocument(target).defaultView;
+
+ attach(styleFor(panel), window);
});
diff --git a/addon-sdk/source/lib/sdk/system.js b/addon-sdk/source/lib/sdk/system.js
index f97522e14fb..afef91bab89 100644
--- a/addon-sdk/source/lib/sdk/system.js
+++ b/addon-sdk/source/lib/sdk/system.js
@@ -109,17 +109,21 @@ exports.pathFor = function pathFor(id) {
*/
exports.platform = runtime.OS.toLowerCase();
+const [, architecture, compiler] = runtime.XPCOMABI ?
+ runtime.XPCOMABI.match(/^([^-]*)-(.*)$/) :
+ [, null, null];
+
/**
* What processor architecture you're running on:
* `'arm', 'ia32', or 'x64'`.
*/
-exports.architecture = runtime.XPCOMABI.split('_')[0];
+exports.architecture = architecture;
/**
* What compiler used for build:
* `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
*/
-exports.compiler = runtime.XPCOMABI.split('_')[1];
+exports.compiler = compiler;
/**
* The application's build ID/date, for example "2004051604".
diff --git a/addon-sdk/source/lib/sdk/system/events.js b/addon-sdk/source/lib/sdk/system/events.js
index 0e6ad8fa75c..9e2e826088b 100644
--- a/addon-sdk/source/lib/sdk/system/events.js
+++ b/addon-sdk/source/lib/sdk/system/events.js
@@ -8,12 +8,13 @@ module.metadata = {
'stability': 'unstable'
};
-const { Cc, Ci } = require('chrome');
+const { Cc, Ci, Cu } = require('chrome');
const { Unknown } = require('../platform/xpcom');
const { Class } = require('../core/heritage');
const { ns } = require('../core/namespace');
const { addObserver, removeObserver, notifyObservers } =
Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
+const unloadSubject = require('@loader/unload');
const Subject = Class({
extends: Unknown,
@@ -94,6 +95,10 @@ function on(type, listener, strong) {
let observer = Observer(listener);
observers[type] = observer;
addObserver(observer, type, weak);
+ // WeakRef gymnastics to remove all alive observers on unload
+ let ref = Cu.getWeakReference(observer);
+ weakRefs.set(observer, ref);
+ stillAlive.set(ref, type);
}
}
exports.on = on;
@@ -120,6 +125,31 @@ function off(type, listener) {
let observer = observers[type];
delete observers[type];
removeObserver(observer, type);
+ stillAlive.delete(weakRefs.get(observer));
}
}
exports.off = off;
+
+// must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115
+let weakRefs = new WeakMap();
+
+// and we're out of beta, we're releasing on time!
+let stillAlive = new Map();
+
+on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
+ // using logic from ./unload, to avoid a circular module reference
+ if (subject.wrappedJSObject === unloadSubject) {
+ off('sdk:loader:destroy', onunload);
+
+ // don't bother
+ if (reason === 'shutdown')
+ return;
+
+ stillAlive.forEach( (type, ref) => {
+ let observer = ref.get();
+ if (observer)
+ removeObserver(observer, type);
+ })
+ }
+ // a strong reference
+}, true);
diff --git a/addon-sdk/source/lib/sdk/test/loader.js b/addon-sdk/source/lib/sdk/test/loader.js
index 26c956d9e01..71c4d363216 100644
--- a/addon-sdk/source/lib/sdk/test/loader.js
+++ b/addon-sdk/source/lib/sdk/test/loader.js
@@ -62,9 +62,11 @@ exports.LoaderWithHookedConsole = function (module, callback) {
error: hook.bind("error"),
debug: hook.bind("debug"),
exception: hook.bind("exception"),
+ time: hook.bind("time"),
+ timeEnd: hook.bind("timeEnd"),
__exposedProps__: {
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
- exception: "rw"
+ exception: "rw", time: "rw", timeEnd: "rw"
}
}
}),
@@ -105,9 +107,11 @@ exports.LoaderWithFilteredConsole = function (module, callback) {
error: hook.bind("error"),
debug: hook.bind("debug"),
exception: hook.bind("exception"),
+ time: hook.bind("time"),
+ timeEnd: hook.bind("timeEnd"),
__exposedProps__: {
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
- exception: "rw"
+ exception: "rw", time: "rw", timeEnd: "rw"
}
}
});
diff --git a/addon-sdk/source/lib/sdk/ui/button/contract.js b/addon-sdk/source/lib/sdk/ui/button/contract.js
index fa09a7c93f3..b6cd5e3c1d9 100644
--- a/addon-sdk/source/lib/sdk/ui/button/contract.js
+++ b/addon-sdk/source/lib/sdk/ui/button/contract.js
@@ -8,6 +8,7 @@ const { isLocalURL } = require('../../url');
const { isNil, isObject, isString } = require('../../lang/type');
const { required, either, string, boolean, object } = require('../../deprecated/api-utils');
const { merge } = require('../../util/object');
+const { freeze } = Object;
function isIconSet(icons) {
return Object.keys(icons).
@@ -16,6 +17,7 @@ function isIconSet(icons) {
let iconSet = {
is: either(object, string),
+ map: v => isObject(v) ? freeze(merge({}, v)) : v,
ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)),
msg: 'The option "icon" must be a local URL or an object with ' +
'numeric keys / local URL values pair.'
diff --git a/addon-sdk/source/lib/sdk/ui/sidebar.js b/addon-sdk/source/lib/sdk/ui/sidebar.js
index ade6e1146de..763e984fa41 100644
--- a/addon-sdk/source/lib/sdk/ui/sidebar.js
+++ b/addon-sdk/source/lib/sdk/ui/sidebar.js
@@ -253,9 +253,11 @@ const Sidebar = Class({
remove(sidebars, this);
// stop tracking windows
- internals.tracker.unload();
- internals.tracker = null;
+ if (internals.tracker) {
+ internals.tracker.unload();
+ }
+ internals.tracker = null;
internals.windowNS = null;
views.delete(this);
diff --git a/addon-sdk/source/test/fixtures/pagemod-css-include-file.css b/addon-sdk/source/test/fixtures/css-include-file.css
similarity index 100%
rename from addon-sdk/source/test/fixtures/pagemod-css-include-file.css
rename to addon-sdk/source/test/fixtures/css-include-file.css
diff --git a/addon-sdk/source/test/test-content-worker.js b/addon-sdk/source/test/test-content-worker.js
index fd7d7f87590..dbb514b6af9 100644
--- a/addon-sdk/source/test/test-content-worker.js
+++ b/addon-sdk/source/test/test-content-worker.js
@@ -382,8 +382,8 @@ exports["test:ensure console.xxx works in cs"] = WorkerTest(
let calls = [];
function onMessage(type, msg) {
assert.equal(type, msg,
- "console.xxx(\"xxx\"), i.e. message is equal to the " +
- "console method name we are calling");
+ "console.xxx(\"xxx\"), i.e. message is equal to the " +
+ "console method name we are calling");
calls.push(msg);
}
@@ -391,19 +391,23 @@ exports["test:ensure console.xxx works in cs"] = WorkerTest(
let worker = loader.require("sdk/content/worker").Worker({
window: browser.contentWindow,
contentScript: "new " + function WorkerScope() {
+ console.time("time");
console.log("log");
console.info("info");
console.warn("warn");
console.error("error");
console.debug("debug");
console.exception("exception");
+ console.timeEnd("timeEnd");
self.postMessage();
},
onMessage: function() {
// Ensure that console methods are called in the same execution order
+ const EXPECTED_CALLS = ["time", "log", "info", "warn", "error",
+ "debug", "exception", "timeEnd"];
assert.equal(JSON.stringify(calls),
- JSON.stringify(["log", "info", "warn", "error", "debug", "exception"]),
- "console has been called successfully, in the expected order");
+ JSON.stringify(EXPECTED_CALLS),
+ "console methods have been called successfully, in expected order");
done();
}
});
diff --git a/addon-sdk/source/test/test-page-mod.js b/addon-sdk/source/test/test-page-mod.js
index 29b51e9ab78..af8e1ca00da 100644
--- a/addon-sdk/source/test/test-page-mod.js
+++ b/addon-sdk/source/test/test-page-mod.js
@@ -957,7 +957,7 @@ exports.testPageModCss = function(assert, done) {
'data:text/html;charset=utf-8,
css test
', [{
include: ["*", "data:*"],
contentStyle: "div { height: 100px; }",
- contentStyleFile: data.url("pagemod-css-include-file.css")
+ contentStyleFile: data.url("css-include-file.css")
}],
function(win, done) {
let div = win.document.querySelector("div");
@@ -1531,4 +1531,32 @@ exports.testDetachOnUnload = function(assert, done) {
})
}
+exports.testSyntaxErrorInContentScript = function(assert, done) {
+ const url = "data:text/html;charset=utf-8,testSyntaxErrorInContentScript";
+ let hitError = null;
+ let attached = false;
+
+ testPageMod(assert, done, url, [{
+ include: url,
+ contentScript: 'console.log(23',
+
+ onAttach: function() {
+ attached = true;
+ },
+
+ onError: function(e) {
+ hitError = e;
+ }
+ }],
+
+ function(win, done) {
+ assert.ok(attached, "The worker was attached.");
+ assert.notStrictEqual(hitError, null, "The syntax error was reported.");
+ if (hitError)
+ assert.equal(hitError.name, "SyntaxError", "The error thrown should be a SyntaxError");
+ done();
+ }
+ );
+};
+
require('sdk/test').run(exports);
diff --git a/addon-sdk/source/test/test-panel.js b/addon-sdk/source/test/test-panel.js
index 6c2d21c88d5..479426060be 100644
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -25,6 +25,8 @@ const { URL } = require('sdk/url');
const fixtures = require('./fixtures')
const SVG_URL = fixtures.url('mofo_logo.SVG');
+const CSS_URL = fixtures.url('css-include-file.css');
+
const Isolate = fn => '(' + fn + ')()';
function ignorePassingDOMNodeWarning(type, message) {
@@ -974,6 +976,88 @@ exports['test panel can be constructed without any arguments'] = function (asser
assert.ok(true, "Creating a panel with no arguments does not throw");
};
+exports['test panel CSS'] = function(assert, done) {
+ const loader = Loader(module);
+ const { Panel } = loader.require('sdk/panel');
+
+ const { getActiveView } = loader.require('sdk/view/core');
+
+ const getContentWindow = panel =>
+ getActiveView(panel).querySelector('iframe').contentWindow;
+
+ let panel = Panel({
+ contentURL: 'data:text/html;charset=utf-8,' +
+ 'css test
',
+ contentStyle: 'div { height: 100px; }',
+ contentStyleFile: CSS_URL,
+ onShow: () => {
+ ready(getContentWindow(panel)).then(({ document }) => {
+ let div = document.querySelector('div');
+
+ assert.equal(div.clientHeight, 100, 'Panel contentStyle worked');
+ assert.equal(div.offsetHeight, 120, 'Panel contentStyleFile worked');
+
+ loader.unload();
+ done();
+ }).then(null, assert.fail);
+ }
+ });
+
+ panel.show();
+};
+
+exports['test panel CSS list'] = function(assert, done) {
+ const loader = Loader(module);
+ const { Panel } = loader.require('sdk/panel');
+
+ const { getActiveView } = loader.require('sdk/view/core');
+
+ const getContentWindow = panel =>
+ getActiveView(panel).querySelector('iframe').contentWindow;
+
+ let panel = Panel({
+ contentURL: 'data:text/html;charset=utf-8,' +
+ 'css test
',
+ contentStyleFile: [
+ // Highlight evaluation order in this list
+ "data:text/css;charset=utf-8,div { border: 1px solid black; }",
+ "data:text/css;charset=utf-8,div { border: 10px solid black; }",
+ // Highlight evaluation order between contentStylesheet & contentStylesheetFile
+ "data:text/css;charset=utf-8s,div { height: 1000px; }",
+ // Highlight precedence between the author and user style sheet
+ "data:text/css;charset=utf-8,div { width: 200px; max-width: 640px!important}",
+ ],
+ contentStyle: [
+ "div { height: 10px; }",
+ "div { height: 100px; }"
+ ],
+ onShow: () => {
+ ready(getContentWindow(panel)).then(({ window, document }) => {
+ let div = document.querySelector('div');
+ let style = window.getComputedStyle(div);
+
+ assert.equal(div.clientHeight, 100,
+ 'Panel contentStyle list is evaluated after contentStyleFile');
+
+ assert.equal(div.offsetHeight, 120,
+ 'Panel contentStyleFile list works');
+
+ assert.equal(style.width, '320px',
+ 'add-on author/page author stylesheet precedence works');
+
+ assert.equal(style.maxWidth, '480px',
+ 'add-on author/page author stylesheet !important precedence works');
+
+ loader.unload();
+ done();
+ }).then(null, assert.fail);
+ }
+ });
+
+ panel.show();
+};
+
+
if (isWindowPBSupported) {
exports.testGetWindow = function(assert, done) {
let activeWindow = getMostRecentBrowserWindow();
diff --git a/addon-sdk/source/test/test-sandbox.js b/addon-sdk/source/test/test-sandbox.js
index 9f5ee4746d6..d7f2e5ac6e5 100644
--- a/addon-sdk/source/test/test-sandbox.js
+++ b/addon-sdk/source/test/test-sandbox.js
@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-const { sandbox, load, evaluate } = require('sdk/loader/sandbox');
+const { sandbox, load, evaluate, nuke } = require('sdk/loader/sandbox');
const xulApp = require("sdk/system/xul-app");
const fixturesURI = module.uri.split('test-sandbox.js')[0] + 'fixtures/';
@@ -137,4 +137,30 @@ exports['test metadata'] = function(assert) {
let self = require('sdk/self');
}
+exports['test nuke sandbox'] = function(assert) {
+
+ let fixture = sandbox('http://example.com');
+ fixture.foo = 'foo';
+
+ let ref = evaluate(fixture, 'let a = {bar: "bar"}; a');
+
+ nuke(fixture);
+
+ assert.ok(Cu.isDeadWrapper(fixture), 'sandbox should be dead');
+
+ assert.throws(
+ () => fixture.foo,
+ /can't access dead object/,
+ 'property of nuked sandbox should not be accessible'
+ );
+
+ assert.ok(Cu.isDeadWrapper(ref), 'ref to object from sandbox should be dead');
+
+ assert.throws(
+ () => ref.bar,
+ /can't access dead object/,
+ 'object from nuked sandbox should not be alive'
+ );
+}
+
require('test').run(exports);
diff --git a/addon-sdk/source/test/test-system-events.js b/addon-sdk/source/test/test-system-events.js
index 64aa5493f9d..1e66d1ae9d2 100644
--- a/addon-sdk/source/test/test-system-events.js
+++ b/addon-sdk/source/test/test-system-events.js
@@ -119,6 +119,30 @@ exports["test listeners are GC-ed"] = function(assert, done) {
});
};
+exports["test alive listeners are removed on unload"] = function(assert) {
+ let receivedFromWeak = [];
+ let receivedFromStrong = [];
+ let loader = Loader(module);
+ let events = loader.require('sdk/system/events');
+
+ let type = 'test-alive-listeners-are-removed';
+ const handler = (event) => receivedFromStrong.push(event);
+ const weakHandler = (event) => receivedFromWeak.push(event);
+
+ events.on(type, handler, true);
+ events.on(type, weakHandler);
+
+ events.emit(type, { data: 1 });
+ assert.equal(receivedFromStrong.length, 1, "strong listener invoked");
+ assert.equal(receivedFromWeak.length, 1, "weak listener invoked");
+
+ loader.unload();
+ events.emit(type, { data: 2 });
+
+ assert.equal(receivedFromWeak.length, 1, "weak listener was removed");
+ assert.equal(receivedFromStrong.length, 1, "strong listener was removed");
+};
+
exports["test handle nsIObserverService notifications"] = function(assert) {
let ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
diff --git a/addon-sdk/source/test/test-system-runtime.js b/addon-sdk/source/test/test-system-runtime.js
index de47fcb133a..ed911bfdc6b 100644
--- a/addon-sdk/source/test/test-system-runtime.js
+++ b/addon-sdk/source/test/test-system-runtime.js
@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-var runtime = require("sdk/system/runtime");
+const runtime = require("sdk/system/runtime");
exports["test system runtime"] = function(assert) {
assert.equal(typeof(runtime.inSafeMode), "boolean",
@@ -14,7 +14,7 @@ exports["test system runtime"] = function(assert) {
"runtime.processType is a number");
assert.equal(typeof(runtime.widgetToolkit), "string",
"runtime.widgetToolkit is string");
- var XPCOMABI = typeof(runtime.XPCOMABI);
+ const XPCOMABI = runtime.XPCOMABI;
assert.ok(XPCOMABI === null || typeof(XPCOMABI) === "string",
"runtime.XPCOMABI is string or null if not supported by platform");
};
diff --git a/addon-sdk/source/test/test-system.js b/addon-sdk/source/test/test-system.js
new file mode 100644
index 00000000000..0461d98d141
--- /dev/null
+++ b/addon-sdk/source/test/test-system.js
@@ -0,0 +1,37 @@
+/* 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 runtime = require("sdk/system/runtime");
+const system = require("sdk/system");
+
+exports["test system architecture and compiler"] = function(assert) {
+
+ if (system.architecture !== null) {
+ assert.equal(
+ runtime.XPCOMABI.indexOf(system.architecture), 0,
+ "system.architecture is starting substring of runtime.XPCOMABI"
+ );
+ }
+
+ if (system.compiler !== null) {
+ assert.equal(
+ runtime.XPCOMABI.indexOf(system.compiler),
+ runtime.XPCOMABI.length - system.compiler.length,
+ "system.compiler is trailing substring of runtime.XPCOMABI"
+ );
+ }
+
+ assert.ok(
+ system.architecture === null || typeof(system.architecture) === "string",
+ "system.architecture is string or null if not supported by platform"
+ );
+
+ assert.ok(
+ system.compiler === null || typeof(system.compiler) === "string",
+ "system.compiler is string or null if not supported by platform"
+ );
+};
+
+require("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 f6a766401f0..081fbb3fbfa 100644
--- a/addon-sdk/source/test/test-ui-action-button.js
+++ b/addon-sdk/source/test/test-ui-action-button.js
@@ -835,6 +835,44 @@ exports['test button state are snapshot'] = function(assert) {
loader.unload();
}
+exports['test button icon object is a snapshot'] = function(assert) {
+ let loader = Loader(module);
+ let { ActionButton } = loader.require('sdk/ui');
+
+ let icon = {
+ '16': './foo.png'
+ };
+
+ let button = ActionButton({
+ id: 'my-button-17',
+ label: 'my button',
+ icon: icon
+ });
+
+ assert.deepEqual(button.icon, icon,
+ 'button.icon has the same properties of the object set in the constructor');
+
+ assert.notEqual(button.icon, icon,
+ 'button.icon is not the same object of the object set in the constructor');
+
+ assert.throws(
+ () => button.icon[16] = './bar.png',
+ /16 is read-only/,
+ 'properties of button.icon are ready-only'
+ );
+
+ let newIcon = {'16': './bar.png'};
+ button.icon = newIcon;
+
+ assert.deepEqual(button.icon, newIcon,
+ 'button.icon has the same properties of the object set');
+
+ assert.notEqual(button.icon, newIcon,
+ 'button.icon is not the same object of the object set');
+
+ loader.unload();
+}
+
exports['test button after destroy'] = function(assert) {
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 2eb66b1b181..71a6170593b 100644
--- a/addon-sdk/source/test/test-ui-toggle-button.js
+++ b/addon-sdk/source/test/test-ui-toggle-button.js
@@ -844,6 +844,44 @@ exports['test button state are snapshot'] = function(assert) {
loader.unload();
}
+exports['test button icon object is a snapshot'] = function(assert) {
+ let loader = Loader(module);
+ let { ToggleButton } = loader.require('sdk/ui');
+
+ let icon = {
+ '16': './foo.png'
+ };
+
+ let button = ToggleButton({
+ id: 'my-button-17',
+ label: 'my button',
+ icon: icon
+ });
+
+ assert.deepEqual(button.icon, icon,
+ 'button.icon has the same properties of the object set in the constructor');
+
+ assert.notEqual(button.icon, icon,
+ 'button.icon is not the same object of the object set in the constructor');
+
+ assert.throws(
+ () => button.icon[16] = './bar.png',
+ /16 is read-only/,
+ 'properties of button.icon are ready-only'
+ );
+
+ let newIcon = {'16': './bar.png'};
+ button.icon = newIcon;
+
+ assert.deepEqual(button.icon, newIcon,
+ 'button.icon has the same properties of the object set');
+
+ assert.notEqual(button.icon, newIcon,
+ 'button.icon is not the same object of the object set');
+
+ loader.unload();
+}
+
exports['test button after destroy'] = function(assert) {
let loader = Loader(module);
let { ToggleButton } = loader.require('sdk/ui');
diff --git a/b2g/chrome/content/desktop.js b/b2g/chrome/content/desktop.js
index 66256982643..3e0d0234e54 100644
--- a/b2g/chrome/content/desktop.js
+++ b/b2g/chrome/content/desktop.js
@@ -52,6 +52,10 @@ function checkDebuggerPort() {
// DebuggerServer.openListener detects that it isn't a file path (string),
// and starts listening on the tcp port given here as command line argument.
+ if (!window.arguments) {
+ return;
+ }
+
// Get the command line arguments that were passed to the b2g client
let args = window.arguments[0].QueryInterface(Ci.nsICommandLine);
diff --git a/b2g/chrome/content/runapp.js b/b2g/chrome/content/runapp.js
index 59340763db5..6df206fb1cb 100644
--- a/b2g/chrome/content/runapp.js
+++ b/b2g/chrome/content/runapp.js
@@ -5,6 +5,10 @@
let runAppObj;
window.addEventListener('load', function() {
+ if (!window.arguments) {
+ return;
+ }
+
// Get the command line arguments that were passed to the b2g client
let args = window.arguments[0].QueryInterface(Ci.nsICommandLine);
let appname;
diff --git a/b2g/chrome/content/screen.js b/b2g/chrome/content/screen.js
index f20f6579764..1f5eb826673 100644
--- a/b2g/chrome/content/screen.js
+++ b/b2g/chrome/content/screen.js
@@ -57,12 +57,19 @@ window.addEventListener('ContentStart', function() {
};
// Get the command line arguments that were passed to the b2g client
- let args = window.arguments[0].QueryInterface(Ci.nsICommandLine);
- let screenarg;
+ let args;
+ try {
+ // On Firefox Mulet, we don't always have a command line argument
+ args = window.arguments[0].QueryInterface(Ci.nsICommandLine);
+ } catch(e) {}
+
+ let screenarg = null;
// Get the --screen argument from the command line
try {
- screenarg = args.handleFlagWithParam('screen', false);
+ if (args) {
+ screenarg = args.handleFlagWithParam('screen', false);
+ }
// If there isn't one, use the default screen
if (screenarg === null)
diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml
index 90a0aca1e3d..6cad5063ebb 100644
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml
index 194f6436ba7..a904da8d71e 100644
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml
index 0c9b7979fe0..b0dc35ad1fb 100644
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml
index 90a0aca1e3d..6cad5063ebb 100644
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml
index 2182504f9fa..cb30e848d83 100644
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
index e1f051e5e96..e725b098f49 100644
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
- "revision": "a54e097e9e3384d885c0116d9c4ca15c1e1cd75e",
+ "revision": "c766bc0d49af19f18788ad8ed0542b82db81f1d8",
"repo_path": "/integration/gaia-central"
}
diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml
index b8c37aae1e8..37c2ed9c8bc 100644
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml
index be3db5be4d7..76eb1e7218e 100644
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml
index d33d85aecaa..4f919d763cd 100644
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml
index 27858e072bd..7b8c00a8fc6 100644
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml
index 4da2cd2bd1d..4307d436530 100644
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml
index b18c39486fa..d0c0fb3b8ea 100644
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in
index 7a39d31c7f1..7407e9c1721 100644
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -183,7 +183,6 @@
@BINPATH@/components/dom_notification.xpt
@BINPATH@/components/dom_html.xpt
@BINPATH@/components/dom_indexeddb.xpt
-@BINPATH@/components/dom_inputmethod.xpt
@BINPATH@/components/dom_offline.xpt
@BINPATH@/components/dom_payment.xpt
@BINPATH@/components/dom_json.xpt
@@ -457,6 +456,8 @@
@BINPATH@/components/nsPlacesDBFlush.js
@BINPATH@/components/nsPlacesAutoComplete.manifest
@BINPATH@/components/nsPlacesAutoComplete.js
+@BINPATH@/components/UnifiedComplete.manifest
+@BINPATH@/components/UnifiedComplete.js
@BINPATH@/components/nsPlacesExpiration.js
@BINPATH@/components/PlacesProtocolHandler.js
@BINPATH@/components/PlacesCategoriesStarter.js
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
index 52de7c0cd9c..09e7c030370 100644
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -122,7 +122,6 @@ tabbrowser {
visibility: hidden;
}
-.tab-close-button,
.tab-background {
/* Explicitly set the visibility to override the value (collapsed)
* we inherit from #TabsToolbar[collapsed] upon opening a browser window. */
@@ -131,15 +130,28 @@ tabbrowser {
transition: visibility 0ms 25ms;
}
-.tab-close-button:not([fadein]):not([pinned]),
.tab-background:not([fadein]):not([pinned]) {
visibility: hidden;
/* Closing tabs are hidden without a delay. */
transition-delay: 0ms;
}
+.tab-close-button,
+.tab-label {
+ /* Explicitly set the visibility to override the value (collapsed)
+ * we inherit from #TabsToolbar[collapsed] upon opening a browser window. */
+ visibility: visible;
+ transition: opacity 70ms 230ms,
+ visibility 0ms 230ms;
+}
+
+.tab-close-button:not([fadein]):not([pinned]),
+.tab-label:not([fadein]):not([pinned]) {
+ visibility: collapse;
+ opacity: .6;
+}
+
.tab-throbber:not([fadein]):not([pinned]),
-.tab-label:not([fadein]):not([pinned]),
.tab-icon-image:not([fadein]):not([pinned]) {
display: none;
}
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
index a91f8c1ee8c..87e7a01c86a 100644
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -81,6 +81,10 @@
Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
.getService(Components.interfaces.mozIPlacesAutoComplete);
+
+ Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
+ .getService(Components.interfaces.mozIPlacesAutoComplete);
+
document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
@@ -744,8 +748,10 @@
}
let autocomplete = this.mTabBrowser._placesAutocomplete;
+ let unifiedComplete = this.mTabBrowser._unifiedComplete;
if (this.mBrowser.registeredOpenURI) {
autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
+ unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
delete this.mBrowser.registeredOpenURI;
}
// Tabs in private windows aren't registered as "Open" so
@@ -754,6 +760,7 @@
(!PrivateBrowsingUtils.isWindowPrivate(window) ||
PrivateBrowsingUtils.permanentPrivateBrowsing)) {
autocomplete.registerOpenPage(aLocation);
+ unifiedComplete.registerOpenPage(aLocation);
this.mBrowser.registeredOpenURI = aLocation;
}
}
@@ -1672,12 +1679,6 @@
// kick the animation off
t.setAttribute("fadein", "true");
-
- // This call to adjustTabstrip is redundant but needed so that
- // when opening a second tab, the first tab's close buttons
- // appears immediately rather than when the transition ends.
- if (this.tabs.length - this._removingTabs.length == 2)
- this.tabContainer.adjustTabstrip();
}.bind(this));
}
@@ -1863,13 +1864,6 @@
aTab.style.maxWidth = ""; // ensure that fade-out transition happens
aTab.removeAttribute("fadein");
- if (this.tabs.length - this._removingTabs.length == 1) {
- // The second tab just got closed and we will end up with a single
- // one. Remove the first tab's close button immediately (if needed)
- // rather than after the tabclose animation ends.
- this.tabContainer.adjustTabstrip();
- }
-
setTimeout(function (tab, tabbrowser) {
if (tab.parentNode &&
window.getComputedStyle(tab).maxWidth == "0.1px") {
@@ -1969,6 +1963,7 @@
if (browser.registeredOpenURI && !aTabWillBeMoved) {
this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
+ this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
delete browser.registeredOpenURI;
}
@@ -2295,6 +2290,7 @@
// If the current URI is registered as open remove it from the list.
if (aOurBrowser.registeredOpenURI) {
this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
+ this._unifiedComplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
delete aOurBrowser.registeredOpenURI;
}
@@ -3104,6 +3100,7 @@
let browser = this.getBrowserAtIndex(i);
if (browser.registeredOpenURI) {
this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
+ this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
delete browser.registeredOpenURI;
}
browser.webProgress.removeProgressListener(this.mTabFilters[i]);
diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml
index 1468efb592c..1208022789d 100644
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -68,6 +68,12 @@
this.inputField.addEventListener("overflow", this, false);
this.inputField.addEventListener("underflow", this, false);
+ try {
+ if (this._prefs.getBoolPref("unifiedcomplete")) {
+ this.setAttribute("autocompletesearch", "unifiedcomplete");
+ }
+ } catch (ex) {}
+
const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var textBox = document.getAnonymousElementByAttribute(this,
"anonid", "textbox-input-box");
@@ -599,6 +605,14 @@
case "trimURLs":
this._mayTrimURLs = this._prefs.getBoolPref(aData);
break;
+ case "unifiedcomplete":
+ let useUnifiedComplete = false;
+ try {
+ useUnifiedComplete = this._prefs.getBoolPref(aData);
+ } catch (ex) {}
+ this.setAttribute("autocompletesearch",
+ useUnifiedComplete ? "unifiedcomplete"
+ : "urlinline history");
}
}
]]>