Merge m-c to fx-team

This commit is contained in:
Matt Brubeck 2013-11-27 21:33:00 -08:00
commit 069a378bfb
63 changed files with 2034 additions and 688 deletions

View File

@ -96,7 +96,7 @@ package.json modified: please re-run 'cfx run'
alt="Mozilla icon widget" />
Run `cfx run` again, and it will run an instance of Firefox. In the
bottom-right corner of the browser you'll see an icon with the Firefox
bottom-right corner of the browser you'll see an icon with the Mozilla
logo. Click the icon, and a new tab will open with
[http://www.mozilla.org/](http://www.mozilla.org/) loaded into it.

View File

@ -89,7 +89,7 @@ to support private browsing, refer to the
var utils = require('sdk/window/utils');
var browserWindow = utils.getMostRecentBrowserWindow();
var window = browserWindow.content; // `window` object for the current webpage
utils.getToplevelWindw(window) == browserWindow // => true
utils.getToplevelWindow(window) == browserWindow // => true
@param window {nsIDOMWindow}
@returns {nsIDOMWindow}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -137,6 +137,14 @@ const ContentWorker = Object.freeze({
registerMethod = chromeSetInterval;
else
throw new Error("Unknown timer kind: " + timer.kind);
if (typeof timer.fun == 'string') {
let code = timer.fun;
timer.fun = () => chromeAPI.sandbox.evaluate(exports, code);
} else if (typeof timer.fun != 'function') {
throw new Error('Unsupported callback type' + typeof timer.fun);
}
let id = registerMethod(onFire, timer.delay);
function onFire() {
try {
@ -145,12 +153,47 @@ const ContentWorker = Object.freeze({
timer.fun.apply(null, timer.args);
} catch(e) {
console.exception(e);
let wrapper = {
instanceOfError: instanceOf(e, Error),
value: e,
};
if (wrapper.instanceOfError) {
wrapper.value = {
message: e.message,
fileName: e.fileName,
lineNumber: e.lineNumber,
stack: e.stack,
name: e.name,
};
}
pipe.emit('error', wrapper);
}
}
_timers[id] = timer;
return id;
}
// copied from sdk/lang/type.js since modules are not available here
function instanceOf(value, Type) {
var isConstructorNameSame;
var isConstructorSourceSame;
// If `instanceof` returned `true` we know result right away.
var isInstanceOf = value instanceof Type;
// If `instanceof` returned `false` we do ducktype check since `Type` may be
// from a different sandbox. If a constructor of the `value` or a constructor
// of the value's prototype has same name and source we assume that it's an
// instance of the Type.
if (!isInstanceOf && value) {
isConstructorNameSame = value.constructor.name === Type.name;
isConstructorSourceSame = String(value.constructor) == String(Type);
isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
instanceOf(Object.getPrototypeOf(value), Type);
}
return isInstanceOf;
}
function unregisterTimer(id) {
if (!(id in _timers))
return;

View File

@ -13,6 +13,7 @@ const { validateOptions } = require('../deprecated/api-utils');
const { isValidURI, URL } = require('../url');
const file = require('../io/file');
const { contract } = require('../util/contract');
const { isString, instanceOf } = require('../lang/type');
const LOCAL_URI_SCHEMES = ['resource', 'data'];
@ -32,7 +33,7 @@ const valid = {
msg: 'The `contentURL` option must be a valid URL.'
},
contentScriptFile: {
is: ['undefined', 'null', 'string', 'array'],
is: ['undefined', 'null', 'string', 'array', 'object'],
map: ensureNull,
ok: function(value) {
if (value === null)
@ -40,8 +41,13 @@ const valid = {
value = [].concat(value);
// Make sure every item is a local file URL.
// Make sure every item is a string or an
// URL instance, and also a local file URL.
return value.every(function (item) {
if (!isString(item) && !(item instanceof URL))
return false;
try {
return ~LOCAL_URI_SCHEMES.indexOf(URL(item).scheme);
}

View File

@ -108,6 +108,27 @@ const Symbiont = Worker.resolve({
this._frame = frame;
if (getDocShell(frame)) {
this._reallyInitFrame(frame);
}
else {
if (this._waitForFrame) {
observers.remove('content-document-global-created', this._waitForFrame);
}
this._waitForFrame = this.__waitForFrame.bind(this, frame);
observers.add('content-document-global-created', this._waitForFrame);
}
},
__waitForFrame: function _waitForFrame(frame, win, topic) {
if (frame.contentWindow == win) {
observers.remove('content-document-global-created', this._waitForFrame);
delete this._waitForFrame;
this._reallyInitFrame(frame);
}
},
_reallyInitFrame: function _reallyInitFrame(frame) {
getDocShell(frame).allowJavascript = this.allow.script;
frame.setAttribute("src", this._contentURL);
@ -179,6 +200,11 @@ const Symbiont = Worker.resolve({
* This listener is registered in `Symbiont._initFrame`.
*/
_unregisterListener: function _unregisterListener() {
if (this._waitForFrame) {
observers.remove('content-document-global-created', this._waitForFrame);
delete this._waitForFrame;
}
if (!this._loadListener)
return;
if (this._loadEvent == "start") {

View File

@ -202,8 +202,15 @@ const WorkerSandbox = EventEmitter.compose({
clearInterval: 'r'
}
},
sandbox: {
evaluate: evaluate,
__exposedProps__: {
evaluate: 'r',
}
},
__exposedProps__: {
timers: 'r'
timers: 'r',
sandbox: 'r',
}
};
let onEvent = this._onContentEvent.bind(this);
@ -233,6 +240,19 @@ const WorkerSandbox = EventEmitter.compose({
self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments);
});
// unwrap, recreate and propagate async Errors thrown from content-script
this.on("error", function onError({instanceOfError, value}) {
if (self._addonWorker) {
let error = value;
if (instanceOfError) {
error = new Error(value.message, value.fileName, value.lineNumber);
error.stack = value.stack;
error.name = value.name;
}
self._addonWorker._emit('error', error);
}
});
// Inject `addon` global into target document if document is trusted,
// `addon` in document is equivalent to `self` in content script.
if (worker._injectInDocument) {

View File

@ -36,7 +36,7 @@ const observers = function observers(target, type) {
* The listener function that processes the event.
*/
function on(target, type, listener) {
if (typeof(listener) !== 'function')
if (typeof(listener) !== 'function')
throw new Error(BAD_LISTENER);
let listeners = observers(target, type);
@ -56,9 +56,9 @@ exports.on = on;
* The listener function that processes the event.
*/
function once(target, type, listener) {
on(target, type, function observer() {
on(target, type, function observer(...args) {
off(target, type, observer);
listener.apply(target, arguments);
listener.apply(target, args);
});
}
exports.once = once;
@ -74,40 +74,24 @@ exports.once = once;
* Event target object.
* @param {String} type
* The type of event.
* @params {Object|Number|String|Boolean} message
* First argument that will be passed to listeners.
* @params {Object|Number|String|Boolean} ...
* More arguments that will be passed to listeners.
* @params {Object|Number|String|Boolean} args
* Arguments that will be passed to listeners.
*/
function emit(target, type, message /*, ...*/) {
for each (let item in emit.lazy.apply(emit.lazy, arguments)) {
// We just iterate, iterator take care of emitting events.
}
}
/**
* This is very experimental feature that you should not use unless absolutely
* need it. Also it may be removed at any point without any further notice.
*
* Creates lazy iterator of return values of listeners. You can think of it
* as lazy array of return values of listeners for the `emit` with the given
* arguments.
*/
emit.lazy = function lazy(target, type, message /*, ...*/) {
let args = Array.slice(arguments, 2);
function emit (target, type, ...args) {
let state = observers(target, type);
let listeners = state.slice();
let index = 0;
let count = listeners.length;
let index = 0;
// If error event and there are no handlers then print error message
// into a console.
if (count === 0 && type === 'error') console.exception(message);
if (count === 0 && type === 'error') console.exception(args[0]);
while (index < count) {
try {
let listener = listeners[index];
// Dispatch only if listener is still registered.
if (~state.indexOf(listener)) yield listener.apply(target, args);
if (~state.indexOf(listener))
listener.apply(target, args);
}
catch (error) {
// If exception is not thrown by a error listener and error listener is
@ -115,8 +99,10 @@ emit.lazy = function lazy(target, type, message /*, ...*/) {
if (type !== 'error') emit(target, 'error', error);
else console.exception(error);
}
index = index + 1;
index++;
}
// Also emit on `"*"` so that one could listen for all events.
if (type !== '*') emit(target, '*', type, ...args);
}
exports.emit = emit;
@ -145,7 +131,7 @@ function off(target, type, listener) {
}
else if (length === 1) {
let listeners = event(target);
Object.keys(listeners).forEach(function(type) delete listeners[type]);
Object.keys(listeners).forEach(type => delete listeners[type]);
}
}
exports.off = off;
@ -171,7 +157,7 @@ exports.count = count;
* Dictionary of listeners.
*/
function setListeners(target, listeners) {
Object.keys(listeners || {}).forEach(function onEach(key) {
Object.keys(listeners || {}).forEach(key => {
let match = EVENT_TYPE_PATTERN.exec(key);
let type = match && match[1].toLowerCase();
let listener = listeners[key];

View File

@ -11,6 +11,10 @@ module.metadata = {
const { Cc, Ci, Cr } = require("chrome");
const apiUtils = require("./deprecated/api-utils");
const errors = require("./deprecated/errors");
const { isString, isUndefined, instanceOf } = require('./lang/type');
const { URL } = require('./url');
const NOTIFICATION_DIRECTIONS = ["auto", "ltr", "rtl"];
try {
let alertServ = Cc["@mozilla.org/alerts-service;1"].
@ -36,7 +40,7 @@ exports.notify = function notifications_notify(options) {
};
function notifyWithOpts(notifyFn) {
notifyFn(valOpts.iconURL, valOpts.title, valOpts.text, !!clickObserver,
valOpts.data, clickObserver);
valOpts.data, clickObserver, valOpts.tag, valOpts.dir, valOpts.lang);
}
try {
notifyWithOpts(notify);
@ -66,15 +70,32 @@ function validateOptions(options) {
is: ["string", "undefined"]
},
iconURL: {
is: ["string", "undefined"]
is: ["string", "undefined", "object"],
ok: function(value) {
return isUndefined(value) || isString(value) || (value instanceof URL);
},
msg: "`iconURL` must be a string or an URL instance."
},
onClick: {
is: ["function", "undefined"]
},
text: {
is: ["string", "undefined"]
is: ["string", "undefined", "number"]
},
title: {
is: ["string", "undefined", "number"]
},
tag: {
is: ["string", "undefined", "number"]
},
dir: {
is: ["string", "undefined"],
ok: function(value) {
return isUndefined(value) || ~NOTIFICATION_DIRECTIONS.indexOf(value);
},
msg: '`dir` option must be one of: "auto", "ltr" or "rtl".'
},
lang: {
is: ["string", "undefined"]
}
});

View File

@ -136,18 +136,18 @@ const PageMod = Loader.compose(EventEmitter, {
_applyOnExistingDocuments: function _applyOnExistingDocuments() {
let mod = this;
// Returns true if the tab match one rule
let tabs = getAllTabs().filter(function (tab) {
return mod.include.matchesAny(getTabURI(tab));
});
let tabs = getAllTabs();
tabs.forEach(function (tab) {
// Fake a newly created document
let window = getTabContentWindow(tab);
if (has(mod.attachTo, "top"))
if (has(mod.attachTo, "top") && mod.include.matchesAny(getTabURI(tab)))
mod._onContent(window);
if (has(mod.attachTo, "frame"))
getFrames(window).forEach(mod._onContent);
if (has(mod.attachTo, "frame")) {
getFrames(window).
filter((iframe) => mod.include.matchesAny(iframe.location.href)).
forEach(mod._onContent);
}
});
},

View File

@ -130,6 +130,10 @@ const Request = Class({
request(this).contentType = validateSingleOption('contentType', value);
},
get response() { return request(this).response; },
delete: function() {
runRequest('DELETE', this);
return this;
},
get: function() {
runRequest('GET', this);
return this;

View File

@ -13,6 +13,7 @@ const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL, getTabConten
const { emit } = require('../event/core');
const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils');
const { when: unload } = require('../system/unload');
const { viewFor } = require('../event/core');
const { EVENTS } = require('./events');
const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
@ -33,7 +34,7 @@ const Tab = Class({
// TabReady
let onReady = tabInternals.onReady = onTabReady.bind(this);
tab.browser.addEventListener(EVENTS.ready.dom, onReady, false);
let onPageShow = tabInternals.onPageShow = onTabPageShow.bind(this);
tab.browser.addEventListener(EVENTS.pageshow.dom, onPageShow, false);
@ -176,6 +177,10 @@ const Tab = Class({
});
exports.Tab = Tab;
// Implement `viewFor` polymorphic function for the Tab
// instances.
viewFor.define(Tab, x => tabNS(x).tab);
function cleanupTab(tab) {
let tabInternals = tabNS(tab);
if (!tabInternals.tab)

View File

@ -13,9 +13,10 @@ const { getFaviconURIForLocation } = require("../io/data");
const { activateTab, getOwnerWindow, getBrowserForTab, getTabTitle, setTabTitle,
getTabURL, setTabURL, getTabContentType, getTabId } = require('./utils');
const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils');
const viewNS = require('sdk/core/namespace').ns();
const { deprecateUsage } = require('sdk/util/deprecate');
const { getURL } = require('sdk/url/utils');
const viewNS = require('../core/namespace').ns();
const { deprecateUsage } = require('../util/deprecate');
const { getURL } = require('../url/utils');
const { viewFor } = require('../view/core');
// Array of the inner instances of all the wrapped tabs.
const TABS = [];
@ -64,6 +65,7 @@ const TabTrait = Trait.compose(EventEmitter, {
viewNS(this._public).tab = this._tab;
getPBOwnerWindow.implement(this._public, getChromeTab);
viewFor.implement(this._public, getTabView);
// Add tabs to getURL method
getURL.implement(this._public, (function (obj) this._public.url).bind(this));
@ -97,7 +99,7 @@ const TabTrait = Trait.compose(EventEmitter, {
if (event.target == this._contentDocument)
this._emit(EVENTS.ready.name, this._public);
},
/**
* Internal listener that emits public event 'load' when the page of this
* tab is loaded, for triggering on non-HTML content, bug #671305
@ -272,6 +274,10 @@ function getChromeTab(tab) {
return getOwnerWindow(viewNS(tab).tab);
}
// Implement `viewFor` polymorphic function for the Tab
// instances.
const getTabView = tab => viewNS(tab).tab;
function Tab(options, existingOnly) {
let chromeTab = options.tab;
for each (let tab in TABS) {

View File

@ -16,13 +16,12 @@ var method = require("method/core");
// it returns `null`. You can implement this method for
// this type to define what the result should be for it.
let getNodeView = method("getNodeView");
getNodeView.define(function(value) {
if (value instanceof Ci.nsIDOMNode)
return value;
return null;
});
getNodeView.define(x =>
x instanceof Ci.nsIDOMNode ? x :
x instanceof Ci.nsIDOMWindow ? x :
null);
exports.getNodeView = getNodeView;
exports.viewFor = getNodeView;
let getActiveView = method("getActiveView");
exports.getActiveView = getActiveView;

View File

@ -26,6 +26,8 @@ const ERR_CONTENT = "No content or contentURL property found. Widgets must "
"position.",
ERR_DESTROYED = "The widget has been destroyed and can no longer be used.";
const INSERTION_PREF_ROOT = "extensions.sdk-widget-inserted.";
// Supported events, mapping from DOM event names to our event names
const EVENTS = {
"click": "click",
@ -33,6 +35,11 @@ const EVENTS = {
"mouseout": "mouseout",
};
// In the Australis menu panel, normally widgets should be treated like
// 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 { validateOptions } = require("./deprecated/api-utils");
const panels = require("./panel");
const { EventEmitter, EventEmitterTrait } = require("./deprecated/events");
@ -45,8 +52,8 @@ const { WindowTracker } = require("./deprecated/window-utils");
const { isBrowser } = require("./window/utils");
const { setTimeout } = require("./timers");
const unload = require("./system/unload");
const { uuid } = require("./util/uuid");
const { getNodeView } = require("./view/core");
const prefs = require('./preferences/service');
// Data types definition
const valid = {
@ -215,6 +222,13 @@ let model = {
};
function saveInserted(widgetId) {
prefs.set(INSERTION_PREF_ROOT + widgetId, true);
}
function haveInserted(widgetId) {
return prefs.has(INSERTION_PREF_ROOT + widgetId);
}
/**
* Main Widget class: entry point of the widget API
@ -555,6 +569,9 @@ let browserManager = {
let idx = this.items.indexOf(item);
if (idx > -1)
this.items.splice(idx, 1);
},
propagateCurrentset: function browserManager_propagateCurrentset(id, currentset) {
this.windows.forEach(function (w) w.doc.getElementById(id).setAttribute("currentset", currentset));
}
};
@ -605,36 +622,14 @@ BrowserWindow.prototype = {
if (this.window.CustomizableUI) {
let placement = this.window.CustomizableUI.getPlacementOfWidget(node.id);
if (!placement) {
if (haveInserted(node.id)) {
return;
}
placement = {area: 'nav-bar', position: undefined};
saveInserted(node.id);
}
this.window.CustomizableUI.addWidgetToArea(node.id, placement.area, placement.position);
// Depending on when this gets called, we might be in the right place now. In that case,
// don't run the following code.
if (node.parentNode != palette) {
return;
}
// Otherwise, insert:
let container = this.doc.getElementById(placement.area);
if (container.customizationTarget) {
container = container.customizationTarget;
}
if (placement.position !== undefined) {
// Find a position:
let items = this.window.CustomizableUI.getWidgetIdsInArea(placement.area);
let itemIndex = placement.position;
for (let l = items.length; itemIndex < l; itemIndex++) {
let realItems = container.getElementsByAttribute("id", items[itemIndex]);
if (realItems[0]) {
container.insertBefore(node, realItems[0]);
break;
}
}
}
if (node.parentNode != container) {
container.appendChild(node);
}
this.window.CustomizableUI.ensureWidgetPlacedInWindow(node.id, this.window);
return;
}
@ -650,10 +645,14 @@ BrowserWindow.prototype = {
}
// if widget isn't in any toolbar, add it to the addon-bar
// TODO: we may want some "first-launch" module to do this only on very
// first execution
let needToPropagateCurrentset = false;
if (!container) {
if (haveInserted(node.id)) {
return;
}
container = this.doc.getElementById("addon-bar");
saveInserted(node.id);
needToPropagateCurrentset = true;
// TODO: find a way to make the following code work when we use "cfx run":
// http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#8586
// until then, force display of addon bar directly from sdk code
@ -684,9 +683,11 @@ BrowserWindow.prototype = {
// Otherwise, this code will collide with other instance of Widget module
// during Firefox startup. See bug 685929.
if (ids.indexOf(id) == -1) {
container.setAttribute("currentset", container.currentSet);
let set = container.currentSet;
container.setAttribute("currentset", set);
// Save DOM attribute in order to save position on new window opened
this.window.document.persist(container.id, "currentset");
browserManager.propagateCurrentset(container.id, set);
}
}
}
@ -736,7 +737,6 @@ WidgetChrome.prototype.update = function WC_update(updatedItem, property, value)
WidgetChrome.prototype._createNode = function WC__createNode() {
// XUL element container for widget
let node = this._doc.createElement("toolbaritem");
let guid = String(uuid());
// Temporary work around require("self") failing on unit-test execution ...
let jetpackID = "testID";
@ -753,6 +753,14 @@ WidgetChrome.prototype._createNode = function WC__createNode() {
// Bug 626326: Prevent customize toolbar context menu to appear
node.setAttribute("context", "");
// For use in styling by the browser
node.setAttribute("sdkstylewidget", "true");
// Mark wide widgets as such:
if (this.window.CustomizableUI &&
this._widget.width > AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF) {
node.classList.add("panel-wide-item");
}
// TODO move into a stylesheet, configurable by consumers.
// Either widget.style, exposing the style object, or a URL
// (eg, can load local stylesheet file).
@ -784,6 +792,13 @@ WidgetChrome.prototype.fill = function WC_fill() {
// until the node is attached to a document.
this.node.appendChild(iframe);
var label = this._doc.createElement("label");
label.setAttribute("value", this._widget.label);
label.className = "toolbarbutton-text";
label.setAttribute("crop", "right");
label.setAttribute("flex", "1");
this.node.appendChild(label);
// add event handlers
this.addEventHandlers();

View File

@ -12,6 +12,7 @@ const unload = require('../system/unload');
const { isWindowPrivate } = require('../window/utils');
const { EventTarget } = require('../event/target');
const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils');
const { viewFor } = require('../view/core');
const { deprecateUsage } = require('../util/deprecate');
const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead';
@ -48,6 +49,7 @@ const BrowserWindow = Class({
});
exports.BrowserWindow = BrowserWindow;
getPBOwnerWindow.define(BrowserWindow, function(window) {
return windowNS(window).window;
});
const getWindowView = window => windowNS(window).window;
getPBOwnerWindow.define(BrowserWindow, getWindowView);
viewFor.define(BrowserWindow, getWindowView);

View File

@ -22,6 +22,7 @@ const { Cc, Ci, Cr } = require('chrome'),
const { windowNS } = require('../window/namespace');
const { isPrivateBrowsingSupported } = require('../self');
const { ignoreWindow } = require('sdk/private-browsing/utils');
const { viewFor } = require('../view/core');
/**
* Window trait composes safe wrappers for browser window that are E10S
@ -76,6 +77,7 @@ const BrowserWindowTrait = Trait.compose(
windowNS(this._public).window = this._window;
getOwnerWindow.implement(this._public, getChromeWindow);
viewFor.implement(this._public, getChromeWindow);
return this;
},
@ -84,6 +86,7 @@ const BrowserWindowTrait = Trait.compose(
_onLoad: function() {
try {
this._initWindowTabTracker();
this._loaded = true;
}
catch(e) {
this._emit('error', e);
@ -94,9 +97,12 @@ const BrowserWindowTrait = Trait.compose(
_onUnload: function() {
if (!this._window)
return;
this._destroyWindowTabTracker();
if (this._loaded)
this._destroyWindowTabTracker();
this._emitOnObject(browserWindows, 'close', this._public);
this._window = null;
windowNS(this._public).window = null;
// Removing reference from the windows array.
windows.splice(windows.indexOf(this), 1);
this._removeAllListeners();

View File

@ -76,9 +76,6 @@ function Worker(options) {
["pageshow", "pagehide", "detach", "message", "error"].forEach(function(key) {
trait.on(key, function() {
emit.apply(emit, [worker, key].concat(Array.slice(arguments)));
// Workaround lack of ability to listen on all events by emulating
// such ability. This will become obsolete once Bug 821065 is fixed.
emit.apply(emit, [worker, "*", key].concat(Array.slice(arguments)));
});
});
traits.set(worker, trait);

View File

@ -0,0 +1,5 @@
/* 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/. */
self.postMessage("msg from contentScriptFile");

View File

@ -44,16 +44,9 @@ exports["test multiple tabs"] = function(assert, done) {
on(events, "data", handler);
function handler ({type, target, timeStamp}) {
// ignore about:blank pages and *-document-global-created
// events that are not very consistent.
// ignore http:// requests, as Fennec's `about:home` page
// displays add-ons a user could install
if (target.URL !== "about:blank" &&
target.URL !== "about:home" &&
!target.URL.match(/^https?:\/\//i) &&
type !== "chrome-document-global-created" &&
type !== "content-document-global-created")
eventFilter(type, target, () => {
actual.push(type + " -> " + target.URL)
});
}
let window = getMostRecentBrowserWindow();
@ -92,12 +85,9 @@ exports["test nested frames"] = function(assert, done) {
let actual = [];
on(events, "data", handler);
function handler ({type, target, timeStamp}) {
// ignore about:blank pages and *-global-created
// events that are not very consistent.
if (target.URL !== "about:blank" &&
type !== "chrome-document-global-created" &&
type !== "content-document-global-created")
eventFilter(type, target, () => {
actual.push(type + " -> " + target.URL)
});
}
let window = getMostRecentBrowserWindow();
@ -126,4 +116,20 @@ exports["test nested frames"] = function(assert, done) {
});
};
// ignore about:blank pages and *-document-global-created
// events that are not very consistent.
// ignore http:// requests, as Fennec's `about:home` page
// displays add-ons a user could install
// ignore local `searchplugins` files loaded
// Calls callback if passes filter
function eventFilter (type, target, callback) {
if (target.URL !== "about:blank" &&
target.URL !== "about:home" &&
!target.URL.match(/^https?:\/\//i) &&
!target.URL.match(/searchplugins/) &&
type !== "chrome-document-global-created" &&
type !== "content-document-global-created")
callback();
}
require("test").run(exports);

View File

@ -6,6 +6,7 @@
const { Loader } = require('sdk/content/loader');
const self = require("sdk/self");
const fixtures = require("./fixtures");
const { URL } = require('sdk/url');
exports['test:contentURL'] = function(assert) {
let loader = Loader(),
@ -204,6 +205,28 @@ exports['test:contentScriptFile'] = function(assert) {
);
}
let data = 'data:text/html,test';
try {
loader.contentScriptFile = [ { toString: () => data } ];
test.fail('must throw when non-URL object is set');
} catch(e) {
assert.equal(
'The `contentScriptFile` option must be a local URL or an array of URLs.',
e.message
);
}
loader.contentScriptFile = new URL(data);
assert.ok(
loader.contentScriptFile instanceof URL,
'must be able to set `contentScriptFile` to an instance of URL'
);
assert.equal(
data,
loader.contentScriptFile.toString(),
'setting `contentScriptFile` to an instance of URL should preserve the url'
);
loader.contentScriptFile = undefined;
assert.equal(
null,

View File

@ -17,6 +17,10 @@ const { LoaderWithHookedConsole } = require("sdk/test/loader");
const { Worker } = require("sdk/content/worker");
const { close } = require("sdk/window/helpers");
const { set: setPref } = require("sdk/preferences/service");
const { isArray } = require("sdk/lang/type");
const { URL } = require('sdk/url');
const fixtures = require("./fixtures");
const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo";
@ -396,8 +400,116 @@ exports["test:ensure console.xxx works in cs"] = WorkerTest(
}
);
exports["test:setTimeout works with string argument"] = WorkerTest(
"data:text/html;charset=utf-8,<script>var docVal=5;</script>",
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "new " + function ContentScriptScope() {
// must use "window.scVal" instead of "var csVal"
// since we are inside ContentScriptScope function.
// i'm NOT putting code-in-string inside code-in-string </YO DAWG>
window.csVal = 13;
setTimeout("self.postMessage([" +
"csVal, " +
"window.docVal, " +
"'ContentWorker' in window, " +
"'UNWRAP_ACCESS_KEY' in window, " +
"'getProxyForObject' in window, " +
"])", 1);
},
contentScriptWhen: "ready",
onMessage: function([csVal, docVal, chrome1, chrome2, chrome3]) {
// test timer code is executed in the correct context
assert.equal(csVal, 13, "accessing content-script values");
assert.notEqual(docVal, 5, "can't access document values (directly)");
assert.ok(!chrome1 && !chrome2 && !chrome3, "nothing is leaked from chrome");
done();
}
});
}
);
exports["test:setTimeout can\"t be cancelled by content"] = WorkerTest(
exports["test:setInterval works with string argument"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let count = 0;
let worker = Worker({
window: browser.contentWindow,
contentScript: "setInterval('self.postMessage(1)', 50)",
contentScriptWhen: "ready",
onMessage: function(one) {
count++;
assert.equal(one, 1, "got " + count + " message(s) from setInterval");
if (count >= 3) done();
}
});
}
);
exports["test:setInterval async Errors passed to .onError"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let count = 0;
let worker = Worker({
window: browser.contentWindow,
contentScript: "setInterval(() => { throw Error('ubik') }, 50)",
contentScriptWhen: "ready",
onError: function(err) {
count++;
assert.equal(err.message, "ubik",
"error (corectly) propagated " + count + " time(s)");
if (count >= 3) done();
}
});
}
);
exports["test:setTimeout throws array, passed to .onError"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "setTimeout(function() { throw ['array', 42] }, 1)",
contentScriptWhen: "ready",
onError: function(arr) {
assert.ok(isArray(arr),
"the type of thrown/propagated object is array");
assert.ok(arr.length==2,
"the propagated thrown array is the right length");
assert.equal(arr[1], 42,
"element inside the thrown array correctly propagated");
done();
}
});
}
);
exports["test:setTimeout string arg with SyntaxError to .onError"] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let worker = Worker({
window: browser.contentWindow,
contentScript: "setTimeout('syntax 123 error', 1)",
contentScriptWhen: "ready",
onError: function(err) {
assert.equal(err.name, "SyntaxError",
"received SyntaxError thrown from bad code in string argument to setTimeout");
assert.ok('fileName' in err,
"propagated SyntaxError contains a fileName property");
assert.ok('stack' in err,
"propagated SyntaxError contains a stack property");
assert.equal(err.message, "missing ; before statement",
"propagated SyntaxError has the correct (helpful) message");
assert.equal(err.lineNumber, 1,
"propagated SyntaxError was thrown on the right lineNumber");
done();
}
});
}
);
exports["test:setTimeout can't be cancelled by content"] = WorkerTest(
"data:text/html;charset=utf-8,<script>var documentValue=true;</script>",
function(assert, browser, done) {
@ -700,4 +812,21 @@ exports["test:global postMessage"] = WorkerTest(
}
);
exports['test:conentScriptFile as URL instance'] = WorkerTest(
DEFAULT_CONTENT_URL,
function(assert, browser, done) {
let url = new URL(fixtures.url("test-contentScriptFile.js"));
let worker = Worker({
window: browser.contentWindow,
contentScriptFile: url,
onMessage: function(msg) {
assert.equal(msg, "msg from contentScriptFile",
"received a wrong message from contentScriptFile");
done();
}
});
}
);
require("test").run(exports);

View File

@ -222,23 +222,24 @@ exports['test count'] = function(assert) {
assert.equal(count(target, 'foo'), 0, 'listeners unregistered');
};
exports['test emit.lazy'] = function(assert) {
let target = {}, boom = Error('boom!'), errors = [], actual = []
exports['test listen to all events'] = function(assert) {
let actual = [];
let target = {};
on(target, 'error', function error(e) errors.push(e))
on(target, 'foo', message => actual.push(message));
on(target, '*', (type, ...message) => {
actual.push([type].concat(message));
});
emit(target, 'foo', 'hello');
assert.equal(actual[0], 'hello',
'non-wildcard listeners still work');
assert.deepEqual(actual[1], ['foo', 'hello'],
'wildcard listener called');
on(target, 'a', function() 1);
on(target, 'a', function() {});
on(target, 'a', function() 2);
on(target, 'a', function() { throw boom });
on(target, 'a', function() 3);
for each (let value in emit.lazy(target, 'a'))
actual.push(value);
assert.deepEqual(actual, [ 1, undefined, 2, 3 ],
'all results were collected');
assert.deepEqual(errors, [ boom ], 'errors reporetd');
emit(target, 'bar', 'goodbye');
assert.deepEqual(actual[2], ['bar', 'goodbye'],
'wildcard listener called for unbound event name');
};
require('test').run(exports);

View File

@ -5,7 +5,7 @@
'use strict';
const { on, emit } = require("sdk/event/core");
const { filter, map, merge, expand } = require("sdk/event/utils");
const { filter, map, merge, expand, pipe } = require("sdk/event/utils");
const $ = require("./event/helpers");
function isEven(x) !(x % 2)
@ -163,7 +163,96 @@ exports["test expand"] = function(assert) {
assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"],
"all inputs data merged into one");
}
};
exports["test pipe"] = function (assert, done) {
let src = {};
let dest = {};
let aneventCount = 0, multiargsCount = 0;
let wildcardCount = {};
on(dest, 'an-event', arg => {
assert.equal(arg, 'my-arg', 'piped argument to event');
++aneventCount;
check();
});
on(dest, 'multiargs', (...data) => {
assert.equal(data[0], 'a', 'multiple arguments passed via pipe');
assert.equal(data[1], 'b', 'multiple arguments passed via pipe');
assert.equal(data[2], 'c', 'multiple arguments passed via pipe');
++multiargsCount;
check();
});
on(dest, '*', (name, ...data) => {
wildcardCount[name] = (wildcardCount[name] || 0) + 1;
if (name === 'multiargs') {
assert.equal(data[0], 'a', 'multiple arguments passed via pipe, wildcard');
assert.equal(data[1], 'b', 'multiple arguments passed via pipe, wildcard');
assert.equal(data[2], 'c', 'multiple arguments passed via pipe, wildcard');
}
if (name === 'an-event')
assert.equal(data[0], 'my-arg', 'argument passed via pipe, wildcard');
check();
});
pipe(src, dest);
for (let i = 0; i < 3; i++)
emit(src, 'an-event', 'my-arg');
emit(src, 'multiargs', 'a', 'b', 'c');
function check () {
if (aneventCount === 3 && multiargsCount === 1 &&
wildcardCount['an-event'] === 3 &&
wildcardCount['multiargs'] === 1)
done();
}
};
exports["test pipe multiple targets"] = function (assert) {
let src1 = {};
let src2 = {};
let middle = {};
let dest = {};
pipe(src1, middle);
pipe(src2, middle);
pipe(middle, dest);
let middleFired = 0;
let destFired = 0;
let src1Fired = 0;
let src2Fired = 0;
on(src1, '*', () => src1Fired++);
on(src2, '*', () => src2Fired++);
on(middle, '*', () => middleFired++);
on(dest, '*', () => destFired++);
emit(src1, 'ev');
assert.equal(src1Fired, 1, 'event triggers in source in pipe chain');
assert.equal(middleFired, 1, 'event passes through the middle of pipe chain');
assert.equal(destFired, 1, 'event propagates to end of pipe chain');
assert.equal(src2Fired, 0, 'event does not fire on alternative chain routes');
emit(src2, 'ev');
assert.equal(src2Fired, 1, 'event triggers in source in pipe chain');
assert.equal(middleFired, 2,
'event passes through the middle of pipe chain from different src');
assert.equal(destFired, 2,
'event propagates to end of pipe chain from different src');
assert.equal(src1Fired, 1, 'event does not fire on alternative chain routes');
emit(middle, 'ev');
assert.equal(middleFired, 3,
'event triggers in source of pipe chain');
assert.equal(destFired, 3,
'event propagates to end of pipe chain from middle src');
assert.equal(src1Fired, 1, 'event does not fire on alternative chain routes');
assert.equal(src2Fired, 1, 'event does not fire on alternative chain routes');
};
require('test').run(exports);

View File

@ -24,6 +24,53 @@ exports.testOnClick = function (assert) {
loader.unload();
};
exports['test:numbers and URLs in options'] = function(assert) {
let [loader] = makeLoader(module);
let notifs = loader.require('sdk/notifications');
let opts = {
title: 123,
text: 45678,
// must use in-loader `sdk/url` module for the validation type check to work
iconURL: loader.require('sdk/url').URL('data:image/png,blah')
};
try {
notifs.notify(opts);
assert.pass('using numbers and URLs in options works');
} catch (e) {
assert.fail('using numbers and URLs in options must not throw');
}
loader.unload();
}
exports['test:new tag, dir and lang options'] = function(assert) {
let [loader] = makeLoader(module);
let notifs = loader.require('sdk/notifications');
let opts = {
title: 'best',
tag: 'tagging',
lang: 'en'
};
try {
opts.dir = 'ttb';
notifs.notify(opts);
assert.fail('`dir` option must not accept TopToBottom direction.');
} catch (e) {
assert.equal(e.message,
'`dir` option must be one of: "auto", "ltr" or "rtl".');
}
try {
opts.dir = 'rtl';
notifs.notify(opts);
assert.pass('`dir` option accepts "rtl" direction.');
} catch (e) {
assert.fail('`dir` option must accept "rtl" direction.');
}
loader.unload();
}
// Returns [loader, mockAlertService].
function makeLoader(module) {
let loader = Loader(module);

View File

@ -430,6 +430,50 @@ exports.testWorksWithExistingTabs = function(assert, done) {
});
};
exports.testExistingFrameDoesntMatchInclude = function(assert, done) {
let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-42';
let iframe = '<iframe src="' + iframeURL + '" />';
let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe);
tabs.open({
url: url,
onReady: function onReady(tab) {
let pagemod = new PageMod({
include: url,
attachTo: ['existing', 'frame'],
onAttach: function() {
assert.fail("Existing iframe URL doesn't match include, must not attach to anything");
}
});
timer.setTimeout(function() {
assert.pass("PageMod didn't attach to anything")
pagemod.destroy();
tab.close(done);
}, 250);
}
});
};
exports.testExistingOnlyFrameMatchesInclude = function(assert, done) {
let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-43';
let iframe = '<iframe src="' + iframeURL + '" />';
let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe);
tabs.open({
url: url,
onReady: function onReady(tab) {
let pagemod = new PageMod({
include: iframeURL,
attachTo: ['existing', 'frame'],
onAttach: function(worker) {
assert.equal(iframeURL, worker.url,
"PageMod attached to existing iframe when only it matches include rules");
pagemod.destroy();
tab.close(done);
}
});
}
});
};
exports.testTabWorkerOnMessage = function(assert, done) {
let { browserWindows } = require("sdk/windows");
let tabs = require("sdk/tabs");

View File

@ -245,15 +245,14 @@ exports.testMultipleOnMessageCallbacks = function(assert, done) {
let page = Page({
contentScript: "self.postMessage('')",
contentScriptWhen: "end",
onMessage: function() count += 1
onMessage: () => count += 1
});
page.on('message', function() count += 2);
page.on('message', function() count *= 3);
page.on('message', function()
page.on('message', () => count += 2);
page.on('message', () => count *= 3);
page.on('message', () =>
assert.equal(count, 9, "All callbacks were called, in order."));
page.on('message', function() done());
}
page.on('message', done);
};
exports.testLoadContentPage = function(assert, done) {
let page = Page({

View File

@ -210,6 +210,27 @@ exports.testInvalidJSON = function (assert, done) {
});
};
exports.testDelete = function (assert, done) {
let srv = startServerAsync(port, basePath);
srv.registerPathHandler("/test-delete",
function handle(request, response) {
response.setHeader("Content-Type", "text/plain", false);
});
Request({
url: "http://localhost:" + port + "/test-delete",
onComplete: function (response) {
// We cannot access the METHOD of the request to verify it's set
// correctly.
assert.equal(response.text, "");
assert.equal(response.statusText, "OK");
assert.equal(response.headers["Content-Type"], "text/plain");
srv.stop(done);
}
}).delete();
};
exports.testHead = function (assert, done) {
let srv = startServerAsync(port, basePath);

View File

@ -41,7 +41,7 @@ exports.testSetGetBool = function(assert) {
};
// TEST: setting and getting preferences with special characters work
exports.testSpecialChars = function(assert) {
exports.testSpecialChars = function(assert, done) {
let chars = specialChars.split("");
let len = chars.length;
@ -54,7 +54,7 @@ exports.testSpecialChars = function(assert) {
// end test
if (++count == len)
test.done();
done();
})
sp[char] = rand;
});

View File

@ -7,6 +7,8 @@ const tabs = require("sdk/tabs"); // From addon-kit
const windowUtils = require("sdk/deprecated/window-utils");
const { getTabForWindow } = require('sdk/tabs/helpers');
const app = require("sdk/system/xul-app");
const { viewFor } = require("sdk/view/core");
const { getTabId } = require("sdk/tabs/utils");
// The primary test tab
var primaryTab;
@ -136,4 +138,17 @@ exports["test behavior on close"] = function(assert, done) {
});
};
exports["test viewFor(tab)"] = (assert, done) => {
tabs.once("open", tab => {
const view = viewFor(tab);
assert.ok(view, "view is returned");
assert.equal(getTabId(view), tab.id, "tab has a same id");
tab.close();
done();
});
tabs.open({ url: "about:mozilla" });
}
require("test").run(exports);

View File

@ -18,6 +18,7 @@ const self = require("sdk/self");
const windowUtils = require("sdk/deprecated/window-utils");
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { close } = require("sdk/window/helpers");
const unload = require("sdk/system/unload");
const fixtures = require("./fixtures");
let jetpackID = "testID";
@ -51,7 +52,7 @@ exports.testConstructor = function(assert, done) {
// Test basic construct/destroy
AddonsMgrListener.onInstalling();
let w = widgets.Widget({ id: "fooID", label: "foo", content: "bar" });
let w = widgets.Widget({ id: "basic-construct-destroy", label: "foo", content: "bar" });
AddonsMgrListener.onInstalled();
assert.equal(widgetCount(), widgetStartCount + 1, "panel has correct number of child elements after widget construction");
@ -69,7 +70,7 @@ exports.testConstructor = function(assert, done) {
let loader = Loader(module);
let widgetsFromLoader = loader.require("sdk/widget");
let widgetStartCount = widgetCount();
let w = widgetsFromLoader.Widget({ id: "fooID", label: "foo", content: "bar" });
let w = widgetsFromLoader.Widget({ id: "destroy-on-unload", label: "foo", content: "bar" });
assert.equal(widgetCount(), widgetStartCount + 1, "widget has been correctly added");
loader.unload();
assert.equal(widgetCount(), widgetStartCount, "widget has been destroyed on module unload");
@ -94,25 +95,25 @@ exports.testConstructor = function(assert, done) {
// Test no content or image
assert.throws(
function() widgets.Widget({id: "fooID", label: "foo"}),
function() widgets.Widget({id: "no-content-throws", label: "foo"}),
/^No content or contentURL property found\. Widgets must have one or the other\.$/,
"throws on no content");
// Test empty content, no image
assert.throws(
function() widgets.Widget({id:"fooID", label: "foo", content: ""}),
function() widgets.Widget({id:"empty-content-throws", label: "foo", content: ""}),
/^No content or contentURL property found\. Widgets must have one or the other\.$/,
"throws on empty content");
// Test empty image, no content
assert.throws(
function() widgets.Widget({id:"fooID", label: "foo", image: ""}),
function() widgets.Widget({id:"empty-image-throws", label: "foo", image: ""}),
/^No content or contentURL property found\. Widgets must have one or the other\.$/,
"throws on empty content");
// Test empty content, empty image
assert.throws(
function() widgets.Widget({id:"fooID", label: "foo", content: "", image: ""}),
function() widgets.Widget({id:"empty-image-and-content-throws", label: "foo", content: "", image: ""}),
/^No content or contentURL property found. Widgets must have one or the other\.$/,
"throws on empty content");
@ -138,14 +139,14 @@ exports.testConstructor = function(assert, done) {
// Test position restore on create/destroy/create
// Create 3 ordered widgets
let w1 = widgets.Widget({id: "first", label:"first", content: "bar"});
let w2 = widgets.Widget({id: "second", label:"second", content: "bar"});
let w3 = widgets.Widget({id: "third", label:"third", content: "bar"});
let w1 = widgets.Widget({id: "position-first", label:"first", content: "bar"});
let w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
let w3 = widgets.Widget({id: "position-third", label:"third", content: "bar"});
// Remove the middle widget
assert.equal(widgetNode(1).getAttribute("label"), "second", "second widget is the second widget inserted");
w2.destroy();
assert.equal(widgetNode(1).getAttribute("label"), "third", "second widget is removed, so second widget is now the third one");
w2 = widgets.Widget({id: "second", label:"second", content: "bar"});
w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
assert.equal(widgetNode(1).getAttribute("label"), "second", "second widget is created again, at the same location");
// Cleanup this testcase
AddonsMgrListener.onUninstalling();
@ -160,14 +161,14 @@ exports.testConstructor = function(assert, done) {
let anotherWidgetsInstance = loader.require("sdk/widget");
assert.ok(container().collapsed, "UI is hidden when no widgets");
AddonsMgrListener.onInstalling();
let w1 = widgets.Widget({id: "foo", label: "foo", content: "bar"});
let w1 = widgets.Widget({id: "ui-unhide", label: "foo", content: "bar"});
// Ideally we would let AddonsMgrListener display the addon bar
// But, for now, addon bar is immediatly displayed by sdk code
// https://bugzilla.mozilla.org/show_bug.cgi?id=627484
assert.ok(!container().collapsed, "UI is already visible when we just added the widget");
AddonsMgrListener.onInstalled();
assert.ok(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation");
let w2 = anotherWidgetsInstance.Widget({id: "bar", label: "bar", content: "foo"});
let w2 = anotherWidgetsInstance.Widget({id: "ui-stay-open", label: "bar", content: "foo"});
assert.ok(!container().collapsed, "UI still visible when we add a second widget");
AddonsMgrListener.onUninstalling();
w1.destroy();
@ -214,7 +215,7 @@ exports.testConstructor = function(assert, done) {
// text widget
tests.push(function testTextWidget() testSingleWidget({
id: "text",
id: "text-single",
label: "text widget",
content: "oh yeah",
contentScript: "self.postMessage(document.body.innerHTML);",
@ -278,7 +279,7 @@ exports.testConstructor = function(assert, done) {
// event: onclick + content
tests.push(function testOnclickEventContent() testSingleWidget({
id: "click",
id: "click-content",
label: "click test widget - content",
content: "<div id='me'>foo</div>",
contentScript: "var evt = new MouseEvent('click', {button: 0});" +
@ -293,7 +294,7 @@ exports.testConstructor = function(assert, done) {
// event: onmouseover + content
tests.push(function testOnmouseoverEventContent() testSingleWidget({
id: "mouseover",
id: "mouseover-content",
label: "mouseover test widget - content",
content: "<div id='me'>foo</div>",
contentScript: "var evt = new MouseEvent('mouseover'); " +
@ -308,7 +309,7 @@ exports.testConstructor = function(assert, done) {
// event: onmouseout + content
tests.push(function testOnmouseoutEventContent() testSingleWidget({
id: "mouseout",
id: "mouseout-content",
label: "mouseout test widget - content",
content: "<div id='me'>foo</div>",
contentScript: "var evt = new MouseEvent('mouseout');" +
@ -323,7 +324,7 @@ exports.testConstructor = function(assert, done) {
// event: onclick + image
tests.push(function testOnclickEventImage() testSingleWidget({
id: "click",
id: "click-image",
label: "click test widget - image",
contentURL: fixtures.url("moz_favicon.ico"),
contentScript: "var evt = new MouseEvent('click'); " +
@ -338,7 +339,7 @@ exports.testConstructor = function(assert, done) {
// event: onmouseover + image
tests.push(function testOnmouseoverEventImage() testSingleWidget({
id: "mouseover",
id: "mouseover-image",
label: "mouseover test widget - image",
contentURL: fixtures.url("moz_favicon.ico"),
contentScript: "var evt = new MouseEvent('mouseover');" +
@ -353,7 +354,7 @@ exports.testConstructor = function(assert, done) {
// event: onmouseout + image
tests.push(function testOnmouseoutEventImage() testSingleWidget({
id: "mouseout",
id: "mouseout-image",
label: "mouseout test widget - image",
contentURL: fixtures.url("moz_favicon.ico"),
contentScript: "var evt = new MouseEvent('mouseout'); " +
@ -380,7 +381,7 @@ exports.testConstructor = function(assert, done) {
// test updating widget content
let loads = 0;
tests.push(function testUpdatingWidgetContent() testSingleWidget({
id: "content",
id: "content-updating",
label: "content update test widget",
content: "<div id='me'>foo</div>",
contentScript: "self.postMessage(1)",
@ -403,7 +404,7 @@ exports.testConstructor = function(assert, done) {
let url2 = "data:text/html;charset=utf-8,<body>nistel</body>";
tests.push(function testUpdatingContentURL() testSingleWidget({
id: "content",
id: "content-url-updating",
label: "content update test widget",
contentURL: url1,
contentScript: "self.postMessage(document.location.href);",
@ -426,7 +427,7 @@ exports.testConstructor = function(assert, done) {
// test tooltip
tests.push(function testTooltip() testSingleWidget({
id: "text",
id: "text-with-tooltip",
label: "text widget",
content: "oh yeah",
tooltip: "foo",
@ -456,7 +457,7 @@ exports.testConstructor = function(assert, done) {
// test updating widget tooltip
let updated = false;
tests.push(function testUpdatingTooltip() testSingleWidget({
id: "tooltip",
id: "tooltip-updating",
label: "tooltip update test widget",
tooltip: "foo",
content: "<div id='me'>foo</div>",
@ -472,7 +473,7 @@ exports.testConstructor = function(assert, done) {
// test allow attribute
tests.push(function testDefaultAllow() testSingleWidget({
id: "allow",
id: "allow-default",
label: "allow.script attribute",
content: "<script>document.title = 'ok';</script>",
contentScript: "self.postMessage(document.title)",
@ -484,7 +485,7 @@ exports.testConstructor = function(assert, done) {
}));
tests.push(function testExplicitAllow() testSingleWidget({
id: "allow",
id: "allow-explicit",
label: "allow.script attribute",
allow: {script: true},
content: "<script>document.title = 'ok';</script>",
@ -497,7 +498,7 @@ exports.testConstructor = function(assert, done) {
}));
tests.push(function testExplicitDisallow() testSingleWidget({
id: "allow",
id: "allow-explicit-disallow",
label: "allow.script attribute",
content: "<script>document.title = 'ok';</script>",
allow: {script: false},
@ -521,11 +522,11 @@ exports.testConstructor = function(assert, done) {
function widgetCount2() container() ? container().querySelectorAll('[id^="widget\:"]').length : 0;
let widgetStartCount2 = widgetCount2();
let w1Opts = {id:"first", label: "first widget", content: "first content"};
let w1Opts = {id:"first-multi-window", label: "first widget", content: "first content"};
let w1 = testSingleWidget(w1Opts);
assert.equal(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first widget");
let w2Opts = {id:"second", label: "second widget", content: "second content"};
let w2Opts = {id:"second-multi-window", label: "second widget", content: "second content"};
let w2 = testSingleWidget(w2Opts);
assert.equal(widgetCount2(), widgetStartCount2 + 2, "2nd window has correct number of child elements after second widget");
@ -542,7 +543,7 @@ exports.testConstructor = function(assert, done) {
tests.push(function testWindowClosing() {
// 1/ Create a new widget
let w1Opts = {
id:"first",
id:"first-win-closing",
label: "first widget",
content: "first content",
contentScript: "self.port.on('event', function () self.port.emit('event'))"
@ -595,7 +596,7 @@ exports.testConstructor = function(assert, done) {
});
});
if (!australis) {
if (false) {
tests.push(function testAddonBarHide() {
const tabBrowser = require("sdk/deprecated/tab-browser");
@ -614,11 +615,13 @@ exports.testConstructor = function(assert, done) {
assert.ok(container2().collapsed,
"2nd window starts with an hidden addon-bar");
let w1Opts = {id:"first", label: "first widget", content: "first content"};
let w1Opts = {id:"first-addonbar-hide", label: "first widget", content: "first content"};
let w1 = testSingleWidget(w1Opts);
assert.equal(widgetCount2(), widgetStartCount2 + 1,
"2nd window has correct number of child elements after" +
"widget creation");
assert.ok(!container().collapsed, "1st window has a visible addon-bar");
assert.ok(!container2().collapsed, "2nd window has a visible addon-bar");
w1.destroy();
assert.equal(widgetCount2(), widgetStartCount2,
"2nd window has correct number of child elements after" +
@ -637,7 +640,7 @@ exports.testConstructor = function(assert, done) {
// test widget.width
tests.push(function testWidgetWidth() testSingleWidget({
id: "text",
id: "text-test-width",
label: "test widget.width",
content: "test width",
width: 200,
@ -661,7 +664,7 @@ exports.testConstructor = function(assert, done) {
// test click handler not respond to right-click
let clickCount = 0;
tests.push(function testNoRightClick() testSingleWidget({
id: "click-content",
id: "right-click-content",
label: "click test widget - content",
content: "<div id='me'>foo</div>",
contentScript: // Left click
@ -762,7 +765,7 @@ exports.testWidgetMessaging = function testWidgetMessaging(assert, done) {
let origMessage = "foo";
const widgets = require("sdk/widget");
let widget = widgets.Widget({
id: "foo",
id: "widget-messaging",
label: "foo",
content: "<bar>baz</bar>",
contentScriptWhen: "end",
@ -782,7 +785,7 @@ exports.testWidgetMessaging = function testWidgetMessaging(assert, done) {
exports.testWidgetViews = function testWidgetViews(assert, done) {
const widgets = require("sdk/widget");
let widget = widgets.Widget({
id: "foo",
id: "widget-views",
label: "foo",
content: "<bar>baz</bar>",
contentScriptWhen: "ready",
@ -805,7 +808,7 @@ exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(assert, done)
const widgets = require("sdk/widget");
let view = null;
let widget = widgets.Widget({
id: "foo",
id: "widget-view-ui-events",
label: "foo",
content: "<div id='me'>foo</div>",
contentScript: "var evt = new MouseEvent('click', {button: 0});" +
@ -830,7 +833,7 @@ exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(assert, done)
exports.testWidgetViewsCustomEvents = function testWidgetViewsCustomEvents(assert, done) {
const widgets = require("sdk/widget");
let widget = widgets.Widget({
id: "foo",
id: "widget-view-custom-events",
label: "foo",
content: "<div id='me'>foo</div>",
contentScript: "self.port.emit('event', 'ok');",
@ -853,7 +856,7 @@ exports.testWidgetViewsTooltip = function testWidgetViewsTooltip(assert, done) {
const widgets = require("sdk/widget");
let widget = new widgets.Widget({
id: "foo",
id: "widget-views-tooltip",
label: "foo",
content: "foo"
});
@ -879,7 +882,7 @@ exports.testWidgetMove = function testWidgetMove(assert, done) {
let gotFirstReady = false;
let widget = widgets.Widget({
id: "foo",
id: "widget-move",
label: label,
content: "<bar>baz</bar>",
contentScriptWhen: "ready",
@ -947,7 +950,7 @@ exports.testWidgetWithPound = function testWidgetWithPound(assert, done) {
exports.testContentScriptOptionsOption = function(assert, done) {
let widget = require("sdk/widget").Widget({
id: "fooz",
id: "widget-script-options",
label: "fooz",
content: "fooz",
contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
@ -1051,6 +1054,34 @@ exports.testSVGWidget = function(assert, done) {
});
};
exports.testReinsertion = function(assert, done) {
const WIDGETID = "test-reinsertion";
let windowUtils = require("sdk/deprecated/window-utils");
let browserWindow = windowUtils.activeBrowserWindow;
let widget = require("sdk/widget").Widget({
id: "test-reinsertion",
label: "test reinsertion",
content: "Test",
});
let realWidgetId = "widget:" + jetpackID + "-" + WIDGETID;
// Remove the widget:
if (australis) {
browserWindow.CustomizableUI.removeWidgetFromArea(realWidgetId);
} else {
let widget = browserWindow.document.getElementById(realWidgetId);
let container = widget.parentNode;
container.currentSet = container.currentSet.replace("," + realWidgetId, "");
container.setAttribute("currentset", container.currentSet);
container.ownerDocument.persist(container.id, "currentset");
}
const tabBrowser = require("sdk/deprecated/tab-browser");
tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
assert.equal(e.target.defaultView.document.getElementById(realWidgetId), null);
close(e.target.defaultView).then(done);
}});
};
if (!australis) {
exports.testNavigationBarWidgets = function testNavigationBarWidgets(assert, done) {
let w1 = widgets.Widget({id: "1st", label: "1st widget", content: "1"});

View File

@ -5,6 +5,9 @@
const { Loader } = require('sdk/test/loader');
const { browserWindows } = require('sdk/windows');
const { viewFor } = require('sdk/view/core');
const { Ci } = require("chrome");
const { isBrowser, getWindowTitle } = require("sdk/window/utils");
// TEST: browserWindows Iterator
exports.testBrowserWindowsIterator = function(assert) {
@ -55,4 +58,23 @@ exports.testWindowActivateMethod_simple = function(assert) {
'Active tab is active after window.activate() call');
};
exports["test getView(window)"] = function(assert, done) {
browserWindows.once("open", window => {
const view = viewFor(window);
assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window");
assert.ok(isBrowser(view), "view is a browser window");
assert.equal(getWindowTitle(view), window.title,
"window has a right title");
window.close();
window.destroy();
assert.equal(viewFor(window), null, "window view is gone");
done();
});
browserWindows.open({ url: "data:text/html,<title>yo</title>" });
};
require('sdk/test').run(exports);

View File

@ -121,8 +121,13 @@ var FeedHandler = {
var haveFeeds = feeds && feeds.length > 0;
var feedButton = document.getElementById("feed-button");
if (feedButton)
feedButton.disabled = !haveFeeds;
if (feedButton) {
if (haveFeeds) {
feedButton.removeAttribute("disabled");
} else {
feedButton.setAttribute("disabled", "true");
}
}
if (!haveFeeds) {
this._feedMenuitem.setAttribute("disabled", "true");

View File

@ -1267,6 +1267,10 @@ SocialSidebar = {
sbrowser.stop();
sbrowser.removeAttribute("origin");
sbrowser.setAttribute("src", "about:blank");
// We need to explicitly create a new content viewer because the old one
// doesn't get destroyed until about:blank has loaded (which does not happen
// as long as the element is hidden).
sbrowser.docShell.createAboutBlankContentViewer(null);
SocialFlyout.unload();
},

File diff suppressed because it is too large Load Diff

View File

@ -1072,20 +1072,10 @@ function waitForMessages(aOptions)
return false;
}
if (aRule.type) {
// The rule tries to match the newer types of messages, based on their
// object constructor.
if (!aElement._messageObject ||
!(aElement._messageObject instanceof aRule.type)) {
return false;
}
}
else if (aElement._messageObject) {
// If the message element holds a reference to its object, it means this
// is a newer message type. All of the older waitForMessages() rules do
// not expect this kind of messages. We return false here.
// TODO: we keep this behavior until bug 778766 is fixed. After that we
// will not require |type| to match newer types of messages.
// The rule tries to match the newer types of messages, based on their
// object constructor.
if (aRule.type && (!aElement._messageObject ||
!(aElement._messageObject instanceof aRule.type))) {
return false;
}

View File

@ -52,10 +52,6 @@ const CONSOLE_DIR_VIEW_HEIGHT = 0.6;
const IGNORED_SOURCE_URLS = ["debugger eval code", "self-hosted"];
// The amount of time in milliseconds that must pass between messages to
// trigger the display of a new group.
const NEW_GROUP_DELAY = 5000;
// The amount of time in milliseconds that we wait before performing a live
// search.
const SEARCH_DELAY = 200;
@ -166,9 +162,6 @@ const FILTER_PREFS_PREFIX = "devtools.webconsole.filter.";
// The minimum font size.
const MIN_FONT_SIZE = 10;
// The maximum length of strings to be displayed by the Web Console.
const MAX_LONG_STRING_LENGTH = 200000;
const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
@ -1162,6 +1155,7 @@ WebConsoleFrame.prototype = {
let level = aMessage.level;
let args = aMessage.arguments;
let objectActors = new Set();
let node = null;
// Gather the actor IDs.
args.forEach((aValue) => {
@ -1175,7 +1169,11 @@ WebConsoleFrame.prototype = {
case "info":
case "warn":
case "error":
case "debug":
case "debug": {
let msg = new Messages.ConsoleGeneric(aMessage);
node = msg.init(this.output).render().element;
break;
}
case "dir": {
body = { arguments: args };
let clipboardArray = [];
@ -1283,18 +1281,22 @@ WebConsoleFrame.prototype = {
return null; // no need to continue
}
let node = this.createMessageNode(CATEGORY_WEBDEV, LEVELS[level], body,
sourceURL, sourceLine, clipboardText,
level, aMessage.timeStamp);
if (aMessage.private) {
node.setAttribute("private", true);
if (!node) {
node = this.createMessageNode(CATEGORY_WEBDEV, LEVELS[level], body,
sourceURL, sourceLine, clipboardText,
level, aMessage.timeStamp);
if (aMessage.private) {
node.setAttribute("private", true);
}
}
if (objectActors.size > 0) {
node._objectActors = objectActors;
let repeatNode = node.getElementsByClassName("repeats")[0];
repeatNode._uid += [...objectActors].join("-");
if (!node._messageObject) {
let repeatNode = node.getElementsByClassName("repeats")[0];
repeatNode._uid += [...objectActors].join("-");
}
}
if (level == "trace") {
@ -1316,26 +1318,6 @@ WebConsoleFrame.prototype = {
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage, [aMessage]);
},
/**
* The click event handler for objects shown inline coming from the
* window.console API.
*
* @private
* @param nsIDOMNode aAnchor
* The object inspector anchor element. This is the clickable element
* in the console.log message we display.
* @param object aObjectActor
* The object actor grip.
*/
_consoleLogClick: function WCF__consoleLogClick(aAnchor, aObjectActor)
{
this.jsterm.openVariablesView({
label: aAnchor.textContent,
objectActor: aObjectActor,
autofocus: true,
});
},
/**
* Reports an error in the page source, either JavaScript or CSS.
*
@ -1540,7 +1522,7 @@ WebConsoleFrame.prototype = {
aLinkNode.appendChild(mixedContentWarningNode);
this._addMessageLinkCallback(mixedContentWarningNode, (aNode, aEvent) => {
this._addMessageLinkCallback(mixedContentWarningNode, (aEvent) => {
aEvent.stopPropagation();
this.owner.openLink(MIXED_CONTENT_LEARN_MORE);
});
@ -1601,7 +1583,7 @@ WebConsoleFrame.prototype = {
warningNode.textContent = moreInfoLabel;
warningNode.className = "learn-more-link";
this._addMessageLinkCallback(warningNode, (aNode, aEvent) => {
this._addMessageLinkCallback(warningNode, (aEvent) => {
aEvent.stopPropagation();
this.owner.openLink(aURL);
});
@ -1694,16 +1676,6 @@ WebConsoleFrame.prototype = {
this.outputMessage(CATEGORY_JS, node);
},
/**
* Inform user that the string he tries to view is too long.
*/
logWarningAboutStringTooLong: function WCF_logWarningAboutStringTooLong()
{
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_WARNING,
l10n.getStr("longStringTooLong"));
this.outputMessage(CATEGORY_JS, node);
},
/**
* Handle the network events coming from the remote Web Console.
*
@ -2302,6 +2274,9 @@ WebConsoleFrame.prototype = {
*/
_pruneItemFromQueue: function WCF__pruneItemFromQueue(aItem)
{
// TODO: handle object releasing in a more elegant way once all console
// messages use the new API - bug 778766.
let [category, methodOrNode, args] = aItem;
if (typeof methodOrNode != "function" && methodOrNode._objectActors) {
for (let actor of methodOrNode._objectActors) {
@ -2310,6 +2285,19 @@ WebConsoleFrame.prototype = {
methodOrNode._objectActors.clear();
}
if (methodOrNode == this.output._flushMessageQueue &&
args[0]._objectActors) {
for (let arg of args) {
if (!arg._objectActors) {
continue;
}
for (let actor of arg._objectActors) {
this._releaseObject(actor);
}
arg._objectActors.clear();
}
}
if (category == CATEGORY_NETWORK) {
let connectionId = null;
if (methodOrNode == this.logNetEvent) {
@ -2482,10 +2470,6 @@ WebConsoleFrame.prototype = {
if (aLevel == "dir") {
str = VariablesView.getString(aBody.arguments[0]);
}
else if (["log", "info", "warn", "error", "debug"].indexOf(aLevel) > -1 &&
typeof aBody == "object") {
this._makeConsoleLogMessageBody(node, bodyNode, aBody);
}
else {
str = aBody;
}
@ -2561,126 +2545,6 @@ WebConsoleFrame.prototype = {
return node;
},
/**
* Make the message body for console.log() calls.
*
* @private
* @param nsIDOMElement aMessage
* The message element that holds the output for the given call.
* @param nsIDOMElement aContainer
* The specific element that will hold each part of the console.log
* output.
* @param object aBody
* The object given by this.logConsoleAPIMessage(). This object holds
* the call information that we need to display - mainly the arguments
* array of the given API call.
*/
_makeConsoleLogMessageBody:
function WCF__makeConsoleLogMessageBody(aMessage, aContainer, aBody)
{
Object.defineProperty(aMessage, "_panelOpen", {
get: function() {
let nodes = aContainer.getElementsByTagName("a");
return Array.prototype.some.call(nodes, function(aNode) {
return aNode._panelOpen;
});
},
enumerable: true,
configurable: false
});
aBody.arguments.forEach(function(aItem) {
if (aContainer.firstChild) {
aContainer.appendChild(this.document.createTextNode(" "));
}
let text = VariablesView.getString(aItem);
let inspectable = !VariablesView.isPrimitive({ value: aItem });
if (aItem && typeof aItem != "object" || !inspectable) {
aContainer.appendChild(this.document.createTextNode(text));
if (aItem.type && aItem.type == "longString") {
let ellipsis = this.document.createElementNS(XHTML_NS, "a");
ellipsis.classList.add("longStringEllipsis");
ellipsis.textContent = l10n.getStr("longStringEllipsis");
ellipsis.href = "#";
ellipsis.draggable = false;
let formatter = function(s) '"' + s + '"';
this._addMessageLinkCallback(ellipsis,
this._longStringClick.bind(this, aMessage, aItem, formatter));
aContainer.appendChild(ellipsis);
}
return;
}
// For inspectable objects.
let elem = this.document.createElementNS(XHTML_NS, "a");
elem.setAttribute("aria-haspopup", "true");
elem.textContent = text;
elem.href = "#";
elem.draggable = false;
this._addMessageLinkCallback(elem,
this._consoleLogClick.bind(this, elem, aItem));
aContainer.appendChild(elem);
}, this);
},
/**
* Click event handler for the ellipsis shown immediately after a long string.
* This method retrieves the full string and updates the console output to
* show it.
*
* @private
* @param nsIDOMElement aMessage
* The message element.
* @param object aActor
* The LongStringActor instance we work with.
* @param [function] aFormatter
* Optional function you can use to format the string received from the
* server, before being displayed in the console.
* @param nsIDOMElement aEllipsis
* The DOM element the user can click on to expand the string.
*/
_longStringClick:
function WCF__longStringClick(aMessage, aActor, aFormatter, aEllipsis)
{
if (!aFormatter) {
aFormatter = function(s) s;
}
let longString = this.webConsoleClient.longString(aActor);
let toIndex = Math.min(longString.length, MAX_LONG_STRING_LENGTH);
longString.substring(longString.initial.length, toIndex,
function WCF__onSubstring(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF__longStringClick substring failure: " +
aResponse.error);
return;
}
let node = aEllipsis.previousSibling;
node.textContent = aFormatter(longString.initial + aResponse.substring);
aEllipsis.parentNode.removeChild(aEllipsis);
if (aMessage.category == CATEGORY_WEBDEV ||
aMessage.category == CATEGORY_OUTPUT) {
aMessage.clipboardText = aMessage.textContent;
}
this.emit("messages-updated", new Set([aMessage]));
if (toIndex != longString.length) {
this.logWarningAboutStringTooLong();
}
}.bind(this));
},
/**
* Creates the anchor that displays the textual location of an incoming
* message.
@ -2807,7 +2671,7 @@ WebConsoleFrame.prototype = {
return;
}
aCallback(this, aEvent);
aCallback.call(this, aEvent);
}, false);
},
@ -3196,8 +3060,8 @@ JSTerm.prototype = {
* The JavaScript evaluation response handler.
*
* @private
* @param nsIDOMElement [aAfterNode]
* Optional DOM element after which the evaluation result will be
* @param object [aAfterMessage]
* Optional message after which the evaluation result will be
* inserted.
* @param function [aCallback]
* Optional function to invoke when the evaluation result is added to
@ -3206,7 +3070,7 @@ JSTerm.prototype = {
* The message received from the server.
*/
_executeResultCallback:
function JST__executeResultCallback(aAfterNode, aCallback, aResponse)
function JST__executeResultCallback(aAfterMessage, aCallback, aResponse)
{
if (!this.hud) {
return;
@ -3218,13 +3082,8 @@ JSTerm.prototype = {
}
let errorMessage = aResponse.exceptionMessage;
let result = aResponse.result;
let inspectable = false;
if (result && !VariablesView.isPrimitive({ value: result })) {
inspectable = true;
}
let helperResult = aResponse.helperResult;
let helperHasRawOutput = !!(helperResult || {}).rawOutput;
let resultString = VariablesView.getString(result);
if (helperResult && helperResult.type) {
switch (helperResult.type) {
@ -3232,11 +3091,11 @@ JSTerm.prototype = {
this.clearOutput();
break;
case "inspectObject":
if (aAfterNode) {
if (!aAfterNode._objectActors) {
aAfterNode._objectActors = new Set();
if (aAfterMessage) {
if (!aAfterMessage._objectActors) {
aAfterMessage._objectActors = new Set();
}
aAfterNode._objectActors.add(helperResult.object.actor);
aAfterMessage._objectActors.add(helperResult.object.actor);
}
this.openVariablesView({
label: VariablesView.getString(helperResult.object),
@ -3265,26 +3124,13 @@ JSTerm.prototype = {
return;
}
let node;
if (errorMessage) {
node = this.writeOutput(errorMessage, CATEGORY_OUTPUT, SEVERITY_ERROR,
aAfterNode, aResponse.timestamp);
}
else if (inspectable) {
node = this.writeOutputJS(resultString,
this._evalOutputClick.bind(this, aResponse),
aAfterNode, aResponse.timestamp);
}
else {
node = this.writeOutput(resultString, CATEGORY_OUTPUT, SEVERITY_LOG,
aAfterNode, aResponse.timestamp);
}
let msg = new Messages.JavaScriptEvalOutput(aResponse, errorMessage);
this.hud.output.addMessage(msg);
if (aCallback) {
let oldFlushCallback = this.hud._flushCallback;
this.hud._flushCallback = () => {
aCallback(node);
aCallback(msg.element);
if (oldFlushCallback) {
oldFlushCallback();
this.hud._flushCallback = oldFlushCallback;
@ -3295,36 +3141,15 @@ JSTerm.prototype = {
};
}
node._objectActors = new Set();
msg._afterMessage = aAfterMessage;
msg._objectActors = new Set();
let error = aResponse.exception;
if (WebConsoleUtils.isActorGrip(error)) {
node._objectActors.add(error.actor);
if (WebConsoleUtils.isActorGrip(aResponse.exception)) {
msg._objectActors.add(aResponse.exception.actor);
}
if (WebConsoleUtils.isActorGrip(result)) {
node._objectActors.add(result.actor);
if (result.type == "longString") {
// Add an ellipsis to expand the short string if the object is not
// inspectable.
let body = node.getElementsByClassName("body")[0];
let ellipsis = this.hud.document.createElementNS(XHTML_NS, "a");
ellipsis.classList.add("longStringEllipsis");
ellipsis.textContent = l10n.getStr("longStringEllipsis");
ellipsis.href = "#";
ellipsis.draggable = false;
let formatter = function(s) '"' + s + '"';
let onclick = this.hud._longStringClick.bind(this.hud, node, result,
formatter);
this.hud._addMessageLinkCallback(ellipsis, onclick);
body.appendChild(ellipsis);
node.clipboardText += " " + ellipsis.textContent;
}
msg._objectActors.add(result.actor);
}
},
@ -3345,8 +3170,12 @@ JSTerm.prototype = {
return;
}
let node = this.writeOutput(aExecuteString, CATEGORY_INPUT, SEVERITY_LOG);
let onResult = this._executeResultCallback.bind(this, node, aCallback);
let message = new Messages.Simple(aExecuteString, {
category: "input",
severity: "log",
});
this.hud.output.addMessage(message);
let onResult = this._executeResultCallback.bind(this, message, aCallback);
let options = { frame: this.SELECTED_FRAME };
this.requestEvaluation(aExecuteString, options).then(onResult, onResult);
@ -3782,14 +3611,16 @@ JSTerm.prototype = {
return;
}
let exception = aResponse.exception;
if (exception) {
let node = this.writeOutput(aResponse.exceptionMessage,
CATEGORY_OUTPUT, SEVERITY_ERROR,
null, aResponse.timestamp);
node._objectActors = new Set();
if (WebConsoleUtils.isActorGrip(exception)) {
node._objectActors.add(exception.actor);
if (aResponse.exceptionMessage) {
let message = new Messages.Simple(aResponse.exceptionMessage, {
category: "output",
severity: "error",
timestamp: aResponse.timestamp,
});
this.hud.output.addMessage(message);
message._objectActors = new Set();
if (WebConsoleUtils.isActorGrip(aResponse.exception)) {
message._objectActors.add(aResponse.exception.actor);
}
}
@ -3810,74 +3641,6 @@ JSTerm.prototype = {
},
/**
* Writes a JS object to the JSTerm outputNode.
*
* @param string aOutputMessage
* The message to display.
* @param function [aCallback]
* Optional function to invoke when users click the message.
* @param nsIDOMNode [aNodeAfter]
* Optional DOM node after which you want to insert the new message.
* This is used when execution results need to be inserted immediately
* after the user input.
* @param number [aTimestamp]
* Optional timestamp to show for the output message (millisconds since
* the UNIX epoch). If no timestamp is provided then Date.now() is
* used.
* @return nsIDOMNode
* The new message node.
*/
writeOutputJS:
function JST_writeOutputJS(aOutputMessage, aCallback, aNodeAfter, aTimestamp)
{
let link = null;
if (aCallback) {
link = this.hud.document.createElementNS(XHTML_NS, "a");
link.setAttribute("aria-haspopup", true);
link.textContent = aOutputMessage;
link.href = "#";
link.draggable = false;
this.hud._addMessageLinkCallback(link, aCallback);
}
return this.writeOutput(link || aOutputMessage, CATEGORY_OUTPUT,
SEVERITY_LOG, aNodeAfter, aTimestamp);
},
/**
* Writes a message to the HUD that originates from the interactive
* JavaScript console.
*
* @param nsIDOMNode|string aOutputMessage
* The message to display.
* @param number aCategory
* The category of message: one of the CATEGORY_ constants.
* @param number aSeverity
* The severity of message: one of the SEVERITY_ constants.
* @param nsIDOMNode [aNodeAfter]
* Optional DOM node after which you want to insert the new message.
* This is used when execution results need to be inserted immediately
* after the user input.
* @param number [aTimestamp]
* Optional timestamp to show for the output message (millisconds since
* the UNIX epoch). If no timestamp is provided then Date.now() is
* used.
* @return nsIDOMNode
* The new message node.
*/
writeOutput:
function JST_writeOutput(aOutputMessage, aCategory, aSeverity, aNodeAfter,
aTimestamp)
{
let node = this.hud.createMessageNode(aCategory, aSeverity, aOutputMessage,
null, null, null, null, aTimestamp);
node._outputAfterNode = aNodeAfter;
this.hud.outputMessage(aCategory, node);
return node;
},
/**
* Clear the Web Console output.
*
@ -4562,21 +4325,6 @@ JSTerm.prototype = {
this.completeNode.value = prefix + aSuffix;
},
/**
* The click event handler for evaluation results in the output.
*
* @private
* @param object aResponse
* The JavaScript evaluation response received from the server.
*/
_evalOutputClick: function JST__evalOutputClick(aResponse)
{
this.openVariablesView({
label: VariablesView.getString(aResponse.result),
objectActor: aResponse.result,
autofocus: true,
});
},
/**
* Destroy the sidebar.

View File

@ -540,8 +540,6 @@
<property name="matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
<property name="popupOpen" readonly="true" onget="return !this.hidden"/>
<property name="overrideValue" readonly="true" onget="return null;"/>
<!-- Alias of popupOpen, for compatibility with global/content/bindings/autocomplete.xml -->
<property name="mPopupOpen" readonly="true" onget="return this.popupOpen"/>
<property name="selectedItem">
<getter>
@ -551,7 +549,9 @@
</getter>
<setter>
<![CDATA[
return this._isGridBound(this._results) ? this._results.selectedItem : null;
if (this._isGridBound(this._results)) {
this._results.selectedItem = val;
}
]]>
</setter>
</property>
@ -564,7 +564,9 @@
</getter>
<setter>
<![CDATA[
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
if (this._isGridBound(this._results)) {
this._results.selectedIndex = val;
}
]]>
</setter>
</property>

View File

@ -369,6 +369,10 @@ gTests.push({
EventUtils.synthesizeKey("VK_UP", {}, window);
is(gEdit.popup._results.selectedIndex, 0, "Pressing arrow up selects first item.");
EventUtils.synthesizeKey("VK_BACK_SPACE", {}, window);
yield waitForEvent(document.getElementById("urlbar-edit"), "input");
is(gEdit.popup._results.selectedIndex, -1, "backspace: autocomplete de-selected");
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -682,7 +682,6 @@ menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
-moz-margin-start: -4px;
}
#urlbar-search-splitter + #urlbar-container > #urlbar-wrapper > #urlbar,
#urlbar-search-splitter + #search-container > #searchbar > .searchbar-textbox {
-moz-margin-start: 0;
}

View File

@ -940,7 +940,6 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
background: transparent;
}
#urlbar-search-splitter + #urlbar-container > #urlbar-wrapper > #urlbar,
#urlbar-search-splitter + #search-container > #searchbar > .searchbar-textbox {
-moz-margin-start: 0;
}

View File

@ -208,13 +208,9 @@ abstract public class BrowserApp extends GeckoApp
Log.d(LOGTAG, "BrowserApp.onTabChanged: " + tab.getId() + ": " + msg);
switch(msg) {
// We don't get a LOCATION_CHANGE event for the first about:home
// load, because the previous and current URIs are the
// same. That means it's OK to trigger a new favicon load
// at this point.
case LOCATION_CHANGE:
if (Tabs.getInstance().isSelectedTab(tab)) {
loadFavicon(tab);
maybeCancelFaviconLoad(tab);
}
// fall through
case SELECTED:
@ -255,6 +251,9 @@ abstract public class BrowserApp extends GeckoApp
invalidateOptionsMenu();
}
break;
case PAGE_SHOW:
loadFavicon(tab);
break;
case LINK_FAVICON:
// If tab is not loading and the favicon is updated, we
// want to load the image straight away. If tab is still

View File

@ -126,6 +126,9 @@ public class GeckoAppShell
static private final HashMap<String, String>
mAlertCookies = new HashMap<String, String>();
// See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
static private final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
/* Keep in sync with constants found here:
http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
*/
@ -199,6 +202,7 @@ public class GeckoAppShell
}
public static native Message getNextMessageFromQueue(MessageQueue queue);
public static native void onSurfaceTextureFrameAvailable(Object surfaceTexture, int id);
public static native void dispatchMemoryPressure();
public static void registerGlobalExceptionHandler() {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@ -1377,29 +1381,7 @@ public class GeckoAppShell
}
private static boolean isHighMemoryDevice() {
BufferedReader br = null;
FileReader fr = null;
try {
fr = new FileReader("/proc/meminfo");
if (fr == null)
return false;
br = new BufferedReader(fr);
String line = br.readLine();
while (line != null && !line.startsWith("MemTotal")) {
line = br.readLine();
}
String[] tokens = line.split("\\s+");
if (tokens.length >= 2 && Long.parseLong(tokens[1]) >= 786432 /* 768MB in kb*/) {
return true;
}
} catch (Exception ex) {
} finally {
try {
if (fr != null)
fr.close();
} catch (IOException ioe) {}
}
return false;
return HardwareUtils.getMemSize() > HIGH_MEMORY_DEVICE_THRESHOLD_MB;
}
/**

View File

@ -152,7 +152,7 @@ class MemoryMonitor extends BroadcastReceiver {
if (level >= MEMORY_PRESSURE_MEDIUM) {
//Only send medium or higher events because that's all that is used right now
if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createLowMemoryEvent(level));
GeckoAppShell.dispatchMemoryPressure();
}
Favicons.clearMemCache();

View File

@ -6,16 +6,14 @@
package org.mozilla.gecko;
import org.mozilla.gecko.util.HardwareUtils;
import android.os.StrictMode;
import android.util.Log;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@ -30,7 +28,6 @@ public final class SysInfo {
// avoid inconsistency, so we don't bother with synchronization for
// these.
private static volatile int cpuCount = -1;
private static volatile int totalRAM = -1;
/**
* Get the number of cores on the device.
@ -80,58 +77,10 @@ public final class SysInfo {
}
/**
* Fetch the total memory of the device in MB by parsing /proc/meminfo.
*
* Of course, Android doesn't have a neat and tidy way to find total
* RAM, so we do it by parsing /proc/meminfo.
*
* @return 0 if a problem occurred, or memory size in MB.
* Wraps HardwareUtils so callers don't need to know about it.
*/
public static int getMemSize() {
if (totalRAM >= 0) {
return totalRAM;
}
try {
RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r");
try {
// MemTotal will be one of the first three lines.
int i = 0;
String memTotal = null;
while (i++ < 3) {
memTotal = reader.readLine();
if (memTotal == null ||
memTotal.startsWith("MemTotal: ")) {
break;
}
memTotal = null;
}
if (memTotal == null) {
return totalRAM = 0;
}
// Parse a line like this:
// MemTotal: 1605324 kB
Matcher m = Pattern.compile("^MemTotal:\\s+([0-9]+) kB\\s*$")
.matcher(memTotal);
if (m.matches()) {
String kb = m.group(1);
if (kb != null) {
return totalRAM = (Integer.parseInt(kb) / 1024);
}
}
Log.w(LOG_TAG, "Got unexpected MemTotal line: " + memTotal);
return totalRAM = 0;
} finally {
reader.close();
}
} catch (FileNotFoundException f) {
return totalRAM = 0;
} catch (IOException e) {
return totalRAM = 0;
}
return HardwareUtils.getMemSize();
}
/**

View File

@ -6,6 +6,7 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.EditBookmarkDialog;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
@ -40,6 +41,9 @@ abstract class HomeFragment extends Fragment {
// Log Tag.
private static final String LOGTAG="GeckoHomeFragment";
// Share MIME type.
private static final String SHARE_MIME_TYPE = "text/plain";
// Whether the fragment can load its content or not
// This is used to defer data loading until the editing
// mode animation ends.
@ -91,6 +95,8 @@ abstract class HomeFragment extends Fragment {
menu.findItem(R.id.home_remove).setVisible(false);
}
menu.findItem(R.id.home_share).setVisible(!GeckoProfile.get(getActivity()).inGuestMode());
final boolean canOpenInReader = (info.display == Combined.DISPLAY_READER);
menu.findItem(R.id.home_open_in_reader).setVisible(canOpenInReader);
}
@ -111,6 +117,25 @@ abstract class HomeFragment extends Fragment {
final Context context = getActivity().getApplicationContext();
final int itemId = item.getItemId();
if (itemId == R.id.home_share) {
if (info.url == null) {
Log.e(LOGTAG, "Can't share because URL is null");
} else {
GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
Intent.ACTION_SEND, info.getDisplayTitle());
}
}
if (itemId == R.id.home_add_to_launcher) {
if (info.url == null) {
Log.e(LOGTAG, "Can't add to home screen because URL is null");
return false;
}
// Fetch the largest cacheable icon size.
Favicons.getLargestFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle()));
return true;
}
if (itemId == R.id.home_open_private_tab || itemId == R.id.home_open_new_tab) {
if (info.url == null) {

View File

@ -178,10 +178,13 @@ size. -->
<!ENTITY datareporting_wifi_title "&vendorShortName; location services">
<!ENTITY datareporting_wifi_summary "Help improve geolocation services for the Open Web by letting &brandShortName; collect and send anonymous cellular tower data">
<!ENTITY pref_update_autodownload "Automatic updates">
<!-- Localization note (pref_update_autodownload2) : This should mention downloading
specifically, since the pref only prevents automatic downloads and not the
actual notification that an update is available. -->
<!ENTITY pref_update_autodownload2 "Download updates automatically">
<!ENTITY pref_update_autodownload_wifi "Only over Wi-Fi">
<!ENTITY pref_update_autodownload_disabled "Disabled">
<!ENTITY pref_update_autodownload_enabled "Enabled">
<!ENTITY pref_update_autodownload_never "Never">
<!ENTITY pref_update_autodownload_always "Always">
<!ENTITY quit "Quit">

View File

@ -14,10 +14,16 @@
<item android:id="@+id/home_open_in_reader"
android:title="@string/contextmenu_open_in_reader"/>
<item android:id="@+id/home_share"
android:title="@string/contextmenu_share"/>
<item android:id="@+id/home_edit_bookmark"
android:title="@string/contextmenu_edit_bookmark"/>
<item android:id="@+id/home_remove"
android:title="@string/contextmenu_remove"/>
<item android:id="@+id/home_add_to_launcher"
android:title="@string/contextmenu_add_to_launcher"/>
</menu>

View File

@ -145,10 +145,10 @@
<string name="pref_private_data_siteSettings">&pref_private_data_siteSettings2;</string>
<string name="pref_private_data_downloadFiles">&pref_private_data_downloadFiles;</string>
<string name="pref_import_android">&pref_import_android;</string>
<string name="pref_update_autodownload">&pref_update_autodownload;</string>
<string name="pref_update_autodownload">&pref_update_autodownload2;</string>
<string name="pref_update_autodownload_wifi">&pref_update_autodownload_wifi;</string>
<string name="pref_update_autodownload_disabled">&pref_update_autodownload_disabled;</string>
<string name="pref_update_autodownload_enabled">&pref_update_autodownload_enabled;</string>
<string name="pref_update_autodownload_disabled">&pref_update_autodownload_never;</string>
<string name="pref_update_autodownload_enabled">&pref_update_autodownload_always;</string>
<string name="pref_about_firefox">&pref_about_firefox;</string>

View File

@ -43,7 +43,9 @@ class StringHelper {
"Open in New Tab",
"Open in Private Tab",
"Edit",
"Remove"
"Remove",
"Share",
"Add to Home Screen"
};
public static final String TITLE_PLACE_HOLDER = "Enter Search or Address";

View File

@ -151,7 +151,7 @@ public class testSettingsMenuItems extends PixelTest {
// Automatic updates
if (AppConstants.MOZ_UPDATER) {
String[] autoUpdateUi = { "Automatic updates", "Only over Wi-Fi", "Enabled", "Only over Wi-Fi", "Disabled" };
String[] autoUpdateUi = { "Download updates automatically", "Only over Wi-Fi", "Always", "Only over Wi-Fi", "Never" };
settingsMap.get("Customize").add(autoUpdateUi);
}

View File

@ -21,9 +21,9 @@ import java.util.List;
/**
* This test covers the opening and content of the Share Link pop-up list
* The test opens the Share menu from the app menu, the URL bar, and a link context menu.
* The test opens the Share menu from the app menu, the URL bar, a link context menu and the Awesomescreen tabs
*/
public class testShareLink extends BaseTest {
public class testShareLink extends AboutHomeTest {
String url;
String urlTitle = "Big Link";
@ -37,6 +37,9 @@ public class testShareLink extends BaseTest {
ArrayList<String> shareOptions;
blockForGeckoReady();
// FIXME: This is a temporary hack workaround for a permissions problem.
openAboutHomeTab(AboutHomeTabs.READING_LIST);
inputAndLoadUrl(url);
verifyPageTitle(urlTitle); // Waiting for page title to ensure the page is loaded
@ -66,6 +69,71 @@ public class testShareLink extends BaseTest {
float left = mDriver.getGeckoLeft() + mDriver.getGeckoWidth() / 2;
mSolo.clickLongOnScreen(left, top);
verifySharePopup("Share Link",shareOptions,"Link");
// Test the share popup in the Bookmarks page
openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
final ListView bookmarksList = findListViewWithTag("bookmarks");
mAsserter.is(waitForNonEmptyListToLoad(bookmarksList), true, "list is properly loaded");
int headerViewsCount = bookmarksList.getHeaderViewsCount();
View bookmarksItem = bookmarksList.getChildAt(headerViewsCount);
if (bookmarksItem == null) {
mAsserter.dumpLog("no child at index " + headerViewsCount + "; waiting for one...");
Condition listWaitCondition = new Condition() {
@Override
public boolean isSatisfied() {
if (bookmarksList.getChildAt(bookmarksList.getHeaderViewsCount()) == null)
return false;
return true;
}
};
waitForCondition(listWaitCondition, MAX_WAIT_MS);
headerViewsCount = bookmarksList.getHeaderViewsCount();
bookmarksItem = bookmarksList.getChildAt(headerViewsCount);
}
mSolo.clickLongOnView(bookmarksItem);
verifySharePopup(shareOptions,"bookmarks");
// Prepopulate top sites with history items to overflow tiles.
// We are trying to move away from using reflection and doing more black-box testing.
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_01.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_02.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_03.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_04.html"));
if (mDevice.type.equals("tablet")) {
// Tablets have more tile spaces to fill.
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_05.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_boxes.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_search.html"));
inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_text_page.html"));
}
// Test the share popup in Top Sites.
openAboutHomeTab(AboutHomeTabs.TOP_SITES);
// Scroll down a bit so that the top sites list has more items on screen.
int width = mDriver.getGeckoWidth();
int height = mDriver.getGeckoHeight();
mActions.drag(width / 2, width / 2, height - 10, height / 2);
ListView topSitesList = findListViewWithTag("top_sites");
mAsserter.is(waitForNonEmptyListToLoad(topSitesList), true, "list is properly loaded");
View mostVisitedItem = topSitesList.getChildAt(topSitesList.getHeaderViewsCount());
mSolo.clickLongOnView(mostVisitedItem);
verifySharePopup(shareOptions,"top_sites");
// Test the share popup in the Most Recent tab
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
ListView mostRecentList = findListViewWithTag("most_recent");
mAsserter.is(waitForNonEmptyListToLoad(mostRecentList), true, "list is properly loaded");
// Getting second child after header views because the first is the "Today" label
View mostRecentItem = mostRecentList.getChildAt(mostRecentList.getHeaderViewsCount() + 1);
mSolo.clickLongOnView(mostRecentItem);
verifySharePopup(shareOptions,"most recent");
}
public void verifySharePopup(ArrayList<String> shareOptions, String openedFrom) {

View File

@ -546,6 +546,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
switch (msg) {
case ADDED:
case CLOSED:
updateTabCount(tabs.getDisplayCount());
break;
case RESTORED:
@ -588,12 +589,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
case LOCATION_CHANGE:
// A successful location change will cause Tab to notify
// us of a title change, so we don't update the title here.
// And there's no point in refreshing the UI
// if the page is the same.
final String oldURL = (String) data;
if (!TextUtils.equals(oldURL, tab.getURL())) {
refresh();
}
refresh();
break;
case CLOSED:

View File

@ -12,6 +12,7 @@ import android.os.Build;
import android.util.Log;
import android.view.ViewConfiguration;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.util.regex.Matcher;
@ -21,17 +22,13 @@ public final class HardwareUtils {
private static final String LOGTAG = "GeckoHardwareUtils";
// Minimum memory threshold for a device to be considered
// a low memory platform (see sIsLowMemoryPlatform). This value
// a low memory platform (see isLowMemoryPlatform). This value
// has be in sync with Gecko's equivalent threshold (defined in
// xpcom/base/nsMemoryImpl.cpp) and should only be used in cases
// where we can't depend on Gecko to be up and running e.g. show/hide
// reading list capabilities in HomePager.
private static final int LOW_MEMORY_THRESHOLD_KB = 384 * 1024;
private static final String PROC_MEMINFO_FILE = "/proc/meminfo";
private static final Pattern PROC_MEMTOTAL_FORMAT =
Pattern.compile("^MemTotal:[ \t]*([0-9]*)[ \t]kB");
private static final int LOW_MEMORY_THRESHOLD_MB = 384;
private static volatile int sTotalRAM = -1;
private static Context sContext;
@ -39,7 +36,6 @@ public final class HardwareUtils {
private static Boolean sIsSmallTablet;
private static Boolean sIsTelevision;
private static Boolean sHasMenuButton;
private static Boolean sIsLowMemoryPlatform;
private HardwareUtils() {
}
@ -93,42 +89,73 @@ public final class HardwareUtils {
return sHasMenuButton;
}
public static boolean isLowMemoryPlatform() {
if (sIsLowMemoryPlatform == null) {
RandomAccessFile fileReader = null;
try {
fileReader = new RandomAccessFile(PROC_MEMINFO_FILE, "r");
// Defaults to false
long totalMem = LOW_MEMORY_THRESHOLD_KB;
String line = null;
while ((line = fileReader.readLine()) != null) {
final Matcher matcher = PROC_MEMTOTAL_FORMAT.matcher(line);
if (matcher.find()) {
totalMem = Long.parseLong(matcher.group(1));
break;
}
}
sIsLowMemoryPlatform = (totalMem < LOW_MEMORY_THRESHOLD_KB);
} catch (IOException e) {
// Fallback to false if we fail to read meminfo
// for some reason.
Log.w(LOGTAG, "Could not read " + PROC_MEMINFO_FILE + "." +
"Falling back to isLowMemoryPlatform = false", e);
sIsLowMemoryPlatform = false;
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
// Do nothing
}
}
}
/**
* Fetch the total memory of the device in MB by parsing /proc/meminfo.
*
* Of course, Android doesn't have a neat and tidy way to find total
* RAM, so we do it by parsing /proc/meminfo.
*
* @return 0 if a problem occurred, or memory size in MB.
*/
public static int getMemSize() {
if (sTotalRAM >= 0) {
return sTotalRAM;
}
return sIsLowMemoryPlatform;
try {
RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r");
try {
// MemTotal will be one of the first three lines.
int i = 0;
String memTotal = null;
while (i++ < 3) {
memTotal = reader.readLine();
if (memTotal == null ||
memTotal.startsWith("MemTotal: ")) {
break;
}
memTotal = null;
}
if (memTotal == null) {
return sTotalRAM = 0;
}
// Parse a line like this:
// MemTotal: 1605324 kB
Matcher m = Pattern.compile("^MemTotal:\\s+([0-9]+) kB\\s*$")
.matcher(memTotal);
if (m.matches()) {
String kb = m.group(1);
if (kb != null) {
sTotalRAM = (Integer.parseInt(kb) / 1024);
Log.d(LOGTAG, "System memory: " + sTotalRAM + "MB.");
return sTotalRAM;
}
}
Log.w(LOGTAG, "Got unexpected MemTotal line: " + memTotal);
return sTotalRAM = 0;
} finally {
reader.close();
}
} catch (FileNotFoundException f) {
return sTotalRAM = 0;
} catch (IOException e) {
return sTotalRAM = 0;
}
}
public static boolean isLowMemoryPlatform() {
final int memSize = getMemSize();
// Fallback to false if we fail to read meminfo
// for some reason.
if (memSize == 0) {
Log.w(LOGTAG, "Could not compute system memory. Falling back to isLowMemoryPlatform = false.");
return false;
}
return memSize < LOW_MEMORY_THRESHOLD_MB;
}
}

View File

@ -96,6 +96,25 @@ Java_org_mozilla_gecko_GeckoAppShell_onSurfaceTextureFrameAvailable(JNIEnv * arg
#ifdef JNI_STUBS
typedef void (*Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure_t)(JNIEnv *, jclass);
static Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure_t f_Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure;
extern "C" NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure(JNIEnv * arg0, jclass arg1) {
if (!f_Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure) {
arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
"JNI Function called before it was loaded");
return ;
}
f_Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure(arg0, arg1);
}
#endif
#ifdef JNI_BINDINGS
xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure", &f_Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure);
#endif
#ifdef JNI_STUBS
typedef void (*Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash_t)(JNIEnv *, jclass, jstring);
static Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash_t f_Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash;
extern "C" NS_EXPORT void JNICALL

View File

@ -23,11 +23,11 @@ var popup = window.open("seek_with_sound.ogg");
popup.addEventListener("load", function onLoad() {
popup.removeEventListener("load", onLoad);
var video = getMediaElement(popup);
if (video.readyState >= video.HAVE_METADATA)
if (video.readyState >= video.HAVE_ENOUGH_DATA)
runTestVideo(video);
else {
video.addEventListener("loadedmetadata", function onLoadedMetaData() {
video.removeEventListener("loadedmetadata", onLoadedMetaData);
video.addEventListener("play", function onPlay() {
video.removeEventListener("play", onPlay);
runTestVideo(video);
});
}
@ -46,11 +46,11 @@ function runTestAudioPre() {
popup.addEventListener("load", function onLoad() {
popup.removeEventListener("load", onLoad);
var audio = getMediaElement(popup);
if (audio.readyState >= audio.HAVE_METADATA)
if (audio.readyState >= audio.HAVE_ENOUGH_DATA)
runTestAudio(audio);
else {
audio.addEventListener("loadedmetadata", function onLoadedMetaData() {
audio.removeEventListener("loadedmetadata", onLoadedMetaData);
audio.addEventListener("play", function onPlay() {
audio.removeEventListener("play", onPlay);
runTestAudio(audio);
})
}
@ -59,8 +59,9 @@ function runTestAudioPre() {
function runTestAudio(aAudio) {
var boundingRect = aAudio.getBoundingClientRect();
var isAndroid = (navigator.userAgent.indexOf("Android") !== -1);
var isAndroid = navigator.userAgent.contains("Android");
var expectedHeight = isAndroid ? 123 : 28;
info("User agent (help diagnose bug #943556): " + navigator.userAgent);
is(boundingRect.height, expectedHeight,
"Height of audio element should be " + expectedHeight + ", which is equal to the controls bar.");
popup.close();

View File

@ -404,7 +404,7 @@
<method name="toggleHistoryPopup">
<body><![CDATA[
if (!this.popup.mPopupOpen)
if (!this.popup.popupOpen)
this.showHistoryPopup();
else
this.closePopup();
@ -460,7 +460,7 @@
if (!this.disableKeyNavigation && !aEvent.ctrlKey && !aEvent.altKey) {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_TAB:
if (this.tabScrolling && this.popup.mPopupOpen)
if (this.tabScrolling && this.popup.popupOpen)
cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ?
KeyEvent.DOM_VK_UP :
KeyEvent.DOM_VK_DOWN);

View File

@ -1180,17 +1180,20 @@ WebGLProxy.prototype = {
* Returns the framebuffer property value for the specified WebGL parameter.
* If no framebuffer binding is available, null is returned.
*
* @param string type
* The framebuffer object attachment point, for example "COLOR_ATTACHMENT0".
* @param string name
* The WebGL parameter name, for example "BLEND_COLOR".
* The WebGL parameter name, for example "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME".
* If unspecified, defaults to "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE".
* @return any
* The corresponding parameter's value.
*/
_getFramebufferAttachmentParameter: function(type, name) {
_getFramebufferAttachmentParameter: function(type, name = "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE") {
if (!this._getParameter("FRAMEBUFFER_BINDING")) {
return null;
}
let gl = this._gl;
return gl.getFramebufferAttachmentParameter(gl.RENDERBUFFER, gl[type], gl[name]);
return gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl[type], gl[name]);
},
/**
@ -1281,9 +1284,19 @@ WebGLProxy.prototype = {
_enableHighlighting: function() {
let gl = this._gl;
// Avoid changing the blending params when rendering to a depth texture.
let format = this._getRenderbufferParameter("RENDERBUFFER_INTERNAL_FORMAT");
if (format == gl.DEPTH_COMPONENT16) {
// Avoid changing the blending params when "rendering to texture".
// Check drawing to a custom framebuffer bound to the default renderbuffer.
let hasFramebuffer = this._getParameter("FRAMEBUFFER_BINDING");
let hasRenderbuffer = this._getParameter("RENDERBUFFER_BINDING");
if (hasFramebuffer && !hasRenderbuffer) {
return;
}
// Check drawing to a depth or stencil component of the framebuffer.
let writesDepth = this._getFramebufferAttachmentParameter("DEPTH_ATTACHMENT");
let writesStencil = this._getFramebufferAttachmentParameter("STENCIL_ATTACHMENT");
if (writesDepth || writesStencil) {
return;
}

View File

@ -41,8 +41,7 @@
#include "nsPluginInstanceOwner.h"
#include "nsSurfaceTexture.h"
#include "GeckoProfiler.h"
#include "GeckoProfiler.h"
#include "nsMemoryPressure.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -839,6 +838,12 @@ Java_org_mozilla_gecko_GeckoAppShell_onSurfaceTextureFrameAvailable(JNIEnv* jenv
st->NotifyFrameAvailable();
}
NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure(JNIEnv* jenv, jclass)
{
NS_DispatchMemoryPressure(MemPressure_New);
}
NS_EXPORT jdouble JNICALL
Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime(JNIEnv *jenv, jclass jc)
{