mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c a=merge
This commit is contained in:
commit
6a521db2ac
@ -32,11 +32,7 @@ let SidebarUI = {
|
||||
this._title = document.getElementById("sidebar-title");
|
||||
this._splitter = document.getElementById("sidebar-splitter");
|
||||
|
||||
if (window.opener && !window.opener.closed &&
|
||||
window.opener.document.documentURIObject.schemeIs("chrome") &&
|
||||
PrivateBrowsingUtils.isWindowPrivate(window) == PrivateBrowsingUtils.isWindowPrivate(window.opener)) {
|
||||
this.adoptFromWindow(window.opener);
|
||||
} else {
|
||||
if (!this.adoptFromWindow(window.opener)) {
|
||||
let commandID = this._box.getAttribute("sidebarcommand");
|
||||
if (commandID) {
|
||||
let command = document.getElementById(commandID);
|
||||
@ -67,24 +63,40 @@ let SidebarUI = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Adopt the status of the sidebar from another window.
|
||||
* Try and adopt the status of the sidebar from another window.
|
||||
* @param {Window} sourceWindow - Window to use as a source for sidebar status.
|
||||
* @return true if we adopted the state, or false if the caller should
|
||||
* initialize the state itself.
|
||||
*/
|
||||
adoptFromWindow(sourceWindow) {
|
||||
// No source window, or it being closed, or not chrome, or in a different
|
||||
// private-browsing context means we can't adopt.
|
||||
if (!sourceWindow || sourceWindow.closed ||
|
||||
!sourceWindow.document.documentURIObject.schemeIs("chrome") ||
|
||||
PrivateBrowsingUtils.isWindowPrivate(window) != PrivateBrowsingUtils.isWindowPrivate(sourceWindow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the opener had a sidebar, open the same sidebar in our window.
|
||||
// The opener can be the hidden window too, if we're coming from the state
|
||||
// where no windows are open, and the hidden window has no sidebar box.
|
||||
let sourceUI = sourceWindow.SidebarUI;
|
||||
if (!sourceUI || sourceUI._box.hidden) {
|
||||
return;
|
||||
if (!sourceUI || !sourceUI._box) {
|
||||
// no source UI or no _box means we also can't adopt the state.
|
||||
return false;
|
||||
}
|
||||
if (sourceUI._box.hidden) {
|
||||
// just hidden means we have adopted the hidden state.
|
||||
return true;
|
||||
}
|
||||
|
||||
let commandID = sourceUI._box.getAttribute("sidebarcommand");
|
||||
let commandElem = document.getElementById(commandID);
|
||||
|
||||
// dynamically generated sidebars will fail this check.
|
||||
// dynamically generated sidebars will fail this check, but we still
|
||||
// consider it adopted.
|
||||
if (!commandElem) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
this._title.setAttribute("value",
|
||||
@ -101,6 +113,7 @@ let SidebarUI = {
|
||||
this._box.hidden = false;
|
||||
this._splitter.hidden = false;
|
||||
commandElem.setAttribute("checked", "true");
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -16,5 +16,7 @@ FIREFOX_PREFERENCES = {
|
||||
"loop.seenToS": "seen",
|
||||
|
||||
# this dialog is fragile, and likely to introduce intermittent failures
|
||||
"media.navigator.permission.disabled": True
|
||||
"media.navigator.permission.disabled": True,
|
||||
# Use fake streams only
|
||||
"media.navigator.streams.fake": True
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
* 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/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
|
||||
const nsICookiePermission = Components.interfaces.nsICookiePermission;
|
||||
|
||||
@ -17,12 +19,13 @@ function Permission(host, rawHost, type, capability)
|
||||
}
|
||||
|
||||
var gPermissionManager = {
|
||||
_type : "",
|
||||
_permissions : [],
|
||||
_pm : Components.classes["@mozilla.org/permissionmanager;1"]
|
||||
.getService(Components.interfaces.nsIPermissionManager),
|
||||
_bundle : null,
|
||||
_tree : null,
|
||||
_type : "",
|
||||
_permissions : [],
|
||||
_permissionsToAdd : new Map(),
|
||||
_permissionsToDelete : new Map(),
|
||||
_bundle : null,
|
||||
_tree : null,
|
||||
_observerRemoved : false,
|
||||
|
||||
_view: {
|
||||
_rowCount: 0,
|
||||
@ -82,38 +85,41 @@ var gPermissionManager = {
|
||||
var textbox = document.getElementById("url");
|
||||
var host = textbox.value.replace(/^\s*([-\w]*:\/+)?/, ""); // trim any leading space and scheme
|
||||
try {
|
||||
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
var uri = ioService.newURI("http://"+host, null, null);
|
||||
var uri = Services.io.newURI("http://"+host, null, null);
|
||||
host = uri.host;
|
||||
} catch(ex) {
|
||||
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
var message = this._bundle.getString("invalidURI");
|
||||
var title = this._bundle.getString("invalidURITitle");
|
||||
promptService.alert(window, title, message);
|
||||
Services.prompt.alert(window, title, message);
|
||||
return;
|
||||
}
|
||||
|
||||
var capabilityString = this._getCapabilityString(aCapability);
|
||||
|
||||
// check whether the permission already exists, if not, add it
|
||||
var exists = false;
|
||||
let hostExists = false;
|
||||
let capabilityExists = false;
|
||||
for (var i = 0; i < this._permissions.length; ++i) {
|
||||
if (this._permissions[i].rawHost == host) {
|
||||
// Avoid calling the permission manager if the capability settings are
|
||||
// the same. Otherwise allow the call to the permissions manager to
|
||||
// update the listbox for us.
|
||||
exists = this._permissions[i].capability == capabilityString;
|
||||
hostExists = true;
|
||||
capabilityExists = this._permissions[i].capability == capabilityString;
|
||||
if (!capabilityExists) {
|
||||
this._permissions[i].capability = capabilityString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
host = (host.charAt(0) == ".") ? host.substring(1,host.length) : host;
|
||||
var uri = ioService.newURI("http://" + host, null, null);
|
||||
this._pm.add(uri, this._type, aCapability);
|
||||
let permissionParams = {host: host, type: this._type, capability: aCapability};
|
||||
if (!hostExists) {
|
||||
this._permissionsToAdd.set(host, permissionParams);
|
||||
this._addPermission(permissionParams);
|
||||
}
|
||||
else if (!capabilityExists) {
|
||||
this._permissionsToAdd.set(host, permissionParams);
|
||||
this._handleCapabilityChange();
|
||||
}
|
||||
|
||||
textbox.value = "";
|
||||
textbox.focus();
|
||||
|
||||
@ -124,6 +130,49 @@ var gPermissionManager = {
|
||||
document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
|
||||
},
|
||||
|
||||
_removePermission: function(aPermission)
|
||||
{
|
||||
this._removePermissionFromList(aPermission.host);
|
||||
|
||||
// If this permission was added during this session, let's remove
|
||||
// it from the pending adds list to prevent calls to the
|
||||
// permission manager.
|
||||
let isNewPermission = this._permissionsToAdd.delete(aPermission.host);
|
||||
|
||||
if (!isNewPermission) {
|
||||
this._permissionsToDelete.set(aPermission.host, aPermission);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_handleCapabilityChange: function ()
|
||||
{
|
||||
// Re-do the sort, if the status changed from Block to Allow
|
||||
// or vice versa, since if we're sorted on status, we may no
|
||||
// longer be in order.
|
||||
if (this._lastPermissionSortColumn.id == "statusCol") {
|
||||
this._resortPermissions();
|
||||
}
|
||||
this._tree.treeBoxObject.invalidate();
|
||||
},
|
||||
|
||||
_addPermission: function(aPermission)
|
||||
{
|
||||
this._addPermissionToList(aPermission);
|
||||
++this._view._rowCount;
|
||||
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1);
|
||||
// Re-do the sort, since we inserted this new item at the end.
|
||||
this._resortPermissions();
|
||||
},
|
||||
|
||||
_resortPermissions: function()
|
||||
{
|
||||
gTreeUtils.sort(this._tree, this._view, this._permissions,
|
||||
this._permissionsComparator,
|
||||
this._lastPermissionSortColumn,
|
||||
this._lastPermissionSortAscending);
|
||||
},
|
||||
|
||||
onHostInput: function (aSiteField)
|
||||
{
|
||||
document.getElementById("btnSession").disabled = !aSiteField.value;
|
||||
@ -182,10 +231,8 @@ var gPermissionManager = {
|
||||
var urlLabel = document.getElementById("urlLabel");
|
||||
urlLabel.hidden = !urlFieldVisible;
|
||||
|
||||
var os = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
os.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type);
|
||||
os.addObserver(this, "perm-changed", false);
|
||||
Services.obs.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type);
|
||||
Services.obs.addObserver(this, "perm-changed", false);
|
||||
|
||||
this._loadPermissions();
|
||||
|
||||
@ -194,9 +241,11 @@ var gPermissionManager = {
|
||||
|
||||
uninit: function ()
|
||||
{
|
||||
var os = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
os.removeObserver(this, "perm-changed");
|
||||
if (!this._observerRemoved) {
|
||||
Services.obs.removeObserver(this, "perm-changed");
|
||||
|
||||
this._observerRemoved = true;
|
||||
}
|
||||
},
|
||||
|
||||
observe: function (aSubject, aTopic, aData)
|
||||
@ -209,14 +258,7 @@ var gPermissionManager = {
|
||||
return;
|
||||
|
||||
if (aData == "added") {
|
||||
this._addPermissionToList(permission);
|
||||
++this._view._rowCount;
|
||||
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1);
|
||||
// Re-do the sort, since we inserted this new item at the end.
|
||||
gTreeUtils.sort(this._tree, this._view, this._permissions,
|
||||
this._permissionsComparator,
|
||||
this._lastPermissionSortColumn,
|
||||
this._lastPermissionSortAscending);
|
||||
this._addPermission(permission);
|
||||
}
|
||||
else if (aData == "changed") {
|
||||
for (var i = 0; i < this._permissions.length; ++i) {
|
||||
@ -225,27 +267,10 @@ var gPermissionManager = {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Re-do the sort, if the status changed from Block to Allow
|
||||
// or vice versa, since if we're sorted on status, we may no
|
||||
// longer be in order.
|
||||
if (this._lastPermissionSortColumn.id == "statusCol") {
|
||||
gTreeUtils.sort(this._tree, this._view, this._permissions,
|
||||
this._permissionsComparator,
|
||||
this._lastPermissionSortColumn,
|
||||
this._lastPermissionSortAscending);
|
||||
}
|
||||
this._tree.treeBoxObject.invalidate();
|
||||
this._handleCapabilityChange();
|
||||
}
|
||||
else if (aData == "deleted") {
|
||||
for (var i = 0; i < this._permissions.length; i++) {
|
||||
if (this._permissions[i].host == permission.host) {
|
||||
this._permissions.splice(i, 1);
|
||||
this._view._rowCount--;
|
||||
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1);
|
||||
this._tree.treeBoxObject.invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._removePermissionFromList(permission);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -266,7 +291,7 @@ var gPermissionManager = {
|
||||
gTreeUtils.deleteSelectedItems(this._tree, this._view, this._permissions, removedPermissions);
|
||||
for (var i = 0; i < removedPermissions.length; ++i) {
|
||||
var p = removedPermissions[i];
|
||||
this._pm.remove(p.host, p.type);
|
||||
this._removePermission(p);
|
||||
}
|
||||
document.getElementById("removePermission").disabled = !this._permissions.length;
|
||||
document.getElementById("removeAllPermissions").disabled = !this._permissions.length;
|
||||
@ -280,7 +305,7 @@ var gPermissionManager = {
|
||||
gTreeUtils.deleteAll(this._tree, this._view, this._permissions, removedPermissions);
|
||||
for (var i = 0; i < removedPermissions.length; ++i) {
|
||||
var p = removedPermissions[i];
|
||||
this._pm.remove(p.host, p.type);
|
||||
this._removePermission(p);
|
||||
}
|
||||
document.getElementById("removePermission").disabled = true;
|
||||
document.getElementById("removeAllPermissions").disabled = true;
|
||||
@ -316,6 +341,25 @@ var gPermissionManager = {
|
||||
this._lastPermissionSortColumn = aColumn;
|
||||
},
|
||||
|
||||
onApplyChanges: function()
|
||||
{
|
||||
// Stop observing permission changes since we are about
|
||||
// to write out the pending adds/deletes and don't need
|
||||
// to update the UI
|
||||
this.uninit();
|
||||
|
||||
for (let permissionParams of this._permissionsToAdd.values()) {
|
||||
let uri = Services.io.newURI("http://" + permissionParams.host, null, null);
|
||||
Services.perms.add(uri, permissionParams.type, permissionParams.capability);
|
||||
}
|
||||
|
||||
for (let p of this._permissionsToDelete.values()) {
|
||||
Services.perms.remove(p.host, p.type);
|
||||
}
|
||||
|
||||
window.close();
|
||||
},
|
||||
|
||||
_loadPermissions: function ()
|
||||
{
|
||||
this._tree = document.getElementById("permissionsTree");
|
||||
@ -323,7 +367,7 @@ var gPermissionManager = {
|
||||
|
||||
// load permissions into a table
|
||||
var count = 0;
|
||||
var enumerator = this._pm.enumerator;
|
||||
var enumerator = Services.perms.enumerator;
|
||||
while (enumerator.hasMoreElements()) {
|
||||
var nextPermission = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
|
||||
this._addPermissionToList(nextPermission);
|
||||
@ -355,6 +399,19 @@ var gPermissionManager = {
|
||||
}
|
||||
},
|
||||
|
||||
_removePermissionFromList: function (aHost)
|
||||
{
|
||||
for (let i = 0; i < this._permissions.length; ++i) {
|
||||
if (this._permissions[i].host == aHost) {
|
||||
this._permissions.splice(i, 1);
|
||||
this._view._rowCount--;
|
||||
this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1);
|
||||
this._tree.treeBoxObject.invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setHost: function (aHost)
|
||||
{
|
||||
document.getElementById("url").value = aHost;
|
||||
|
@ -61,8 +61,8 @@
|
||||
<treechildren/>
|
||||
</tree>
|
||||
</vbox>
|
||||
<hbox align="end">
|
||||
<hbox class="actionButtons" flex="1">
|
||||
<vbox>
|
||||
<hbox class="actionButtons" align="left" flex="1">
|
||||
<button id="removePermission" disabled="true"
|
||||
accesskey="&removepermission.accesskey;"
|
||||
icon="remove" label="&removepermission.label;"
|
||||
@ -71,9 +71,13 @@
|
||||
icon="clear" label="&removeallpermissions.label;"
|
||||
accesskey="&removeallpermissions.accesskey;"
|
||||
oncommand="gPermissionManager.onAllPermissionsDeleted();"/>
|
||||
<spacer flex="1"/>
|
||||
<button oncommand="close();" icon="close"
|
||||
label="&button.close.label;" accesskey="&button.close.accesskey;"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<spacer flex="1"/>
|
||||
<hbox class="actionButtons" align="right" flex="1">
|
||||
<button oncommand="close();" icon="close"
|
||||
label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
|
||||
<button id="btnApplyChanges" oncommand="gPermissionManager.onApplyChanges();" icon="save"
|
||||
label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</window>
|
||||
|
@ -4,182 +4,197 @@
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
function prefWindowObserver(subject, topic, data) {
|
||||
if (topic != "domwindowopened")
|
||||
return;
|
||||
|
||||
Services.ww.unregisterNotification(this);
|
||||
|
||||
let win = subject.QueryInterface(Ci.nsIDOMEventTarget);
|
||||
|
||||
win.addEventListener("load", function(event) {
|
||||
let historyMode = event.target.getElementById("historyMode");
|
||||
historyMode.value = "custom";
|
||||
historyMode.doCommand();
|
||||
Services.ww.registerNotification(cookiesWindowObserver);
|
||||
event.target.getElementById("cookieExceptions").doCommand();
|
||||
}, false);
|
||||
}
|
||||
|
||||
function cookiesWindowObserver(subject, topic, data) {
|
||||
if (topic != "domwindowopened")
|
||||
return;
|
||||
|
||||
Services.ww.unregisterNotification(this);
|
||||
|
||||
let win = subject.QueryInterface(Ci.nsIDOMEventTarget);
|
||||
|
||||
win.addEventListener("load", function(event) {
|
||||
SimpleTest.executeSoon(function() windowLoad(event, win, dialog));
|
||||
}, false);
|
||||
}
|
||||
|
||||
Services.ww.registerNotification(prefWindowObserver);
|
||||
|
||||
let dialog = openDialog("chrome://browser/content/preferences/preferences.xul",
|
||||
"Preferences", "chrome,titlebar,toolbar,centerscreen,dialog=no",
|
||||
"panePrivacy");
|
||||
testRunner.runTests();
|
||||
}
|
||||
|
||||
function windowLoad(event, win, dialog) {
|
||||
let doc = event.target;
|
||||
let tree = doc.getElementById("permissionsTree");
|
||||
let statusCol = tree.treeBoxObject.columns.getColumnAt(1);
|
||||
let url = doc.getElementById("url");
|
||||
let btnAllow = doc.getElementById("btnAllow");
|
||||
let btnBlock = doc.getElementById("btnBlock");
|
||||
let btnRemove = doc.getElementById("removePermission");
|
||||
let pm = Cc["@mozilla.org/permissionmanager;1"]
|
||||
.getService(Ci.nsIPermissionManager);
|
||||
let ioService = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
const allowText = win.gPermissionManager._getCapabilityString(
|
||||
Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
const denyText = win.gPermissionManager._getCapabilityString(
|
||||
Ci.nsIPermissionManager.DENY_ACTION);
|
||||
const allow = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
const deny = Ci.nsIPermissionManager.DENY_ACTION;
|
||||
var testRunner = {
|
||||
|
||||
is(tree.view.rowCount, 0, "no cookie exceptions");
|
||||
tests:
|
||||
[
|
||||
{
|
||||
test: function(params) {
|
||||
params.url.value = "test.com";
|
||||
params.btnAllow.doCommand();
|
||||
is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
|
||||
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
|
||||
"permission text should be set correctly");
|
||||
params.btnApplyChanges.doCommand();
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "added",
|
||||
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
|
||||
},
|
||||
{
|
||||
test: function(params) {
|
||||
params.url.value = "test.com";
|
||||
params.btnBlock.doCommand();
|
||||
is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
|
||||
"permission should change to deny in UI");
|
||||
params.btnApplyChanges.doCommand();
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "changed",
|
||||
capability: Ci.nsIPermissionManager.DENY_ACTION }],
|
||||
},
|
||||
{
|
||||
test: function(params) {
|
||||
params.url.value = "test.com";
|
||||
params.btnAllow.doCommand();
|
||||
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
|
||||
"permission should revert back to allow");
|
||||
params.btnApplyChanges.doCommand();
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "changed",
|
||||
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
|
||||
},
|
||||
{
|
||||
test: function(params) {
|
||||
params.url.value = "test.com";
|
||||
params.btnRemove.doCommand();
|
||||
is(params.tree.view.rowCount, 0, "exception should be removed");
|
||||
params.btnApplyChanges.doCommand();
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "deleted" }],
|
||||
},
|
||||
{
|
||||
test: function(params) {
|
||||
let uri = params.ioService.newURI("http://test.com", null, null);
|
||||
params.pm.add(uri, "popup", Ci.nsIPermissionManager.DENY_ACTION);
|
||||
is(params.tree.view.rowCount, 0, "adding unrelated permission should not change display");
|
||||
params.btnApplyChanges.doCommand();
|
||||
},
|
||||
observances: [{ type: "popup", host: "test.com", data: "added",
|
||||
capability: Ci.nsIPermissionManager.DENY_ACTION }],
|
||||
cleanUp: function(params) {
|
||||
params.pm.remove("test.com", "popup");
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
let tests = [
|
||||
{
|
||||
test: function() {
|
||||
url.value = "test.com";
|
||||
btnAllow.doCommand();
|
||||
is(tree.view.rowCount, 1, "added exception shows up in treeview");
|
||||
is(tree.view.getCellText(0, statusCol), allowText,
|
||||
"permission text should be set correctly");
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "added",
|
||||
capability: allow }]
|
||||
},
|
||||
{
|
||||
test: function() {
|
||||
url.value = "test.com";
|
||||
btnBlock.doCommand();
|
||||
is(tree.view.getCellText(0, statusCol), denyText,
|
||||
"permission should change to deny in UI");
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "changed",
|
||||
capability: deny }],
|
||||
},
|
||||
{
|
||||
test: function() {
|
||||
url.value = "test.com";
|
||||
btnAllow.doCommand();
|
||||
is(tree.view.getCellText(0, statusCol), allowText,
|
||||
"permission should revert back to allow");
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "changed",
|
||||
capability: allow }],
|
||||
},
|
||||
{
|
||||
test: function() {
|
||||
url.value = "test.com";
|
||||
btnRemove.doCommand();
|
||||
is(tree.view.rowCount, 0, "exception should be removed");
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "deleted" }],
|
||||
},
|
||||
{
|
||||
test: function() {
|
||||
let uri = ioService.newURI("http://test.com", null, null);
|
||||
pm.add(uri, "popup", Ci.nsIPermissionManager.DENY_ACTION);
|
||||
is(tree.view.rowCount, 0, "adding unrelated permission should not change display");
|
||||
},
|
||||
observances: [{ type: "popup", host: "test.com", data: "added",
|
||||
capability: deny }],
|
||||
cleanUp: function() {
|
||||
pm.remove("test.com", "popup");
|
||||
},
|
||||
},
|
||||
{
|
||||
test: function() {
|
||||
url.value = "test.com";
|
||||
btnAllow.doCommand();
|
||||
pm.remove("test.com", "cookie");
|
||||
is(tree.view.rowCount, 0, "display should update when cookie permission is deleted");
|
||||
},
|
||||
observances: [{ type: "cookie", host: "test.com", data: "added",
|
||||
capability: allow },
|
||||
{ type: "cookie", host: "test.com", data: "deleted" }]
|
||||
},
|
||||
];
|
||||
_currentTest: -1,
|
||||
|
||||
let permObserver = {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic != "perm-changed")
|
||||
return;
|
||||
runTests: function() {
|
||||
this._currentTest++;
|
||||
|
||||
if (tests[currentTest].observances.length == 0) {
|
||||
// Should fail here as we are not expecting a notification, but we don't.
|
||||
// See bug 1063410.
|
||||
return;
|
||||
info("Running test #" + (this._currentTest + 1) + "\n");
|
||||
let that = this;
|
||||
let p = this.runCurrentTest();
|
||||
p.then(function() {
|
||||
if (that._currentTest == that.tests.length - 1) {
|
||||
finish();
|
||||
}
|
||||
|
||||
let permission = aSubject.QueryInterface(Ci.nsIPermission);
|
||||
let expected = tests[currentTest].observances.shift();
|
||||
|
||||
is(aData, expected.data, "type of message should be the same");
|
||||
for each (let prop in ["type", "host", "capability"]) {
|
||||
if (expected[prop])
|
||||
is(permission[prop], expected[prop],
|
||||
"property: \"" + prop + "\" should be equal");
|
||||
else {
|
||||
that.runTests();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
if (tests[currentTest].observances.length == 0) {
|
||||
SimpleTest.executeSoon(function() {
|
||||
if (tests[currentTest].cleanUp)
|
||||
tests[currentTest].cleanUp();
|
||||
runCurrentTest: function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
runNextTest();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
let helperFunctions = {
|
||||
|
||||
let os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
prefWindowObserver: function(subject, topic, data) {
|
||||
if (topic != "domwindowopened")
|
||||
return;
|
||||
|
||||
os.addObserver(permObserver, "perm-changed", false);
|
||||
Services.ww.unregisterNotification(helperFunctions.prefWindowObserver);
|
||||
|
||||
var currentTest = -1;
|
||||
function runNextTest() {
|
||||
currentTest++;
|
||||
if (currentTest == tests.length) {
|
||||
os.removeObserver(permObserver, "perm-changed");
|
||||
win.close();
|
||||
dialog.close();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let win = subject.QueryInterface(Ci.nsIDOMEventTarget);
|
||||
|
||||
info("Running test #" + (currentTest + 1) + "\n");
|
||||
tests[currentTest].test();
|
||||
if (!tests[currentTest].observances)
|
||||
runNextTest();
|
||||
}
|
||||
win.addEventListener("load", function(event) {
|
||||
let historyMode = event.target.getElementById("historyMode");
|
||||
historyMode.value = "custom";
|
||||
historyMode.doCommand();
|
||||
Services.ww.registerNotification(helperFunctions.cookiesWindowObserver);
|
||||
event.target.getElementById("cookieExceptions").doCommand();
|
||||
}, false);
|
||||
},
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
cookiesWindowObserver: function(subject, topic, data) {
|
||||
if (topic != "domwindowopened")
|
||||
return;
|
||||
|
||||
Services.ww.unregisterNotification(helperFunctions.cookiesWindowObserver);
|
||||
|
||||
let win = subject.QueryInterface(Ci.nsIDOMEventTarget);
|
||||
|
||||
win.addEventListener("load", function(event) {
|
||||
SimpleTest.executeSoon(function() helperFunctions.windowLoad(event, win));
|
||||
}, false);
|
||||
},
|
||||
|
||||
windowLoad: function(event, win) {
|
||||
let params = {
|
||||
doc: event.target,
|
||||
tree: event.target.getElementById("permissionsTree"),
|
||||
statusCol: event.target.getElementById("permissionsTree").treeBoxObject.columns.getColumnAt(1),
|
||||
url: event.target.getElementById("url"),
|
||||
btnAllow: event.target.getElementById("btnAllow"),
|
||||
btnBlock: event.target.getElementById("btnBlock"),
|
||||
btnApplyChanges: event.target.getElementById("btnApplyChanges"),
|
||||
btnRemove: event.target.getElementById("removePermission"),
|
||||
pm: Cc["@mozilla.org/permissionmanager;1"]
|
||||
.getService(Ci.nsIPermissionManager),
|
||||
ioService: Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService),
|
||||
allowText: win.gPermissionManager._getCapabilityString(
|
||||
Ci.nsIPermissionManager.ALLOW_ACTION),
|
||||
denyText: win.gPermissionManager._getCapabilityString(
|
||||
Ci.nsIPermissionManager.DENY_ACTION),
|
||||
allow: Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
deny: Ci.nsIPermissionManager.DENY_ACTION,
|
||||
};
|
||||
|
||||
let permObserver = {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic != "perm-changed")
|
||||
return;
|
||||
|
||||
if (testRunner.tests[testRunner._currentTest].observances.length == 0) {
|
||||
// Should fail here as we are not expecting a notification, but we don't.
|
||||
// See bug 1063410.
|
||||
return;
|
||||
}
|
||||
|
||||
let permission = aSubject.QueryInterface(Ci.nsIPermission);
|
||||
let expected = testRunner.tests[testRunner._currentTest].observances.shift();
|
||||
|
||||
is(aData, expected.data, "type of message should be the same");
|
||||
for each (let prop in ["type", "host", "capability"]) {
|
||||
if (expected[prop])
|
||||
is(permission[prop], expected[prop],
|
||||
"property: \"" + prop + "\" should be equal");
|
||||
}
|
||||
|
||||
os.removeObserver(permObserver, "perm-changed");
|
||||
|
||||
if (testRunner.tests[testRunner._currentTest].cleanup) {
|
||||
testRunner.tests[testRunner._currentTest].cleanup();
|
||||
}
|
||||
|
||||
testRunner.dialog.close(params);
|
||||
win.close();
|
||||
resolve();
|
||||
},
|
||||
};
|
||||
|
||||
let os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
|
||||
os.addObserver(permObserver, "perm-changed", false);
|
||||
|
||||
if (testRunner._currentTest == 0) {
|
||||
is(params.tree.view.rowCount, 0, "no cookie exceptions");
|
||||
}
|
||||
|
||||
testRunner.tests[testRunner._currentTest].test(params);
|
||||
},
|
||||
};
|
||||
|
||||
Services.ww.registerNotification(helperFunctions.prefWindowObserver);
|
||||
|
||||
testRunner.dialog = openDialog("chrome://browser/content/preferences/preferences.xul",
|
||||
"Preferences", "chrome,titlebar,toolbar,centerscreen,dialog=no",
|
||||
"panePrivacy");
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -12,8 +12,9 @@ const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
let XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
|
||||
let strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties");
|
||||
|
||||
function AppValidator(project) {
|
||||
this.project = project;
|
||||
function AppValidator({ type, location }) {
|
||||
this.type = type;
|
||||
this.location = location;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
}
|
||||
@ -27,7 +28,7 @@ AppValidator.prototype.warning = function (message) {
|
||||
};
|
||||
|
||||
AppValidator.prototype._getPackagedManifestFile = function () {
|
||||
let manifestFile = FileUtils.File(this.project.location);
|
||||
let manifestFile = FileUtils.File(this.location);
|
||||
if (!manifestFile.exists()) {
|
||||
this.error(strings.GetStringFromName("validator.nonExistingFolder"));
|
||||
return null;
|
||||
@ -149,12 +150,12 @@ AppValidator.prototype._fetchManifest = function (manifestURL) {
|
||||
|
||||
AppValidator.prototype._getManifest = function () {
|
||||
let manifestURL;
|
||||
if (this.project.type == "packaged") {
|
||||
if (this.type == "packaged") {
|
||||
manifestURL = this._getPackagedManifestURL();
|
||||
if (!manifestURL)
|
||||
return promise.resolve(null);
|
||||
} else if (this.project.type == "hosted") {
|
||||
manifestURL = this.project.location;
|
||||
} else if (this.type == "hosted") {
|
||||
manifestURL = this.location;
|
||||
try {
|
||||
Services.io.newURI(manifestURL, null, null);
|
||||
} catch(e) {
|
||||
@ -162,7 +163,7 @@ AppValidator.prototype._getManifest = function () {
|
||||
return promise.resolve(null);
|
||||
}
|
||||
} else {
|
||||
this.error(strings.formatStringFromName("validator.invalidProjectType", [this.project.type], 1));
|
||||
this.error(strings.formatStringFromName("validator.invalidProjectType", [this.type], 1));
|
||||
return promise.resolve(null);
|
||||
}
|
||||
return this._fetchManifest(manifestURL);
|
||||
@ -181,11 +182,11 @@ AppValidator.prototype.validateManifest = function (manifest) {
|
||||
};
|
||||
|
||||
AppValidator.prototype._getOriginURL = function () {
|
||||
if (this.project.type == "packaged") {
|
||||
if (this.type == "packaged") {
|
||||
let manifestURL = Services.io.newURI(this.manifestURL, null, null);
|
||||
return Services.io.newURI(".", null, manifestURL).spec;
|
||||
} else if (this.project.type == "hosted") {
|
||||
return Services.io.newURI(this.project.location, null, null).prePath;
|
||||
} else if (this.type == "hosted") {
|
||||
return Services.io.newURI(this.location, null, null).prePath;
|
||||
}
|
||||
};
|
||||
|
||||
@ -203,9 +204,9 @@ AppValidator.prototype.validateLaunchPath = function (manifest) {
|
||||
}
|
||||
let origin = this._getOriginURL();
|
||||
let path;
|
||||
if (this.project.type == "packaged") {
|
||||
if (this.type == "packaged") {
|
||||
path = "." + ( manifest.launch_path || "/index.html" );
|
||||
} else if (this.project.type == "hosted") {
|
||||
} else if (this.type == "hosted") {
|
||||
path = manifest.launch_path || "/";
|
||||
}
|
||||
let indexURL;
|
||||
@ -251,7 +252,7 @@ AppValidator.prototype.validateType = function (manifest) {
|
||||
let appType = manifest.type || "web";
|
||||
if (["web", "trusted", "privileged", "certified"].indexOf(appType) === -1) {
|
||||
this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1));
|
||||
} else if (this.project.type == "hosted" &&
|
||||
} else if (this.type == "hosted" &&
|
||||
["certified", "privileged"].indexOf(appType) !== -1) {
|
||||
this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1));
|
||||
}
|
||||
|
@ -109,7 +109,7 @@
|
||||
let validator = createPackaged("wrong-launch-path");
|
||||
validator.validate().then(() => {
|
||||
is(validator.errors.length, 1, "app with wrong path got an error");
|
||||
let file = nsFile(validator.project.location);
|
||||
let file = nsFile(validator.location);
|
||||
file.append("wrong-path.html");
|
||||
let url = Services.io.newFileURI(file);
|
||||
is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPath", [url.spec], 1),
|
||||
|
@ -9,8 +9,8 @@
|
||||
|
||||
// Expected values:
|
||||
let res1 = [
|
||||
{selector: "#element-size", value: "160" + "\u00D7" + "160"},
|
||||
{selector: ".size > span", value: "100" + "\u00D7" + "100"},
|
||||
{selector: "#element-size", value: "160" + "\u00D7" + "160.117"},
|
||||
{selector: ".size > span", value: "100" + "\u00D7" + "100.117"},
|
||||
{selector: ".margin.top > span", value: 30},
|
||||
{selector: ".margin.left > span", value: "auto"},
|
||||
{selector: ".margin.bottom > span", value: 30},
|
||||
@ -43,7 +43,7 @@ let res2 = [
|
||||
];
|
||||
|
||||
add_task(function*() {
|
||||
let style = "div { position: absolute; top: 42px; left: 42px; height: 100px; width: 100px; border: 10px solid black; padding: 20px; margin: 30px auto;}";
|
||||
let style = "div { position: absolute; top: 42px; left: 42px; height: 100.111px; width: 100px; border: 10px solid black; padding: 20px; margin: 30px auto;}";
|
||||
let html = "<style>" + style + "</style><div></div>"
|
||||
|
||||
yield addTab("data:text/html," + encodeURIComponent(html));
|
||||
|
@ -422,7 +422,7 @@ LayoutView.prototype = {
|
||||
// might be missing.
|
||||
continue;
|
||||
}
|
||||
let parsedValue = parseInt(layout[property]);
|
||||
let parsedValue = parseFloat(layout[property]);
|
||||
if (Number.isNaN(parsedValue)) {
|
||||
// Not a number. We use the raw string.
|
||||
// Useful for "position" for example.
|
||||
@ -451,9 +451,10 @@ LayoutView.prototype = {
|
||||
|
||||
width -= this.map.borderLeft.value + this.map.borderRight.value +
|
||||
this.map.paddingLeft.value + this.map.paddingRight.value;
|
||||
|
||||
width = parseFloat(width.toPrecision(6));
|
||||
height -= this.map.borderTop.value + this.map.borderBottom.value +
|
||||
this.map.paddingTop.value + this.map.paddingBottom.value;
|
||||
height = parseFloat(height.toPrecision(6));
|
||||
|
||||
let newValue = width + "\u00D7" + height;
|
||||
if (this.sizeLabel.textContent != newValue) {
|
||||
|
@ -129,6 +129,8 @@ function updateUI() {
|
||||
warningsNode.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
AppManager.update("details");
|
||||
}
|
||||
|
||||
function showPrepackageLog() {
|
||||
|
@ -153,11 +153,11 @@ function doOK() {
|
||||
target.remove(false);
|
||||
AppProjects.addPackaged(folder).then((project) => {
|
||||
window.arguments[0].location = project.location;
|
||||
AppManager.validateProject(project).then(() => {
|
||||
AppManager.validateAndUpdateProject(project).then(() => {
|
||||
if (project.manifest) {
|
||||
project.manifest.name = projectName;
|
||||
AppManager.writeManifest(project).then(() => {
|
||||
AppManager.validateProject(project).then(
|
||||
AppManager.validateAndUpdateProject(project).then(
|
||||
() => {window.close()}, bail)
|
||||
}, bail)
|
||||
} else {
|
||||
|
@ -134,7 +134,7 @@ let UI = {
|
||||
AppManager.selectedProject.type != "mainProcess" &&
|
||||
AppManager.selectedProject.type != "runtimeApp" &&
|
||||
AppManager.selectedProject.type != "tab") {
|
||||
AppManager.validateProject(AppManager.selectedProject);
|
||||
AppManager.validateAndUpdateProject(AppManager.selectedProject);
|
||||
}
|
||||
|
||||
// Hook to display promotional Developer Edition doorhanger. Only displayed once.
|
||||
@ -567,7 +567,7 @@ let UI = {
|
||||
menuindex: 1
|
||||
});
|
||||
this.projecteditor.on("onEditorSave", (editor, resource) => {
|
||||
AppManager.validateProject(AppManager.selectedProject);
|
||||
AppManager.validateAndUpdateProject(AppManager.selectedProject);
|
||||
});
|
||||
return this.projecteditor.loaded;
|
||||
},
|
||||
@ -667,9 +667,6 @@ let UI = {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate project
|
||||
yield AppManager.validateProject(project);
|
||||
|
||||
// Select project
|
||||
AppManager.selectedProject = project;
|
||||
}),
|
||||
@ -1045,9 +1042,6 @@ let Cmds = {
|
||||
// Retrieve added project
|
||||
let project = AppProjects.get(ret.location);
|
||||
|
||||
// Validate project
|
||||
yield AppManager.validateProject(project);
|
||||
|
||||
// Select project
|
||||
AppManager.selectedProject = project;
|
||||
|
||||
@ -1106,7 +1100,7 @@ let Cmds = {
|
||||
// The result of the validation process (storing names, icons, …) is not stored in
|
||||
// the IndexedDB database when App Manager v1 is used.
|
||||
// We need to run the validation again and update the name and icon of the app.
|
||||
AppManager.validateProject(project).then(() => {
|
||||
AppManager.validateAndUpdateProject(project).then(() => {
|
||||
panelItemNode.setAttribute("label", project.name);
|
||||
panelItemNode.setAttribute("image", project.icon);
|
||||
});
|
||||
|
@ -278,36 +278,36 @@ let AppManager = exports.AppManager = {
|
||||
},
|
||||
|
||||
_selectedProject: null,
|
||||
set selectedProject(value) {
|
||||
set selectedProject(project) {
|
||||
// A regular comparison still sees a difference when equal in some cases
|
||||
if (JSON.stringify(this._selectedProject) !==
|
||||
JSON.stringify(value)) {
|
||||
|
||||
let cancelled = false;
|
||||
this.update("before-project", { cancel: () => { cancelled = true; } });
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectedProject = value;
|
||||
|
||||
// Clear out tab store's selected state, if any
|
||||
this.tabStore.selectedTab = null;
|
||||
|
||||
if (this.selectedProject) {
|
||||
if (this.selectedProject.type == "packaged" ||
|
||||
this.selectedProject.type == "hosted") {
|
||||
this.validateProject(this.selectedProject);
|
||||
}
|
||||
if (this.selectedProject.type == "tab") {
|
||||
this.tabStore.selectedTab = this.selectedProject.app;
|
||||
}
|
||||
}
|
||||
|
||||
this.update("project");
|
||||
|
||||
this.checkIfProjectIsRunning();
|
||||
if (JSON.stringify(this._selectedProject) ===
|
||||
JSON.stringify(project)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
this.update("before-project", { cancel: () => { cancelled = true; } });
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectedProject = project;
|
||||
|
||||
// Clear out tab store's selected state, if any
|
||||
this.tabStore.selectedTab = null;
|
||||
|
||||
if (project) {
|
||||
if (project.type == "packaged" ||
|
||||
project.type == "hosted") {
|
||||
this.validateAndUpdateProject(project);
|
||||
}
|
||||
if (project.type == "tab") {
|
||||
this.tabStore.selectedTab = project.app;
|
||||
}
|
||||
}
|
||||
|
||||
this.update("project");
|
||||
this.checkIfProjectIsRunning();
|
||||
},
|
||||
get selectedProject() {
|
||||
return this._selectedProject;
|
||||
@ -323,6 +323,19 @@ let AppManager = exports.AppManager = {
|
||||
return AppProjects.remove(location);
|
||||
},
|
||||
|
||||
packageProject: Task.async(function*(project) {
|
||||
if (!project) {
|
||||
return;
|
||||
}
|
||||
if (project.type == "packaged" ||
|
||||
project.type == "hosted") {
|
||||
yield ProjectBuilding.build({
|
||||
project: project,
|
||||
logger: this.update.bind(this, "pre-package")
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
_selectedRuntime: null,
|
||||
set selectedRuntime(value) {
|
||||
this._selectedRuntime = value;
|
||||
@ -481,12 +494,9 @@ let AppManager = exports.AppManager = {
|
||||
return Task.spawn(function* () {
|
||||
let self = AppManager;
|
||||
|
||||
let packageDir = yield ProjectBuilding.build({
|
||||
project: project,
|
||||
logger: self.update.bind(self, "pre-package")
|
||||
});
|
||||
|
||||
yield self.validateProject(project);
|
||||
// Package and validate project
|
||||
yield self.packageProject(project);
|
||||
yield self.validateAndUpdateProject(project);
|
||||
|
||||
if (project.errorsCount > 0) {
|
||||
self.reportError("error_cantInstallValidationErrors");
|
||||
@ -501,7 +511,7 @@ let AppManager = exports.AppManager = {
|
||||
|
||||
let response;
|
||||
if (project.type == "packaged") {
|
||||
packageDir = packageDir || project.location;
|
||||
let packageDir = yield ProjectBuilding.getPackageDir(project);
|
||||
console.log("Installing app from " + packageDir);
|
||||
|
||||
response = yield self._appsFront.installPackaged(packageDir,
|
||||
@ -557,14 +567,20 @@ let AppManager = exports.AppManager = {
|
||||
|
||||
/* PROJECT VALIDATION */
|
||||
|
||||
validateProject: function(project) {
|
||||
validateAndUpdateProject: function(project) {
|
||||
if (!project) {
|
||||
return promise.reject();
|
||||
}
|
||||
|
||||
return Task.spawn(function* () {
|
||||
|
||||
let validation = new AppValidator(project);
|
||||
let packageDir = yield ProjectBuilding.getPackageDir(project);
|
||||
let validation = new AppValidator({
|
||||
type: project.type,
|
||||
// Build process may place the manifest in a non-root directory
|
||||
location: packageDir
|
||||
});
|
||||
|
||||
yield validation.validate();
|
||||
|
||||
if (validation.manifest) {
|
||||
@ -584,7 +600,7 @@ let AppManager = exports.AppManager = {
|
||||
let origin = Services.io.newURI(manifestURL.prePath, null, null);
|
||||
project.icon = Services.io.newURI(iconPath, null, origin).spec;
|
||||
} else if (project.type == "packaged") {
|
||||
let projectFolder = FileUtils.File(project.location);
|
||||
let projectFolder = FileUtils.File(packageDir);
|
||||
let folderURI = Services.io.newFileURI(projectFolder).spec;
|
||||
project.icon = folderURI + iconPath.replace(/^\/|\\/, "");
|
||||
}
|
||||
|
@ -14,10 +14,10 @@ const ProjectBuilding = exports.ProjectBuilding = {
|
||||
let manifestPath = OS.Path.join(project.location, "package.json");
|
||||
let exists = yield OS.File.exists(manifestPath);
|
||||
if (!exists) {
|
||||
return;
|
||||
// No explicit manifest, try to generate one if possible
|
||||
return this.generatePackageManifest(project);
|
||||
}
|
||||
|
||||
let Decoder = new TextDecoder();
|
||||
let data = yield OS.File.read(manifestPath);
|
||||
data = new TextDecoder().decode(data);
|
||||
let manifest;
|
||||
@ -30,6 +30,31 @@ const ProjectBuilding = exports.ProjectBuilding = {
|
||||
return manifest;
|
||||
}),
|
||||
|
||||
/**
|
||||
* For common frameworks in the community, attempt to detect the build
|
||||
* settings if none are defined. This makes it much easier to get started
|
||||
* with WebIDE. Later on, perhaps an add-on could define such things for
|
||||
* different frameworks.
|
||||
*/
|
||||
generatePackageManifest: Task.async(function*(project) {
|
||||
// Cordova
|
||||
let cordovaConfigPath = OS.Path.join(project.location, "config.xml");
|
||||
let exists = yield OS.File.exists(cordovaConfigPath);
|
||||
if (!exists) {
|
||||
return;
|
||||
}
|
||||
let data = yield OS.File.read(cordovaConfigPath);
|
||||
data = new TextDecoder().decode(data);
|
||||
if (data.contains("cordova.apache.org")) {
|
||||
return {
|
||||
"webide": {
|
||||
"prepackage": "cordova prepare",
|
||||
"packageDir": "./platforms/firefoxos/www"
|
||||
}
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
hasPrepackage: Task.async(function* (project) {
|
||||
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
|
||||
return manifest && manifest.webide && "prepackage" in manifest.webide;
|
||||
@ -37,22 +62,19 @@ const ProjectBuilding = exports.ProjectBuilding = {
|
||||
|
||||
// If the app depends on some build step, run it before pushing the app
|
||||
build: Task.async(function* ({ project, logger }) {
|
||||
if (!this.hasPrepackage(project)) {
|
||||
if (!(yield this.hasPrepackage(project))) {
|
||||
return;
|
||||
}
|
||||
|
||||
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
|
||||
|
||||
logger("start");
|
||||
let packageDir;
|
||||
try {
|
||||
packageDir = yield this._build(project, manifest, logger);
|
||||
yield this._build(project, manifest, logger);
|
||||
logger("succeed");
|
||||
} catch(e) {
|
||||
logger("failed", e);
|
||||
}
|
||||
|
||||
return packageDir;
|
||||
}),
|
||||
|
||||
_build: Task.async(function* (project, manifest, logger) {
|
||||
@ -61,6 +83,15 @@ const ProjectBuilding = exports.ProjectBuilding = {
|
||||
|
||||
let command, cwd, args = [], env = [];
|
||||
|
||||
// Copy frequently used env vars
|
||||
let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
["HOME", "PATH"].forEach(key => {
|
||||
let value = envService.get(key);
|
||||
if (value) {
|
||||
env.push(key + "=" + value);
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof(manifest.prepackage) === "string") {
|
||||
command = manifest.prepackage.replace(/%project%/g, project.location);
|
||||
} else if (manifest.prepackage.command) {
|
||||
@ -69,16 +100,9 @@ const ProjectBuilding = exports.ProjectBuilding = {
|
||||
args = manifest.prepackage.args || [];
|
||||
args = args.map(a => a.replace(/%project%/g, project.location));
|
||||
|
||||
env = manifest.prepackage.env || [];
|
||||
env = env.concat(manifest.prepackage.env || []);
|
||||
env = env.map(a => a.replace(/%project%/g, project.location));
|
||||
|
||||
// Gaia build system crashes if HOME env variable isn't set...
|
||||
let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
let home = envService.get("HOME");
|
||||
if (home) {
|
||||
env.push("HOME=" + home);
|
||||
}
|
||||
|
||||
if (manifest.prepackage.cwd) {
|
||||
// Normalize path for Windows support (converts / to \)
|
||||
let path = OS.Path.normalize(manifest.prepackage.cwd);
|
||||
@ -110,7 +134,6 @@ const ProjectBuilding = exports.ProjectBuilding = {
|
||||
// Otherwise, on Linux and Mac, SHELL env variable should refer to
|
||||
// the user chosen shell program.
|
||||
// (We do not check for OS, as on windows, with cygwin, ComSpec isn't set)
|
||||
let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||
let shell = envService.get("ComSpec") || envService.get("SHELL");
|
||||
args.unshift(command);
|
||||
|
||||
@ -155,17 +178,22 @@ const ProjectBuilding = exports.ProjectBuilding = {
|
||||
throw new Error("Unable to run pre-package command '" + command + "' " +
|
||||
args.join(" ") + ":\n" + (e.message || e));
|
||||
}
|
||||
|
||||
if (manifest.packageDir) {
|
||||
let packageDir = OS.Path.join(project.location, manifest.packageDir);
|
||||
// On Windows, replace / by \\
|
||||
packageDir = OS.Path.normalize(packageDir);
|
||||
let exists = yield OS.File.exists(packageDir);
|
||||
if (exists) {
|
||||
return packageDir;
|
||||
}
|
||||
throw new Error("Unable to resolve application package directory: '" + manifest.packageDir + "'");
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
getPackageDir: Task.async(function*(project) {
|
||||
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
|
||||
if (!manifest || !manifest.webide || !manifest.webide.packageDir) {
|
||||
return project.location;
|
||||
}
|
||||
manifest = manifest.webide;
|
||||
|
||||
let packageDir = OS.Path.join(project.location, manifest.packageDir);
|
||||
// On Windows, replace / by \\
|
||||
packageDir = OS.Path.normalize(packageDir);
|
||||
let exists = yield OS.File.exists(packageDir);
|
||||
if (exists) {
|
||||
return packageDir;
|
||||
}
|
||||
throw new Error("Unable to resolve application package directory: '" + manifest.packageDir + "'");
|
||||
})
|
||||
};
|
||||
|
@ -103,8 +103,10 @@ function nextTick() {
|
||||
}
|
||||
|
||||
function waitForUpdate(win, update) {
|
||||
info("Wait: " + update);
|
||||
let deferred = promise.defer();
|
||||
win.AppManager.on("app-manager-update", function onUpdate(e, what) {
|
||||
info("Got: " + what);
|
||||
if (what !== update) {
|
||||
return;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
let AppManager = win.AppManager;
|
||||
|
||||
function isProjectMarkedAsValid() {
|
||||
let details = win.UI.projecteditor.window.frames[0];
|
||||
let details = win.frames[0];
|
||||
return !details.document.body.classList.contains("error");
|
||||
}
|
||||
|
||||
@ -40,13 +40,10 @@
|
||||
let packagedAppLocation = getTestFilePath("build_app" + testSuffix + "1");
|
||||
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "details");
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
|
||||
let deferred = promise.defer();
|
||||
win.UI.projecteditor.once("onEditorCreated", deferred.resolve);
|
||||
yield deferred.promise;
|
||||
|
||||
ok(!project.manifest, "manifest includes name");
|
||||
is(project.name, "--", "Display name uses manifest name");
|
||||
|
||||
@ -55,18 +52,19 @@
|
||||
loggedMessages.push(msg);
|
||||
}
|
||||
|
||||
let packageDir = yield ProjectBuilding.build({
|
||||
yield ProjectBuilding.build({
|
||||
project,
|
||||
logger
|
||||
});
|
||||
ok(!packageDir, "no custom packagedir");
|
||||
let packageDir = yield ProjectBuilding.getPackageDir(project);
|
||||
is(packageDir, packagedAppLocation, "no custom packagedir");
|
||||
is(loggedMessages[0], "start", "log messages are correct");
|
||||
ok(loggedMessages[1].indexOf("Running pre-package hook") != -1, "log messages are correct");
|
||||
is(loggedMessages[2], "Terminated with error code: 0", "log messages are correct");
|
||||
is(loggedMessages[3], "succeed", "log messages are correct");
|
||||
|
||||
// Trigger validation
|
||||
yield AppManager.validateProject(AppManager.selectedProject);
|
||||
yield AppManager.validateAndUpdateProject(AppManager.selectedProject);
|
||||
yield nextTick();
|
||||
|
||||
ok("name" in project.manifest, "manifest includes name");
|
||||
@ -79,14 +77,16 @@
|
||||
packagedAppLocation = getTestFilePath("build_app" + testSuffix + "2");
|
||||
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
project = win.AppManager.selectedProject;
|
||||
|
||||
loggedMessages = [];
|
||||
packageDir = yield ProjectBuilding.build({
|
||||
yield ProjectBuilding.build({
|
||||
project,
|
||||
logger
|
||||
});
|
||||
packageDir = yield ProjectBuilding.getPackageDir(project);
|
||||
is(OS.Path.normalize(packageDir),
|
||||
OS.Path.join(packagedAppLocation, "stage"), "custom packagedir");
|
||||
is(loggedMessages[0], "start", "log messages are correct");
|
||||
@ -96,6 +96,7 @@
|
||||
|
||||
// Switch to the package dir in order to verify the generated webapp.manifest
|
||||
yield win.Cmds.importPackagedApp(packageDir);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
project = win.AppManager.selectedProject;
|
||||
|
||||
@ -115,5 +116,3 @@
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
@ -28,14 +28,17 @@
|
||||
|
||||
info("to call importPackagedApp(" + packagedAppLocation + ")");
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
yield nextTick();
|
||||
|
||||
info("to call importHostedApp(" + hostedAppManifest + ")");
|
||||
yield win.Cmds.importHostedApp(hostedAppManifest);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
yield nextTick();
|
||||
|
||||
info("to call importPackagedApp(" + packagedAppLocation + ") again");
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
is(project.location, packagedAppLocation, "Correctly reselected existing packaged app.");
|
||||
@ -43,6 +46,7 @@
|
||||
|
||||
info("to call importHostedApp(" + hostedAppManifest + ") again");
|
||||
yield win.Cmds.importHostedApp(hostedAppManifest);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
project = win.AppManager.selectedProject;
|
||||
is(project.location, hostedAppManifest, "Correctly reselected existing hosted app.");
|
||||
yield nextTick();
|
||||
@ -71,4 +75,3 @@
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
ok(!win.UI._busyPromise, "UI is not busy");
|
||||
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
is(project.location, packagedAppLocation, "Location is valid");
|
||||
@ -40,6 +41,7 @@
|
||||
|
||||
let hostedAppManifest = TEST_BASE + "hosted_app.manifest";
|
||||
yield win.Cmds.importHostedApp(hostedAppManifest);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
project = win.AppManager.selectedProject;
|
||||
is(project.location, hostedAppManifest, "Location is valid");
|
||||
@ -49,6 +51,7 @@
|
||||
|
||||
hostedAppManifest = TEST_BASE + "/app";
|
||||
yield win.Cmds.importHostedApp(hostedAppManifest);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
project = win.AppManager.selectedProject;
|
||||
ok(project.location.endsWith('manifest.webapp'), "The manifest was found and the project was updated");
|
||||
|
@ -25,20 +25,17 @@
|
||||
let AppManager = win.AppManager;
|
||||
|
||||
function isProjectMarkedAsValid() {
|
||||
let details = win.UI.projecteditor.window.frames[0];
|
||||
let details = win.frames[0];
|
||||
return !details.document.body.classList.contains("error");
|
||||
}
|
||||
|
||||
let packagedAppLocation = getTestFilePath("app");
|
||||
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "details");
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
|
||||
let deferred = promise.defer();
|
||||
win.UI.projecteditor.once("onEditorCreated", deferred.resolve);
|
||||
yield deferred.promise;
|
||||
|
||||
ok("name" in project.manifest, "manifest includes name");
|
||||
is(project.name, project.manifest.name, "Display name uses manifest name");
|
||||
ok(isProjectMarkedAsValid(), "project is marked as valid");
|
||||
@ -66,7 +63,7 @@
|
||||
yield OS.File.writeAtomic(manifestPath, data , {tmpPath: manifestPath + ".tmp"});
|
||||
|
||||
// Trigger validation
|
||||
yield AppManager.validateProject(AppManager.selectedProject);
|
||||
yield AppManager.validateAndUpdateProject(AppManager.selectedProject);
|
||||
yield nextTick();
|
||||
|
||||
ok(!("name" in project.manifest), "manifest has been updated");
|
||||
@ -78,7 +75,7 @@
|
||||
yield AppManager.writeManifest(project);
|
||||
|
||||
// Trigger validation
|
||||
yield AppManager.validateProject(AppManager.selectedProject);
|
||||
yield AppManager.validateAndUpdateProject(AppManager.selectedProject);
|
||||
yield nextTick();
|
||||
|
||||
ok("name" in project.manifest, "manifest includes name");
|
||||
@ -97,5 +94,3 @@
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
@ -67,6 +67,7 @@
|
||||
let packagedAppLocation = getTestFilePath("app");
|
||||
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
|
||||
let panelNode = win.document.querySelector("#runtime-panel");
|
||||
let items = panelNode.querySelectorAll(".runtime-panel-item-usb");
|
||||
|
@ -113,6 +113,7 @@
|
||||
return Task.spawn(function*() {
|
||||
let packagedAppLocation = getTestFilePath("app");
|
||||
yield win.Cmds.importPackagedApp(packagedAppLocation);
|
||||
yield waitForUpdate(win, "project-validated");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,10 @@ function sendMessage(action, data, sync, callbackCookie) {
|
||||
return Components.utils.cloneInto(result, content);
|
||||
}
|
||||
|
||||
function enableDebug() {
|
||||
sendAsyncMessage('Shumway:enableDebug', null);
|
||||
}
|
||||
|
||||
addMessageListener('Shumway:init', function (message) {
|
||||
sendAsyncMessage('Shumway:running', {}, {
|
||||
externalInterface: externalInterfaceWrapper
|
||||
@ -55,6 +59,7 @@ addMessageListener('Shumway:init', function (message) {
|
||||
// up Xray wrappers.
|
||||
shumwayComAdapter = Components.utils.createObjectIn(content, {defineAs: 'ShumwayCom'});
|
||||
Components.utils.exportFunction(sendMessage, shumwayComAdapter, {defineAs: 'sendMessage'});
|
||||
Components.utils.exportFunction(enableDebug, shumwayComAdapter, {defineAs: 'enableDebug'});
|
||||
Object.defineProperties(shumwayComAdapter, {
|
||||
onLoadFileCallback: { value: null, writable: true },
|
||||
onExternalCallback: { value: null, writable: true },
|
||||
|
130
browser/extensions/shumway/chrome/pingpong.js
Normal file
130
browser/extensions/shumway/chrome/pingpong.js
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Simple class for synchronous XHR communication.
|
||||
// See also examples/inspector/debug/server.js.
|
||||
|
||||
var PingPongConnection = (function () {
|
||||
function PingPongConnection(url, onlySend) {
|
||||
this.url = url;
|
||||
this.onData = null;
|
||||
this.onError = null;
|
||||
this.currentXhr = null;
|
||||
this.closed = false;
|
||||
|
||||
if (!onlySend) {
|
||||
this.idle();
|
||||
}
|
||||
}
|
||||
|
||||
PingPongConnection.prototype = {
|
||||
idle: function () {
|
||||
function requestIncoming(connection) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', connection.url + '?idle', true);
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 204 &&
|
||||
xhr.getResponseHeader('X-PingPong-Error') === 'timeout') {
|
||||
requestIncoming(connection);
|
||||
return;
|
||||
}
|
||||
if (xhr.status === 200) {
|
||||
var result;
|
||||
if (connection.onData) {
|
||||
var response = xhr.responseText;
|
||||
result = connection.onData(response ? JSON.parse(response) : undefined);
|
||||
}
|
||||
if (xhr.getResponseHeader('X-PingPong-Async') === '1') {
|
||||
requestIncoming(connection);
|
||||
} else {
|
||||
sendResponse(connection, result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (connection.onError) {
|
||||
connection.onError(xhr.statusText);
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
if (connection.onError) {
|
||||
connection.onError(xhr.error);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
connection.currentXhr = xhr;
|
||||
}
|
||||
function sendResponse(connection, result) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', connection.url + '?response', false);
|
||||
xhr.onload = function () {
|
||||
if (xhr.status !== 204) {
|
||||
if (connection.onError) {
|
||||
connection.onError(xhr.statusText);
|
||||
}
|
||||
}
|
||||
requestIncoming(connection);
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
if (connection.onError) {
|
||||
connection.onError(xhr.error);
|
||||
}
|
||||
};
|
||||
xhr.send(result === undefined ? '' : JSON.stringify(result));
|
||||
connection.currentXhr = xhr;
|
||||
}
|
||||
requestIncoming(this);
|
||||
},
|
||||
send: function (data, async, timeout) {
|
||||
if (this.closed) {
|
||||
throw new Error('connection closed');
|
||||
}
|
||||
|
||||
async = !!async;
|
||||
timeout |= 0;
|
||||
|
||||
var encoded = data === undefined ? '' : JSON.stringify(data);
|
||||
if (async) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', this.url + '?async', true);
|
||||
xhr.send(encoded);
|
||||
return;
|
||||
} else {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', this.url, false);
|
||||
if (timeout > 0) {
|
||||
xhr.setRequestHeader('X-PingPong-Timeout', timeout);
|
||||
}
|
||||
xhr.send(encoded);
|
||||
if (xhr.status === 204 &&
|
||||
xhr.getResponseHeader('X-PingPong-Error') === 'timeout') {
|
||||
throw new Error('sync request timeout');
|
||||
}
|
||||
var response = xhr.responseText;
|
||||
return response ? JSON.parse(response) : undefined;
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
if (this.currentXhr) {
|
||||
this.currentXhr.abort();
|
||||
this.currentXhr = null;
|
||||
}
|
||||
this.closed = true;
|
||||
}
|
||||
};
|
||||
|
||||
return PingPongConnection;
|
||||
})();
|
@ -36,11 +36,23 @@ limitations under the License.
|
||||
line-height: 0;
|
||||
border: 0px none;
|
||||
}
|
||||
|
||||
body.remoteStopped {
|
||||
background-color: red;
|
||||
}
|
||||
body.remoteDebug {
|
||||
background-color: green;
|
||||
}
|
||||
body.remoteReload {
|
||||
background-color: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<iframe id="viewer" src="resource://shumway/web/viewer.html" width="100%" height="100%" mozbrowser remote="true"></iframe>
|
||||
<script src="chrome://shumway/content/pingpong.js"></script>
|
||||
<script src="chrome://shumway/content/viewerDebugger.js"></script>
|
||||
<script src="chrome://shumway/content/viewerWrapper.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
90
browser/extensions/shumway/chrome/viewerDebugger.js
Normal file
90
browser/extensions/shumway/chrome/viewerDebugger.js
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
var DebugUtils = (function () {
|
||||
var baseUrl = null;
|
||||
|
||||
function getBaseUrl() {
|
||||
if (baseUrl === null) {
|
||||
try {
|
||||
baseUrl = Services.prefs.getCharPref('shumway.debug.url');
|
||||
} catch (e) {
|
||||
baseUrl = 'http://localhost:8010';
|
||||
}
|
||||
}
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
var uniqueId = (Date.now() % 888888) * 2 + 1;
|
||||
|
||||
function getEnabledDebuggerId(swfUrl) {
|
||||
return new Promise(function (resolve) {
|
||||
var url = getBaseUrl() + '/debugController/' + uniqueId;
|
||||
var connection = new PingPongConnection(url);
|
||||
connection.onData = function (data) {
|
||||
if (data.action === 'setDebugger' && data.swfUrl === swfUrl) {
|
||||
resolve(data.debuggerId);
|
||||
}
|
||||
};
|
||||
try {
|
||||
connection.send({action: 'getDebugger', swfUrl: swfUrl, swfId: uniqueId}, true);
|
||||
} catch (e) {
|
||||
// ignoring failed send request
|
||||
}
|
||||
setTimeout(function () {
|
||||
resolve(0);
|
||||
connection.close();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
function enableDebug(swfUrl) {
|
||||
var url = getBaseUrl() + '/debugController/' + uniqueId;
|
||||
var connection = new PingPongConnection(url, true);
|
||||
try {
|
||||
connection.send({action: 'enableDebugging', swfUrl: swfUrl}, true);
|
||||
} catch (e) {
|
||||
// ignoring failed send request
|
||||
}
|
||||
connection.close();
|
||||
}
|
||||
|
||||
function createDebuggerConnection(swfUrl) {
|
||||
return getEnabledDebuggerId(swfUrl).then(function (debuggerId) {
|
||||
if (!debuggerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var url = getBaseUrl() + '/debug/' + uniqueId + '/' + debuggerId;
|
||||
console.log('Starting remote debugger with ' + url);
|
||||
return new PingPongConnection(url);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
get isEnabled() {
|
||||
try {
|
||||
return Services.prefs.getBoolPref('shumway.debug.enabled');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
enableDebug: enableDebug,
|
||||
createDebuggerConnection: createDebuggerConnection
|
||||
};
|
||||
})();
|
@ -53,6 +53,7 @@ function runViewer() {
|
||||
// ShumwayStreamConverter.
|
||||
var shumwayComAdapter = Components.utils.createObjectIn(childWindow, {defineAs: 'ShumwayCom'});
|
||||
Components.utils.exportFunction(sendMessage, shumwayComAdapter, {defineAs: 'sendMessage'});
|
||||
Components.utils.exportFunction(enableDebug, shumwayComAdapter, {defineAs: 'enableDebug'});
|
||||
Object.defineProperties(shumwayComAdapter, {
|
||||
onLoadFileCallback: { value: null, writable: true },
|
||||
onExternalCallback: { value: null, writable: true },
|
||||
@ -117,6 +118,10 @@ function runViewer() {
|
||||
return window.notifyShumwayMessage(detail);
|
||||
});
|
||||
|
||||
messageManager.addMessageListener('Shumway:enableDebug', function (message) {
|
||||
enableDebug();
|
||||
});
|
||||
|
||||
window.onExternalCallback = function (call) {
|
||||
return externalInterface.callback(JSON.stringify(call));
|
||||
};
|
||||
@ -135,7 +140,76 @@ function runViewer() {
|
||||
messageManager.sendAsyncMessage('Shumway:init', {});
|
||||
}
|
||||
|
||||
|
||||
function handleDebug(connection) {
|
||||
viewer.parentNode.removeChild(viewer); // we don't need viewer anymore
|
||||
document.body.className = 'remoteDebug';
|
||||
|
||||
function sendMessage(data) {
|
||||
var detail = {
|
||||
action: data.action,
|
||||
data: data.data,
|
||||
sync: data.sync
|
||||
};
|
||||
if (data.callback) {
|
||||
detail.callback = true;
|
||||
detail.cookie = data.cookie;
|
||||
}
|
||||
return window.notifyShumwayMessage(detail);
|
||||
}
|
||||
|
||||
connection.onData = function (data) {
|
||||
switch (data.action) {
|
||||
case 'sendMessage':
|
||||
return sendMessage(data.detail);
|
||||
case 'reload':
|
||||
document.body.className = 'remoteReload';
|
||||
setTimeout(function () {
|
||||
window.top.location.reload();
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
window.onExternalCallback = function (call) {
|
||||
return connection.send({action: 'onExternalCallback', detail: call});
|
||||
};
|
||||
|
||||
window.onMessageCallback = function (response) {
|
||||
return connection.send({action: 'onMessageCallback', detail: response});
|
||||
};
|
||||
|
||||
window.onLoadFileCallback = function (args) {
|
||||
if (args.array) {
|
||||
args.array = Array.prototype.slice.call(args.array, 0);
|
||||
}
|
||||
return connection.send({action: 'onLoadFileCallback', detail: args}, true);
|
||||
};
|
||||
|
||||
connection.send({action: 'runViewer'}, true);
|
||||
}
|
||||
|
||||
function enableDebug() {
|
||||
DebugUtils.enableDebug(window.swfUrlLoading);
|
||||
setTimeout(function () {
|
||||
window.top.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
promise.then(function (oop) {
|
||||
if (DebugUtils.isEnabled) {
|
||||
DebugUtils.createDebuggerConnection(window.swfUrlLoading).then(function (debuggerConnection) {
|
||||
if (debuggerConnection) {
|
||||
handleDebug(debuggerConnection);
|
||||
} else if (oop) {
|
||||
handlerOOP();
|
||||
} else {
|
||||
handler();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (oop) {
|
||||
handlerOOP();
|
||||
} else {
|
||||
|
@ -411,7 +411,7 @@ ChromeActions.prototype = {
|
||||
var position = e.loaded;
|
||||
var data = new Uint8Array(xhr.response);
|
||||
notifyLoadFileListener({callback:"loadFile", sessionId: sessionId,
|
||||
topic: "progress", array: data, loaded: e.loaded, total: e.total});
|
||||
topic: "progress", array: data, loaded: position, total: e.total});
|
||||
lastPosition = position;
|
||||
if (limit && e.total >= limit) {
|
||||
xhr.abort();
|
||||
@ -755,6 +755,59 @@ function activateShumwayScripts(window, requestListener) {
|
||||
return requestListener.receive.apply(requestListener, arguments);
|
||||
};
|
||||
window.wrappedJSObject.runViewer();
|
||||
|
||||
var parentWindow = window.parent;
|
||||
var viewerWindow = window.viewer.contentWindow;
|
||||
|
||||
function activate(e) {
|
||||
e.preventDefault();
|
||||
viewerWindow.removeEventListener('mousedown', activate, true);
|
||||
|
||||
parentWindow.addEventListener('keydown', forwardKeyEvent, true);
|
||||
parentWindow.addEventListener('keyup', forwardKeyEvent, true);
|
||||
|
||||
sendFocusEvent('focus');
|
||||
|
||||
parentWindow.addEventListener('blur', deactivate, true);
|
||||
parentWindow.addEventListener('mousedown', deactivate, true);
|
||||
viewerWindow.addEventListener('unload', deactivate, true);
|
||||
}
|
||||
|
||||
function deactivate() {
|
||||
parentWindow.removeEventListener('blur', deactivate, true);
|
||||
parentWindow.removeEventListener('mousedown', deactivate, true);
|
||||
viewerWindow.removeEventListener('unload', deactivate, true);
|
||||
|
||||
parentWindow.removeEventListener('keydown', forwardKeyEvent, true);
|
||||
parentWindow.removeEventListener('keyup', forwardKeyEvent, true);
|
||||
|
||||
sendFocusEvent('blur');
|
||||
|
||||
viewerWindow.addEventListener('mousedown', activate, true);
|
||||
}
|
||||
|
||||
function forwardKeyEvent(e) {
|
||||
var event = viewerWindow.document.createEvent('KeyboardEvent');
|
||||
event.initKeyEvent(e.type,
|
||||
e.bubbles,
|
||||
e.cancelable,
|
||||
e.view,
|
||||
e.ctrlKey,
|
||||
e.altKey,
|
||||
e.shiftKey,
|
||||
e.metaKey,
|
||||
e.keyCode,
|
||||
e.charCode);
|
||||
viewerWindow.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function sendFocusEvent(type) {
|
||||
var event = viewerWindow.document.createEvent("UIEvent");
|
||||
event.initEvent(type, false, true);
|
||||
viewerWindow.dispatchEvent(event);
|
||||
}
|
||||
|
||||
viewerWindow.addEventListener('mousedown', activate, true);
|
||||
}
|
||||
|
||||
if (window.document.readyState === "interactive" ||
|
||||
@ -1034,6 +1087,8 @@ ShumwayStreamConverterBase.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
domWindow.swfUrlLoading = actions.url;
|
||||
|
||||
// Report telemetry on amount of swfs on the page
|
||||
if (actions.isOverlay) {
|
||||
// Looking for last actions with same baseUrl
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,2 +1,2 @@
|
||||
0.9.3775
|
||||
a82ac47
|
||||
0.10.182
|
||||
0195a96
|
||||
|
BIN
browser/extensions/shumway/content/web/noflv.mp4
Normal file
BIN
browser/extensions/shumway/content/web/noflv.mp4
Normal file
Binary file not shown.
@ -111,6 +111,7 @@ limitations under the License.
|
||||
<menuitem label="Open in Inspector" id="inspectorMenu"></menuitem>
|
||||
<menuitem label="Report Problems" id="reportMenu"></menuitem>
|
||||
<menuitem label="Reload in Adobe Flash Player" id="fallbackMenu" hidden></menuitem>
|
||||
<menuitem label="Debug this SWF" id="debugMenu"></menuitem>
|
||||
<menuitem label="About Shumway %version%..." id="aboutMenu"></menuitem>
|
||||
</menu>
|
||||
</section>
|
||||
|
@ -93,7 +93,7 @@ var viewerPlayerglobalInfo = {
|
||||
catalog: SHUMWAY_ROOT + "playerglobal/playerglobal.json"
|
||||
};
|
||||
|
||||
var builtinPath = SHUMWAY_ROOT + "avm2/generated/builtin/builtin.abc";
|
||||
var builtinPath = SHUMWAY_ROOT + "libs/builtin.abc";
|
||||
|
||||
var playerWindow;
|
||||
var playerWindowLoaded = new Promise(function(resolve) {
|
||||
@ -165,6 +165,13 @@ function runViewer() {
|
||||
var version = Shumway.version || '';
|
||||
document.getElementById('aboutMenu').label =
|
||||
document.getElementById('aboutMenu').label.replace('%version%', version);
|
||||
|
||||
var debugMenuEnabled = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.debug.enabled', def: false});
|
||||
if (debugMenuEnabled) {
|
||||
document.getElementById('debugMenu').addEventListener('click', enableDebug);
|
||||
} else {
|
||||
document.getElementById('debugMenu').remove();
|
||||
}
|
||||
}
|
||||
|
||||
function showURL() {
|
||||
@ -205,6 +212,10 @@ function showAbout() {
|
||||
window.open('http://areweflashyet.com/');
|
||||
}
|
||||
|
||||
function enableDebug() {
|
||||
ShumwayCom.enableDebug();
|
||||
}
|
||||
|
||||
var movieUrl, movieParams, objectParams;
|
||||
|
||||
window.addEventListener("message", function handlerMessage(e) {
|
||||
|
@ -22,8 +22,7 @@ var viewerPlayerglobalInfo = {
|
||||
catalog: SHUMWAY_ROOT + "playerglobal/playerglobal.json"
|
||||
};
|
||||
|
||||
var avm2Root = SHUMWAY_ROOT + "avm2/";
|
||||
var builtinPath = avm2Root + "generated/builtin/builtin.abc";
|
||||
var builtinPath = SHUMWAY_ROOT + "libs/builtin.abc";
|
||||
|
||||
window.print = function(msg) {
|
||||
console.log(msg);
|
||||
|
@ -3,7 +3,7 @@
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!ENTITY window.title "Exceptions">
|
||||
<!ENTITY window.width "36em">
|
||||
<!ENTITY window.width "45em">
|
||||
|
||||
<!ENTITY treehead.sitename.label "Site">
|
||||
<!ENTITY treehead.status.label "Status">
|
||||
@ -21,6 +21,8 @@
|
||||
<!ENTITY allow.accesskey "A">
|
||||
<!ENTITY windowClose.key "w">
|
||||
|
||||
<!ENTITY button.close.label "Close">
|
||||
<!ENTITY button.close.accesskey "C">
|
||||
<!ENTITY button.cancel.label "Cancel">
|
||||
<!ENTITY button.cancel.accesskey "C">
|
||||
<!ENTITY button.ok.label "Save Changes">
|
||||
<!ENTITY button.ok.accesskey "S">
|
||||
|
||||
|
@ -1328,14 +1328,15 @@ public:
|
||||
already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aOnSuccess,
|
||||
already_AddRefed<nsIDOMGetUserMediaErrorCallback> aOnFailure,
|
||||
uint64_t aWindowId, nsACString& aAudioLoopbackDev,
|
||||
nsACString& aVideoLoopbackDev)
|
||||
nsACString& aVideoLoopbackDev, bool aUseFakeDevices)
|
||||
: mConstraints(aConstraints)
|
||||
, mOnSuccess(aOnSuccess)
|
||||
, mOnFailure(aOnFailure)
|
||||
, mManager(MediaManager::GetInstance())
|
||||
, mWindowId(aWindowId)
|
||||
, mLoopbackAudioDevice(aAudioLoopbackDev)
|
||||
, mLoopbackVideoDevice(aVideoLoopbackDev) {}
|
||||
, mLoopbackVideoDevice(aVideoLoopbackDev)
|
||||
, mUseFakeDevices(aUseFakeDevices) {}
|
||||
|
||||
void // NS_IMETHOD
|
||||
Run()
|
||||
@ -1343,7 +1344,7 @@ public:
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
|
||||
nsRefPtr<MediaEngine> backend;
|
||||
if (mConstraints.mFake)
|
||||
if (mConstraints.mFake || mUseFakeDevices)
|
||||
backend = new MediaEngineDefault(mConstraints.mFakeTracks);
|
||||
else
|
||||
backend = mManager->GetBackend(mWindowId);
|
||||
@ -1387,6 +1388,7 @@ private:
|
||||
// automated media tests only.
|
||||
nsCString mLoopbackAudioDevice;
|
||||
nsCString mLoopbackVideoDevice;
|
||||
bool mUseFakeDevices;
|
||||
};
|
||||
|
||||
MediaManager::MediaManager()
|
||||
@ -1596,10 +1598,15 @@ MediaManager::GetUserMedia(
|
||||
if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
|
||||
c.mVideo.SetAsBoolean() = false;
|
||||
}
|
||||
bool fake = true;
|
||||
if (!c.mFake &&
|
||||
!Preferences::GetBool("media.navigator.streams.fake", false)) {
|
||||
fake = false;
|
||||
}
|
||||
|
||||
// Pass callbacks and MediaStreamListener along to GetUserMediaTask.
|
||||
nsAutoPtr<GetUserMediaTask> task;
|
||||
if (c.mFake) {
|
||||
if (fake) {
|
||||
// Fake stream from default backend.
|
||||
task = new GetUserMediaTask(c, onSuccess.forget(),
|
||||
onFailure.forget(), windowID, listener, mPrefs, new MediaEngineDefault(c.mFakeTracks));
|
||||
@ -1711,7 +1718,7 @@ MediaManager::GetUserMedia(
|
||||
|
||||
// XXX No full support for picture in Desktop yet (needs proper UI)
|
||||
if (privileged ||
|
||||
(c.mFake && !Preferences::GetBool("media.navigator.permission.fake"))) {
|
||||
(fake && !Preferences::GetBool("media.navigator.permission.fake"))) {
|
||||
MediaManager::GetMessageLoop()->PostTask(FROM_HERE, task.forget());
|
||||
} else {
|
||||
bool isHTTPS = false;
|
||||
@ -1805,12 +1812,14 @@ MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
|
||||
Preferences::GetCString("media.audio_loopback_dev");
|
||||
nsAdoptingCString loopbackVideoDevice =
|
||||
Preferences::GetCString("media.video_loopback_dev");
|
||||
bool useFakeStreams =
|
||||
Preferences::GetBool("media.navigator.streams.fake", false);
|
||||
|
||||
MediaManager::GetMessageLoop()->PostTask(FROM_HERE,
|
||||
new GetUserMediaDevicesTask(
|
||||
aConstraints, onSuccess.forget(), onFailure.forget(),
|
||||
(aInnerWindowID ? aInnerWindowID : aWindow->WindowID()),
|
||||
loopbackAudioDevice, loopbackVideoDevice));
|
||||
loopbackAudioDevice, loopbackVideoDevice, useFakeStreams));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
12
intl/locales/Makefile.in
Normal file
12
intl/locales/Makefile.in
Normal file
@ -0,0 +1,12 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
PATTERN_FILES = $(strip $(wildcard $(srcdir)/*/hyphenation/*.dic))
|
||||
|
||||
ifneq (,$(PATTERN_FILES))
|
||||
libs::
|
||||
$(INSTALL) $(PATTERN_FILES) $(FINAL_TARGET)/hyphenation
|
||||
endif
|
@ -4,45 +4,3 @@
|
||||
# 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/.
|
||||
|
||||
locales = [
|
||||
'af',
|
||||
'bg',
|
||||
'ca',
|
||||
'cy',
|
||||
'da',
|
||||
'de-1901',
|
||||
'de-1996',
|
||||
'de-CH',
|
||||
# 'en-US', # en-US is renamed -- see below.
|
||||
'eo',
|
||||
'es',
|
||||
'et',
|
||||
'fi',
|
||||
'fr',
|
||||
'gl',
|
||||
'hr',
|
||||
'hsb',
|
||||
'hu',
|
||||
'ia',
|
||||
'is',
|
||||
'it',
|
||||
'kmr',
|
||||
'la',
|
||||
'lt',
|
||||
'mn',
|
||||
'nb',
|
||||
'nl',
|
||||
'nn',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'sh',
|
||||
'sl',
|
||||
'sv',
|
||||
'tr',
|
||||
'uk',
|
||||
]
|
||||
filename = '{locale}/hyphenation/hyph_{locale}.dic'
|
||||
FINAL_TARGET_FILES.hyphenation += [filename.format(locale=locale) for locale in locales]
|
||||
# en-US is a special case: the dic file is named like en_US.
|
||||
FINAL_TARGET_FILES.hyphenation += ['en-US/hyphenation/hyph_en_US.dic']
|
||||
|
@ -133,15 +133,12 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import com.readystatesoftware.systembartint.SystemBarTintManager;
|
||||
|
||||
public class BrowserApp extends GeckoApp
|
||||
implements TabsPanel.TabsLayoutChangeListener,
|
||||
PropertyAnimator.PropertyAnimationListener,
|
||||
@ -245,8 +242,6 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
private ReadingListHelper mReadingListHelper;
|
||||
|
||||
private SystemBarTintManager mTintManager;
|
||||
|
||||
// The tab to be selected on editing mode exit.
|
||||
private Integer mTargetTabForEditingMode;
|
||||
|
||||
@ -687,8 +682,6 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
final Context appContext = getApplicationContext();
|
||||
|
||||
setupSystemUITinting();
|
||||
|
||||
mBrowserChrome = (ViewGroup) findViewById(R.id.browser_chrome);
|
||||
mActionBarFlipper = (ViewFlipper) findViewById(R.id.browser_actionbar);
|
||||
mActionBar = (ActionModeCompatView) findViewById(R.id.actionbar);
|
||||
@ -838,31 +831,6 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
}
|
||||
|
||||
private void setupSystemUITinting() {
|
||||
if (!Versions.feature19Plus) {
|
||||
return;
|
||||
}
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
|
||||
mTintManager = new SystemBarTintManager(this);
|
||||
mTintManager.setTintColor(getResources().getColor(R.color.background_tabs));
|
||||
updateSystemUITinting(mRootLayout.getSystemUiVisibility());
|
||||
|
||||
mRootLayout.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(int visibility) {
|
||||
updateSystemUITinting(visibility);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateSystemUITinting(int visibility) {
|
||||
final boolean shouldTint = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0 &&
|
||||
(visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0;
|
||||
mTintManager.setStatusBarTintEnabled(shouldTint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and show the firstrun pane if the browser has never been launched and
|
||||
* is not opening an external link from another application.
|
||||
@ -2649,11 +2617,6 @@ public class BrowserApp extends GeckoApp
|
||||
view.getHitRect(mTempRect);
|
||||
mTempRect.offset(-view.getScrollX(), -view.getScrollY());
|
||||
|
||||
if (mTintManager != null) {
|
||||
SystemBarTintManager.SystemBarConfig config = mTintManager.getConfig();
|
||||
mTempRect.offset(0, -config.getPixelInsetTop(false));
|
||||
}
|
||||
|
||||
int[] viewCoords = new int[2];
|
||||
view.getLocationOnScreen(viewCoords);
|
||||
|
||||
|
@ -571,7 +571,7 @@ final class GeckoEditable
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdateGecko(boolean update) {
|
||||
public void setUpdateGecko(boolean update, boolean force) {
|
||||
if (!onIcThread()) {
|
||||
// Android may be holding an old InputConnection; ignore
|
||||
if (DEBUG) {
|
||||
@ -580,7 +580,7 @@ final class GeckoEditable
|
||||
return;
|
||||
}
|
||||
if (update) {
|
||||
icUpdateGecko(false);
|
||||
icUpdateGecko(force);
|
||||
}
|
||||
mUpdateGecko = update;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import android.text.Editable;
|
||||
interface GeckoEditableClient {
|
||||
void sendEvent(GeckoEvent event);
|
||||
Editable getEditable();
|
||||
void setUpdateGecko(boolean update);
|
||||
void setUpdateGecko(boolean update, boolean force);
|
||||
void setSuppressKeyUp(boolean suppress);
|
||||
Handler getInputConnectionHandler();
|
||||
boolean setInputConnectionHandler(Handler handler);
|
||||
|
@ -250,7 +250,7 @@ class GeckoInputConnection
|
||||
@Override
|
||||
public synchronized boolean beginBatchEdit() {
|
||||
mBatchEditCount++;
|
||||
mEditableClient.setUpdateGecko(false);
|
||||
mEditableClient.setUpdateGecko(false, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -259,6 +259,10 @@ class GeckoInputConnection
|
||||
if (mBatchEditCount > 0) {
|
||||
mBatchEditCount--;
|
||||
if (mBatchEditCount == 0) {
|
||||
// Force Gecko update for cancelled auto-correction of single-
|
||||
// character words to prevent character duplication. (bug 1133802)
|
||||
boolean forceUpdate = !mBatchTextChanged && !mBatchSelectionChanged;
|
||||
|
||||
if (mBatchTextChanged) {
|
||||
notifyTextChange();
|
||||
mBatchTextChanged = false;
|
||||
@ -269,7 +273,7 @@ class GeckoInputConnection
|
||||
Selection.getSelectionEnd(editable));
|
||||
mBatchSelectionChanged = false;
|
||||
}
|
||||
mEditableClient.setUpdateGecko(true);
|
||||
mEditableClient.setUpdateGecko(true, forceUpdate);
|
||||
}
|
||||
} else {
|
||||
Log.w(LOGTAG, "endBatchEdit() called, but mBatchEditCount == 0?!");
|
||||
|
@ -43,6 +43,7 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
private final DrawListener mDrawListener;
|
||||
private boolean mDraggingHandles;
|
||||
|
||||
private int selectionID; // Unique ID provided for each selection action.
|
||||
private float mViewLeft;
|
||||
private float mViewTop;
|
||||
private float mViewZoom;
|
||||
@ -131,6 +132,7 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
public void run() {
|
||||
try {
|
||||
if (event.equals("TextSelection:ShowHandles")) {
|
||||
selectionID = message.getInt("selectionID");
|
||||
final JSONArray handles = message.getJSONArray("handles");
|
||||
for (int i=0; i < handles.length(); i++) {
|
||||
String handle = handles.getString(i);
|
||||
@ -315,7 +317,17 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
public void onDestroyActionMode(ActionModeCompat mode) {
|
||||
mActionMode = null;
|
||||
mCallback = null;
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:End", null));
|
||||
final JSONObject args = new JSONObject();
|
||||
try {
|
||||
args.put("selectionID", selectionID);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error building JSON arguments for TextSelection:End", e);
|
||||
return;
|
||||
}
|
||||
|
||||
final GeckoEvent event =
|
||||
GeckoEvent.createBroadcastEvent("TextSelection:End", args.toString());
|
||||
GeckoAppShell.sendEventToGecko(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -628,7 +628,6 @@ gtjar.sources += [ thirdparty_source_dir + f for f in [
|
||||
'com/nineoldandroids/view/ViewPropertyAnimatorHC.java',
|
||||
'com/nineoldandroids/view/ViewPropertyAnimatorICS.java',
|
||||
'com/nineoldandroids/view/ViewPropertyAnimatorPreHC.java',
|
||||
'com/readystatesoftware/systembartint/SystemBarTintManager.java',
|
||||
'com/squareup/picasso/Action.java',
|
||||
'com/squareup/picasso/AssetBitmapHunter.java',
|
||||
'com/squareup/picasso/BitmapHunter.java',
|
||||
|
@ -7,8 +7,7 @@
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ViewStub android:id="@+id/tabs_panel"
|
||||
android:layout="@layout/tabs_panel_view"
|
||||
|
@ -102,7 +102,8 @@ function testLTR_selectAll() {
|
||||
|
||||
]).then(function() {
|
||||
// Close selection and complete test.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(),
|
||||
@ -171,7 +172,8 @@ function testRTL_selectAll() {
|
||||
|
||||
]).then(function() {
|
||||
// Close selection and complete test.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(),
|
||||
@ -216,7 +218,8 @@ function testLTR_dragFocusHandleToSelf() {
|
||||
let focusDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_dragFocusHandleToSelf - Test Starts."),
|
||||
@ -275,7 +278,8 @@ function testLTR_dragAnchorHandleToSelf() {
|
||||
let anchorDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_dragAnchorHandleToSelf - Test Starts."),
|
||||
@ -333,7 +337,8 @@ function testRTL_dragFocusHandleToSelf() {
|
||||
let focusDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_dragFocusHandleToSelf - Test Starts."),
|
||||
@ -392,7 +397,8 @@ function testRTL_dragAnchorHandleToSelf() {
|
||||
let anchorDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_dragAnchorHandleToSelf - Test Starts."),
|
||||
|
@ -208,7 +208,12 @@ function testCloseSelection() {
|
||||
// Various ways to close an active selection.
|
||||
]).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: -1}));
|
||||
return ok(sh.isSelectionActive(), "unrelated TextSelection:End should not close active selection");
|
||||
}).then(function() {
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
return ok(!sh.isSelectionActive(), "TextSelection:End should close active selection");
|
||||
|
||||
}).then(function() {
|
||||
@ -310,7 +315,8 @@ function testAttachCaret() {
|
||||
]);
|
||||
|
||||
}).then(function() {
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at end of testAttachCaret"),
|
||||
|
@ -77,7 +77,8 @@ function testLTR_selectionPoints() {
|
||||
let midpointSelText = sh._getSelectedText();
|
||||
|
||||
// Close selection and complete test.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
selectionExists(selection, "LTR Selection existed at points"),
|
||||
@ -121,7 +122,8 @@ function testRTL_selectionPoints() {
|
||||
let midpointSelText = sh._getSelectedText();
|
||||
|
||||
// Close selection and complete test.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
selectionExists(selection, "RTL Selection existed at points"),
|
||||
@ -186,7 +188,8 @@ function test_selectionLineHeight() {
|
||||
|
||||
]).then(function() {
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
greaterThan(selectionLineHeight, 0, "Distance from one line to another " +
|
||||
@ -237,7 +240,8 @@ function testLTR_moveFocusHandleDown() {
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_moveFocusHandleDown - Test Starts."),
|
||||
@ -302,7 +306,8 @@ function testLTR_moveFocusHandleUp() {
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_moveFocusHandleUp - Test Starts."),
|
||||
@ -370,7 +375,8 @@ function testLTR_moveAnchorHandleUp() {
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_moveAnchorHandleUp - Test Starts."),
|
||||
@ -434,7 +440,8 @@ function testLTR_moveAnchorHandleDown() {
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_moveAnchorHandleDown - Test Starts."),
|
||||
@ -502,7 +509,8 @@ function testRTL_moveFocusHandleDown() {
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_moveFocusHandleDown - Test Starts."),
|
||||
@ -566,7 +574,8 @@ function testRTL_moveFocusHandleUp() {
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_moveFocusHandleUp - Test Starts."),
|
||||
@ -634,7 +643,8 @@ function testRTL_moveAnchorHandleUp() {
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_moveAnchorHandleUp - Test Starts."),
|
||||
@ -698,7 +708,8 @@ function testRTL_moveAnchorHandleDown() {
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End", {});
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_moveAnchorHandleDown - Test Starts."),
|
||||
|
@ -44,6 +44,7 @@ var SelectionHandler = {
|
||||
_focusIsRTL: false,
|
||||
|
||||
_activeType: 0, // TYPE_NONE
|
||||
_selectionID: 0, // Unique Selection ID
|
||||
|
||||
_draggingHandles: false, // True while user drags text selection handles
|
||||
_dragStartAnchorOffset: null, // Editables need initial pos during HandleMove events
|
||||
@ -134,10 +135,19 @@ var SelectionHandler = {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Tab:Selected":
|
||||
case "TextSelection:End":
|
||||
this._closeSelection();
|
||||
break;
|
||||
|
||||
case "TextSelection:End":
|
||||
let data = JSON.parse(aData);
|
||||
// End the requested selection only.
|
||||
if (this._selectionID === data.selectionID) {
|
||||
this._closeSelection();
|
||||
}
|
||||
break;
|
||||
|
||||
case "TextSelection:Action":
|
||||
for (let type in this.actions) {
|
||||
if (this.actions[type].id == aData) {
|
||||
@ -365,6 +375,7 @@ var SelectionHandler = {
|
||||
// Determine position and show handles, open actionbar
|
||||
this._positionHandles(positions);
|
||||
Messaging.sendRequest({
|
||||
selectionID: this._selectionID,
|
||||
type: "TextSelection:ShowHandles",
|
||||
handles: [this.HANDLE_TYPE_ANCHOR, this.HANDLE_TYPE_FOCUS]
|
||||
});
|
||||
@ -756,6 +767,7 @@ var SelectionHandler = {
|
||||
// Determine position and show caret, open actionbar
|
||||
this._positionHandles();
|
||||
Messaging.sendRequest({
|
||||
selectionID: this._selectionID,
|
||||
type: "TextSelection:ShowHandles",
|
||||
handles: [this.HANDLE_TYPE_CARET]
|
||||
});
|
||||
@ -777,6 +789,7 @@ var SelectionHandler = {
|
||||
aElement.focus();
|
||||
}
|
||||
|
||||
this._selectionID++;
|
||||
this._stopDraggingHandles();
|
||||
this._contentWindow = aElement.ownerDocument.defaultView;
|
||||
this._targetIsRTL = (this._contentWindow.getComputedStyle(aElement, "").direction == "rtl");
|
||||
|
@ -1,565 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 readyState Software Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.readystatesoftware.systembartint;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout.LayoutParams;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class to manage status and navigation bar tint effects when using KitKat
|
||||
* translucent system UI modes.
|
||||
*
|
||||
*/
|
||||
public class SystemBarTintManager {
|
||||
|
||||
static {
|
||||
// Android allows a system property to override the presence of the navigation bar.
|
||||
// Used by the emulator.
|
||||
// See https://github.com/android/platform_frameworks_base/blob/master/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java#L1076
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
try {
|
||||
Class c = Class.forName("android.os.SystemProperties");
|
||||
Method m = c.getDeclaredMethod("get", String.class);
|
||||
m.setAccessible(true);
|
||||
sNavBarOverride = (String) m.invoke(null, "qemu.hw.mainkeys");
|
||||
} catch (Throwable e) {
|
||||
sNavBarOverride = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The default system bar tint color value.
|
||||
*/
|
||||
public static final int DEFAULT_TINT_COLOR = 0x99000000;
|
||||
|
||||
private static String sNavBarOverride;
|
||||
|
||||
private final SystemBarConfig mConfig;
|
||||
private boolean mStatusBarAvailable;
|
||||
private boolean mNavBarAvailable;
|
||||
private boolean mStatusBarTintEnabled;
|
||||
private boolean mNavBarTintEnabled;
|
||||
private View mStatusBarTintView;
|
||||
private View mNavBarTintView;
|
||||
|
||||
/**
|
||||
* Constructor. Call this in the host activity onCreate method after its
|
||||
* content view has been set. You should always create new instances when
|
||||
* the host activity is recreated.
|
||||
*
|
||||
* @param activity The host activity.
|
||||
*/
|
||||
@TargetApi(19)
|
||||
public SystemBarTintManager(Activity activity) {
|
||||
|
||||
Window win = activity.getWindow();
|
||||
ViewGroup decorViewGroup = (ViewGroup) win.getDecorView();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
// check theme attrs
|
||||
int[] attrs = {android.R.attr.windowTranslucentStatus,
|
||||
android.R.attr.windowTranslucentNavigation};
|
||||
TypedArray a = activity.obtainStyledAttributes(attrs);
|
||||
try {
|
||||
mStatusBarAvailable = a.getBoolean(0, false);
|
||||
mNavBarAvailable = a.getBoolean(1, false);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
// check window flags
|
||||
WindowManager.LayoutParams winParams = win.getAttributes();
|
||||
int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
|
||||
if ((winParams.flags & bits) != 0) {
|
||||
mStatusBarAvailable = true;
|
||||
}
|
||||
bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
|
||||
if ((winParams.flags & bits) != 0) {
|
||||
mNavBarAvailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
mConfig = new SystemBarConfig(activity, mStatusBarAvailable, mNavBarAvailable);
|
||||
// device might not have virtual navigation keys
|
||||
if (!mConfig.hasNavigtionBar()) {
|
||||
mNavBarAvailable = false;
|
||||
}
|
||||
|
||||
if (mStatusBarAvailable) {
|
||||
setupStatusBarView(activity, decorViewGroup);
|
||||
}
|
||||
if (mNavBarAvailable) {
|
||||
setupNavBarView(activity, decorViewGroup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable tinting of the system status bar.
|
||||
*
|
||||
* If the platform is running Jelly Bean or earlier, or translucent system
|
||||
* UI modes have not been enabled in either the theme or via window flags,
|
||||
* then this method does nothing.
|
||||
*
|
||||
* @param enabled True to enable tinting, false to disable it (default).
|
||||
*/
|
||||
public void setStatusBarTintEnabled(boolean enabled) {
|
||||
mStatusBarTintEnabled = enabled;
|
||||
if (mStatusBarAvailable) {
|
||||
mStatusBarTintView.setVisibility(enabled ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable tinting of the system navigation bar.
|
||||
*
|
||||
* If the platform does not have soft navigation keys, is running Jelly Bean
|
||||
* or earlier, or translucent system UI modes have not been enabled in either
|
||||
* the theme or via window flags, then this method does nothing.
|
||||
*
|
||||
* @param enabled True to enable tinting, false to disable it (default).
|
||||
*/
|
||||
public void setNavigationBarTintEnabled(boolean enabled) {
|
||||
mNavBarTintEnabled = enabled;
|
||||
if (mNavBarAvailable) {
|
||||
mNavBarTintView.setVisibility(enabled ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified color tint to all system UI bars.
|
||||
*
|
||||
* @param color The color of the background tint.
|
||||
*/
|
||||
public void setTintColor(int color) {
|
||||
setStatusBarTintColor(color);
|
||||
setNavigationBarTintColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified drawable or color resource to all system UI bars.
|
||||
*
|
||||
* @param res The identifier of the resource.
|
||||
*/
|
||||
public void setTintResource(int res) {
|
||||
setStatusBarTintResource(res);
|
||||
setNavigationBarTintResource(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified drawable to all system UI bars.
|
||||
*
|
||||
* @param drawable The drawable to use as the background, or null to remove it.
|
||||
*/
|
||||
public void setTintDrawable(Drawable drawable) {
|
||||
setStatusBarTintDrawable(drawable);
|
||||
setNavigationBarTintDrawable(drawable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified alpha to all system UI bars.
|
||||
*
|
||||
* @param alpha The alpha to use
|
||||
*/
|
||||
public void setTintAlpha(float alpha) {
|
||||
setStatusBarAlpha(alpha);
|
||||
setNavigationBarAlpha(alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified color tint to the system status bar.
|
||||
*
|
||||
* @param color The color of the background tint.
|
||||
*/
|
||||
public void setStatusBarTintColor(int color) {
|
||||
if (mStatusBarAvailable) {
|
||||
mStatusBarTintView.setBackgroundColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified drawable or color resource to the system status bar.
|
||||
*
|
||||
* @param res The identifier of the resource.
|
||||
*/
|
||||
public void setStatusBarTintResource(int res) {
|
||||
if (mStatusBarAvailable) {
|
||||
mStatusBarTintView.setBackgroundResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified drawable to the system status bar.
|
||||
*
|
||||
* @param drawable The drawable to use as the background, or null to remove it.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setStatusBarTintDrawable(Drawable drawable) {
|
||||
if (mStatusBarAvailable) {
|
||||
mStatusBarTintView.setBackgroundDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified alpha to the system status bar.
|
||||
*
|
||||
* @param alpha The alpha to use
|
||||
*/
|
||||
@TargetApi(11)
|
||||
public void setStatusBarAlpha(float alpha) {
|
||||
if (mStatusBarAvailable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
mStatusBarTintView.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified color tint to the system navigation bar.
|
||||
*
|
||||
* @param color The color of the background tint.
|
||||
*/
|
||||
public void setNavigationBarTintColor(int color) {
|
||||
if (mNavBarAvailable) {
|
||||
mNavBarTintView.setBackgroundColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified drawable or color resource to the system navigation bar.
|
||||
*
|
||||
* @param res The identifier of the resource.
|
||||
*/
|
||||
public void setNavigationBarTintResource(int res) {
|
||||
if (mNavBarAvailable) {
|
||||
mNavBarTintView.setBackgroundResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified drawable to the system navigation bar.
|
||||
*
|
||||
* @param drawable The drawable to use as the background, or null to remove it.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setNavigationBarTintDrawable(Drawable drawable) {
|
||||
if (mNavBarAvailable) {
|
||||
mNavBarTintView.setBackgroundDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified alpha to the system navigation bar.
|
||||
*
|
||||
* @param alpha The alpha to use
|
||||
*/
|
||||
@TargetApi(11)
|
||||
public void setNavigationBarAlpha(float alpha) {
|
||||
if (mNavBarAvailable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
mNavBarTintView.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the system bar configuration.
|
||||
*
|
||||
* @return The system bar configuration for the current device configuration.
|
||||
*/
|
||||
public SystemBarConfig getConfig() {
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is tinting enabled for the system status bar?
|
||||
*
|
||||
* @return True if enabled, False otherwise.
|
||||
*/
|
||||
public boolean isStatusBarTintEnabled() {
|
||||
return mStatusBarTintEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is tinting enabled for the system navigation bar?
|
||||
*
|
||||
* @return True if enabled, False otherwise.
|
||||
*/
|
||||
public boolean isNavBarTintEnabled() {
|
||||
return mNavBarTintEnabled;
|
||||
}
|
||||
|
||||
private void setupStatusBarView(Context context, ViewGroup decorViewGroup) {
|
||||
mStatusBarTintView = new View(context);
|
||||
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getStatusBarHeight());
|
||||
params.gravity = Gravity.TOP;
|
||||
if (mNavBarAvailable && !mConfig.isNavigationAtBottom()) {
|
||||
params.rightMargin = mConfig.getNavigationBarWidth();
|
||||
}
|
||||
mStatusBarTintView.setLayoutParams(params);
|
||||
mStatusBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR);
|
||||
mStatusBarTintView.setVisibility(View.GONE);
|
||||
decorViewGroup.addView(mStatusBarTintView);
|
||||
}
|
||||
|
||||
private void setupNavBarView(Context context, ViewGroup decorViewGroup) {
|
||||
mNavBarTintView = new View(context);
|
||||
LayoutParams params;
|
||||
if (mConfig.isNavigationAtBottom()) {
|
||||
params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getNavigationBarHeight());
|
||||
params.gravity = Gravity.BOTTOM;
|
||||
} else {
|
||||
params = new LayoutParams(mConfig.getNavigationBarWidth(), LayoutParams.MATCH_PARENT);
|
||||
params.gravity = Gravity.RIGHT;
|
||||
}
|
||||
mNavBarTintView.setLayoutParams(params);
|
||||
mNavBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR);
|
||||
mNavBarTintView.setVisibility(View.GONE);
|
||||
decorViewGroup.addView(mNavBarTintView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class which describes system bar sizing and other characteristics for the current
|
||||
* device configuration.
|
||||
*
|
||||
*/
|
||||
public static class SystemBarConfig {
|
||||
|
||||
private static final String STATUS_BAR_HEIGHT_RES_NAME = "status_bar_height";
|
||||
private static final String NAV_BAR_HEIGHT_RES_NAME = "navigation_bar_height";
|
||||
private static final String NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME = "navigation_bar_height_landscape";
|
||||
private static final String NAV_BAR_WIDTH_RES_NAME = "navigation_bar_width";
|
||||
private static final String SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar";
|
||||
|
||||
private final boolean mTranslucentStatusBar;
|
||||
private final boolean mTranslucentNavBar;
|
||||
private final int mStatusBarHeight;
|
||||
private final int mActionBarHeight;
|
||||
private final boolean mHasNavigationBar;
|
||||
private final int mNavigationBarHeight;
|
||||
private final int mNavigationBarWidth;
|
||||
private final boolean mInPortrait;
|
||||
private final float mSmallestWidthDp;
|
||||
|
||||
private SystemBarConfig(Activity activity, boolean translucentStatusBar, boolean traslucentNavBar) {
|
||||
Resources res = activity.getResources();
|
||||
mInPortrait = (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
|
||||
mSmallestWidthDp = getSmallestWidthDp(activity);
|
||||
mStatusBarHeight = getInternalDimensionSize(res, STATUS_BAR_HEIGHT_RES_NAME);
|
||||
mActionBarHeight = getActionBarHeight(activity);
|
||||
mNavigationBarHeight = getNavigationBarHeight(activity);
|
||||
mNavigationBarWidth = getNavigationBarWidth(activity);
|
||||
mHasNavigationBar = (mNavigationBarHeight > 0);
|
||||
mTranslucentStatusBar = translucentStatusBar;
|
||||
mTranslucentNavBar = traslucentNavBar;
|
||||
}
|
||||
|
||||
@TargetApi(14)
|
||||
private int getActionBarHeight(Context context) {
|
||||
int result = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
TypedValue tv = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
|
||||
result = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@TargetApi(14)
|
||||
private int getNavigationBarHeight(Context context) {
|
||||
Resources res = context.getResources();
|
||||
int result = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
if (hasNavBar(context)) {
|
||||
String key;
|
||||
if (mInPortrait) {
|
||||
key = NAV_BAR_HEIGHT_RES_NAME;
|
||||
} else {
|
||||
key = NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME;
|
||||
}
|
||||
return getInternalDimensionSize(res, key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@TargetApi(14)
|
||||
private int getNavigationBarWidth(Context context) {
|
||||
Resources res = context.getResources();
|
||||
int result = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
if (hasNavBar(context)) {
|
||||
return getInternalDimensionSize(res, NAV_BAR_WIDTH_RES_NAME);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@TargetApi(14)
|
||||
private boolean hasNavBar(Context context) {
|
||||
Resources res = context.getResources();
|
||||
int resourceId = res.getIdentifier(SHOW_NAV_BAR_RES_NAME, "bool", "android");
|
||||
if (resourceId != 0) {
|
||||
boolean hasNav = res.getBoolean(resourceId);
|
||||
// check override flag (see static block)
|
||||
if ("1".equals(sNavBarOverride)) {
|
||||
hasNav = false;
|
||||
} else if ("0".equals(sNavBarOverride)) {
|
||||
hasNav = true;
|
||||
}
|
||||
return hasNav;
|
||||
} else { // fallback
|
||||
return !ViewConfiguration.get(context).hasPermanentMenuKey();
|
||||
}
|
||||
}
|
||||
|
||||
private int getInternalDimensionSize(Resources res, String key) {
|
||||
int result = 0;
|
||||
int resourceId = res.getIdentifier(key, "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
result = res.getDimensionPixelSize(resourceId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private float getSmallestWidthDp(Activity activity) {
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
|
||||
} else {
|
||||
// TODO this is not correct, but we don't really care pre-kitkat
|
||||
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
}
|
||||
float widthDp = metrics.widthPixels / metrics.density;
|
||||
float heightDp = metrics.heightPixels / metrics.density;
|
||||
return Math.min(widthDp, heightDp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should a navigation bar appear at the bottom of the screen in the current
|
||||
* device configuration? A navigation bar may appear on the right side of
|
||||
* the screen in certain configurations.
|
||||
*
|
||||
* @return True if navigation should appear at the bottom of the screen, False otherwise.
|
||||
*/
|
||||
public boolean isNavigationAtBottom() {
|
||||
return (mSmallestWidthDp >= 600 || mInPortrait);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the system status bar.
|
||||
*
|
||||
* @return The height of the status bar (in pixels).
|
||||
*/
|
||||
public int getStatusBarHeight() {
|
||||
return mStatusBarHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the action bar.
|
||||
*
|
||||
* @return The height of the action bar (in pixels).
|
||||
*/
|
||||
public int getActionBarHeight() {
|
||||
return mActionBarHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this device have a system navigation bar?
|
||||
*
|
||||
* @return True if this device uses soft key navigation, False otherwise.
|
||||
*/
|
||||
public boolean hasNavigtionBar() {
|
||||
return mHasNavigationBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the system navigation bar.
|
||||
*
|
||||
* @return The height of the navigation bar (in pixels). If the device does not have
|
||||
* soft navigation keys, this will always return 0.
|
||||
*/
|
||||
public int getNavigationBarHeight() {
|
||||
return mNavigationBarHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the system navigation bar when it is placed vertically on the screen.
|
||||
*
|
||||
* @return The width of the navigation bar (in pixels). If the device does not have
|
||||
* soft navigation keys, this will always return 0.
|
||||
*/
|
||||
public int getNavigationBarWidth() {
|
||||
return mNavigationBarWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the layout inset for any system UI that appears at the top of the screen.
|
||||
*
|
||||
* @param withActionBar True to include the height of the action bar, False otherwise.
|
||||
* @return The layout inset (in pixels).
|
||||
*/
|
||||
public int getPixelInsetTop(boolean withActionBar) {
|
||||
return (mTranslucentStatusBar ? mStatusBarHeight : 0) + (withActionBar ? mActionBarHeight : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the layout inset for any system UI that appears at the bottom of the screen.
|
||||
*
|
||||
* @return The layout inset (in pixels).
|
||||
*/
|
||||
public int getPixelInsetBottom() {
|
||||
if (mTranslucentNavBar && isNavigationAtBottom()) {
|
||||
return mNavigationBarHeight;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the layout inset for any system UI that appears at the right of the screen.
|
||||
*
|
||||
* @return The layout inset (in pixels).
|
||||
*/
|
||||
public int getPixelInsetRight() {
|
||||
if (mTranslucentNavBar && !isNavigationAtBottom()) {
|
||||
return mNavigationBarWidth;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
281
services/common/logmanager.js
Normal file
281
services/common/logmanager.js
Normal file
@ -0,0 +1,281 @@
|
||||
/* 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/. */
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Services',
|
||||
'resource://gre/modules/Services.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Preferences',
|
||||
'resource://gre/modules/Preferences.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'FileUtils',
|
||||
'resource://gre/modules/FileUtils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Log',
|
||||
'resource://gre/modules/Log.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Task',
|
||||
'resource://gre/modules/Task.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'OS',
|
||||
'resource://gre/modules/osfile.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'CommonUtils',
|
||||
'resource://services-common/utils.js');
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"LogManager",
|
||||
];
|
||||
|
||||
const DEFAULT_MAX_ERROR_AGE = 20 * 24 * 60 * 60; // 20 days
|
||||
|
||||
// "shared" logs (ie, where the same log name is used by multiple LogManager
|
||||
// instances) are a fact of life here - eg, FirefoxAccounts logs are used by
|
||||
// both Sync and Reading-list.
|
||||
// However, different instances have different pref branches, so we need to
|
||||
// handle when one pref branch says "Debug" and the other says "Error"
|
||||
// So we (a) keep singleton console and dump appenders and (b) keep track
|
||||
// of the minimum (ie, most verbose) level and use that.
|
||||
// This avoids (a) the most recent setter winning (as that is indeterminate)
|
||||
// and (b) multiple dump/console appenders being added to the same log multiple
|
||||
// times, which would cause messages to appear twice.
|
||||
|
||||
// Singletons used by each instance.
|
||||
let formatter;
|
||||
let dumpAppender;
|
||||
let consoleAppender;
|
||||
|
||||
// A set of all preference roots used by all instances.
|
||||
let allBranches = new Set();
|
||||
|
||||
// The public LogManager object.
|
||||
function LogManager(prefRoot, logNames, logFilePrefix) {
|
||||
this.init(prefRoot, logNames, logFilePrefix);
|
||||
}
|
||||
|
||||
LogManager.prototype = {
|
||||
REASON_SUCCESS: "success",
|
||||
REASON_ERROR: "error",
|
||||
|
||||
_cleaningUpFileLogs: false,
|
||||
|
||||
_prefObservers: [],
|
||||
|
||||
init(prefRoot, logNames, logFilePrefix) {
|
||||
if (prefRoot instanceof Preferences) {
|
||||
this._prefs = prefRoot;
|
||||
} else {
|
||||
this._prefs = new Preferences(prefRoot);
|
||||
}
|
||||
|
||||
this.logFilePrefix = logFilePrefix;
|
||||
if (!formatter) {
|
||||
// Create a formatter and various appenders to attach to the logs.
|
||||
formatter = new Log.BasicFormatter();
|
||||
consoleAppender = new Log.ConsoleAppender(formatter);
|
||||
dumpAppender = new Log.DumpAppender(formatter);
|
||||
}
|
||||
|
||||
allBranches.add(this._prefs._branchStr);
|
||||
// We create a preference observer for all our prefs so they are magically
|
||||
// reflected if the pref changes after creation.
|
||||
let setupAppender = (appender, prefName, defaultLevel, findSmallest = false) => {
|
||||
let observer = newVal => {
|
||||
let level = Log.Level[newVal] || defaultLevel;
|
||||
if (findSmallest) {
|
||||
// We need to find the smallest value from all prefs controlling this appender.
|
||||
for (let branch of allBranches) {
|
||||
let lookPrefBranch = new Preferences(branch);
|
||||
let lookVal = Log.Level[lookPrefBranch.get(prefName)];
|
||||
if (lookVal && lookVal < level) {
|
||||
level = lookVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
appender.level = level;
|
||||
}
|
||||
this._prefs.observe(prefName, observer, this);
|
||||
this._prefObservers.push([prefName, observer]);
|
||||
// and call the observer now with the current pref value.
|
||||
observer(this._prefs.get(prefName));
|
||||
return observer;
|
||||
}
|
||||
|
||||
this._observeConsolePref = setupAppender(consoleAppender, "log.appender.console", Log.Level.Error, true);
|
||||
this._observeDumpPref = setupAppender(dumpAppender, "log.appender.dump", Log.Level.Error, true);
|
||||
|
||||
// The file appender doesn't get the special singleton behaviour.
|
||||
let fapp = this._fileAppender = new Log.StorageStreamAppender(formatter);
|
||||
// the stream gets a default of Debug as the user must go out of there way
|
||||
// to see the stuff spewed to it.
|
||||
this._observeStreamPref = setupAppender(fapp, "log.appender.file.level", Log.Level.Debug);
|
||||
|
||||
// now attach the appenders to all our logs.
|
||||
for (let logName of logNames) {
|
||||
let log = Log.repository.getLogger(logName);
|
||||
for (let appender of [fapp, dumpAppender, consoleAppender]) {
|
||||
log.addAppender(appender);
|
||||
}
|
||||
}
|
||||
// and use the first specified log as a "root" for our log.
|
||||
this._log = Log.repository.getLogger(logNames[0] + ".LogManager");
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup this instance
|
||||
*/
|
||||
finalize() {
|
||||
for (let [name, pref] of this._prefObservers) {
|
||||
this._prefs.ignore(name, pref, this);
|
||||
}
|
||||
this._prefObservers = [];
|
||||
try {
|
||||
allBranches.delete(this._prefs._branchStr);
|
||||
} catch (e) {}
|
||||
this._prefs = null;
|
||||
},
|
||||
|
||||
get _logFileDirectory() {
|
||||
// At this point we don't allow a custom directory for the logs so
|
||||
// about:sync-log can be used. We could fix this later if necessary.
|
||||
return FileUtils.getDir("ProfD", ["weave", "logs"]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy an input stream to the named file, doing everything off the main
|
||||
* thread.
|
||||
* outputFile is an nsIFile, but is used only for the name.
|
||||
* Returns a promise that is resolved with the file modification date on
|
||||
* completion or rejected if there is an error.
|
||||
*/
|
||||
_copyStreamToFile: Task.async(function* (inputStream, outputFile) {
|
||||
// The log data could be large, so we don't want to pass it all in a single
|
||||
// message, so use BUFFER_SIZE chunks.
|
||||
const BUFFER_SIZE = 8192;
|
||||
|
||||
// get a binary stream
|
||||
let binaryStream = Cc['@mozilla.org/binaryinputstream;1'].createInstance(Ci.nsIBinaryInputStream);
|
||||
binaryStream.setInputStream(inputStream);
|
||||
yield OS.File.makeDir(outputFile.parent.path, { ignoreExisting: true });
|
||||
let output = yield OS.File.open(outputFile.path, { write: true} );
|
||||
try {
|
||||
while (true) {
|
||||
let available = binaryStream.available();
|
||||
if (!available) {
|
||||
break;
|
||||
}
|
||||
let chunk = binaryStream.readByteArray(Math.min(available, BUFFER_SIZE));
|
||||
yield output.write(new Uint8Array(chunk));
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
binaryStream.close();
|
||||
yield output.close();
|
||||
}
|
||||
this._log.trace("finished copy to", outputFile.path);
|
||||
return (yield OS.File.stat(outputFile.path)).lastModificationDate;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Possibly generate a log file for all accumulated log messages and refresh
|
||||
* the input & output streams.
|
||||
* Returns a promise that resolves on completion or rejects if the file could
|
||||
* not be written.
|
||||
*/
|
||||
resetFileLog(reason) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let flushToFile;
|
||||
let reasonPrefix;
|
||||
switch (reason) {
|
||||
case this.REASON_SUCCESS:
|
||||
flushToFile = this._prefs.get("log.appender.file.logOnSuccess");
|
||||
reasonPrefix = "success";
|
||||
break;
|
||||
case this.REASON_ERROR:
|
||||
flushToFile = this._prefs.get("log.appender.file.logOnError");
|
||||
reasonPrefix = "error";
|
||||
break;
|
||||
default:
|
||||
return reject(new Error("Invalid reason"));
|
||||
}
|
||||
|
||||
let inStream = this._fileAppender.getInputStream();
|
||||
this._fileAppender.reset();
|
||||
if (flushToFile && inStream) {
|
||||
this._log.debug("Flushing file log");
|
||||
let filename = this.logFilePrefix + "-" + reasonPrefix + "-" + Date.now() + ".txt";
|
||||
let file = this._logFileDirectory;
|
||||
file.append(filename);
|
||||
this._log.trace("Beginning stream copy to " + file.leafName + ": " +
|
||||
Date.now());
|
||||
this._copyStreamToFile(inStream, file).then(
|
||||
modDate => {
|
||||
this._log.trace("onCopyComplete: " + Date.now());
|
||||
this._log.trace("Output file timestamp: " + modDate + " (" + modDate.getTime() + ")");
|
||||
},
|
||||
err => {
|
||||
this._log.error("Failed to copy log stream to file", err)
|
||||
reject(err)
|
||||
}
|
||||
).then(
|
||||
() => {
|
||||
// It's not completely clear to markh why we only do log cleanups
|
||||
// for errors, but for now the Sync semantics have been copied...
|
||||
// (one theory is that only cleaning up on error makes it less
|
||||
// likely old error logs would be removed, but that's not true if
|
||||
// there are occasional errors - let's address this later!)
|
||||
if (reason == this.REASON_ERROR &&
|
||||
!this._cleaningUpFileLogs) {
|
||||
this._log.trace("Scheduling cleanup.");
|
||||
// Note we don't return or wait on this promise - it continues
|
||||
// in the background
|
||||
this.cleanupLogs().then(null, err => {
|
||||
this._log.error("Failed to cleanup logs", err);
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
} catch (ex) {
|
||||
this._log.error("Failed to resetFileLog", ex)
|
||||
reject(ex);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds all logs older than maxErrorAge and deletes them using async I/O.
|
||||
*/
|
||||
cleanupLogs: Task.async(function* () {
|
||||
this._cleaningUpFileLogs = true;
|
||||
let iterator = new OS.File.DirectoryIterator(this._logFileDirectory.path);
|
||||
let maxAge = this._prefs.get("log.appender.file.maxErrorAge", DEFAULT_MAX_ERROR_AGE);
|
||||
let threshold = Date.now() - 1000 * maxAge;
|
||||
|
||||
this._log.debug("Log cleanup threshold time: " + threshold);
|
||||
yield iterator.forEach(Task.async(function* (entry) {
|
||||
if (!entry.name.startsWith(this.logFilePrefix + "-")) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// need to call .stat() as the enumerator doesn't give that to us on *nix.
|
||||
let info = yield OS.File.stat(entry.path);
|
||||
if (info.lastModificationDate.getTime() >= threshold) {
|
||||
return;
|
||||
}
|
||||
this._log.trace(" > Cleanup removing " + entry.name +
|
||||
" (" + info.lastModificationDate.getTime() + ")");
|
||||
yield OS.File.remove(entry.path);
|
||||
this._log.trace("Deleted " + entry.name);
|
||||
} catch (ex) {
|
||||
this._log.debug("Encountered error trying to clean up old log file "
|
||||
+ entry.name, ex);
|
||||
}
|
||||
}.bind(this)));
|
||||
this._cleaningUpFileLogs = false;
|
||||
this._log.debug("Done deleting files.");
|
||||
// This notification is used only for tests.
|
||||
Services.obs.notifyObservers(null, "services-tests:common:log-manager:cleanup-logs", null);
|
||||
}),
|
||||
}
|
@ -13,6 +13,7 @@ EXTRA_COMPONENTS += [
|
||||
EXTRA_JS_MODULES['services-common'] += [
|
||||
'hawkclient.js',
|
||||
'hawkrequest.js',
|
||||
'logmanager.js',
|
||||
'storageservice.js',
|
||||
'stringbundle.js',
|
||||
'tokenserverclient.js',
|
||||
|
@ -4,6 +4,7 @@
|
||||
const modules = [
|
||||
"async.js",
|
||||
"bagheeraclient.js",
|
||||
"logmanager.js",
|
||||
"rest.js",
|
||||
"storageservice.js",
|
||||
"stringbundle.js",
|
||||
|
107
services/common/tests/unit/test_logmanager.js
Normal file
107
services/common/tests/unit/test_logmanager.js
Normal file
@ -0,0 +1,107 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// NOTE: The sync test_errorhandler_* tests have quite good coverage for
|
||||
// other aspects of this.
|
||||
|
||||
Cu.import("resource://services-common/logmanager.js");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Returns an array of [consoleAppender, dumpAppender, [fileAppenders]] for
|
||||
// the specified log. Note that fileAppenders will usually have length=1
|
||||
function getAppenders(log) {
|
||||
let capps = log.appenders.filter(app => app instanceof Log.ConsoleAppender);
|
||||
equal(capps.length, 1, "should only have one console appender");
|
||||
let dapps = log.appenders.filter(app => app instanceof Log.DumpAppender);
|
||||
equal(dapps.length, 1, "should only have one dump appender");
|
||||
let fapps = log.appenders.filter(app => app instanceof Log.StorageStreamAppender);
|
||||
return [capps[0], dapps[0], fapps];
|
||||
}
|
||||
|
||||
// Test that the correct thing happens when no prefs exist for the log manager.
|
||||
add_test(function test_noPrefs() {
|
||||
// tell the log manager to init with a pref branch that doesn't exist.
|
||||
let lm = new LogManager("no-such-branch.", ["TestLog"], "test");
|
||||
|
||||
let log = Log.repository.getLogger("TestLog");
|
||||
let [capp, dapp, fapps] = getAppenders(log);
|
||||
// the "dump" and "console" appenders should get Error level
|
||||
equal(capp.level, Log.Level.Error);
|
||||
equal(dapp.level, Log.Level.Error);
|
||||
// and the file (stream) appender gets Dump by default
|
||||
equal(fapps.length, 1, "only 1 file appender");
|
||||
equal(fapps[0].level, Log.Level.Debug);
|
||||
lm.finalize();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
// Test that changes to the prefs used by the log manager are updated dynamically.
|
||||
add_test(function test_PrefChanges() {
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.console", "Trace");
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.dump", "Trace");
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.file.level", "Trace");
|
||||
let lm = new LogManager("log-manager.test.", ["TestLog2"], "test");
|
||||
|
||||
let log = Log.repository.getLogger("TestLog2");
|
||||
let [capp, dapp, [fapp]] = getAppenders(log);
|
||||
equal(capp.level, Log.Level.Trace);
|
||||
equal(dapp.level, Log.Level.Trace);
|
||||
equal(fapp.level, Log.Level.Trace);
|
||||
// adjust the prefs and they should magically be reflected in the appenders.
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.console", "Debug");
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.dump", "Debug");
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.file.level", "Debug");
|
||||
equal(capp.level, Log.Level.Debug);
|
||||
equal(dapp.level, Log.Level.Debug);
|
||||
equal(fapp.level, Log.Level.Debug);
|
||||
// and invalid values should cause them to fallback to their defaults.
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.console", "xxx");
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.dump", "xxx");
|
||||
Services.prefs.setCharPref("log-manager.test.log.appender.file.level", "xxx");
|
||||
equal(capp.level, Log.Level.Error);
|
||||
equal(dapp.level, Log.Level.Error);
|
||||
equal(fapp.level, Log.Level.Debug);
|
||||
lm.finalize();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
// Test that the same log used by multiple log managers does the right thing.
|
||||
add_test(function test_SharedLogs() {
|
||||
// create the prefs for the first instance.
|
||||
Services.prefs.setCharPref("log-manager-1.test.log.appender.console", "Trace");
|
||||
Services.prefs.setCharPref("log-manager-1.test.log.appender.dump", "Trace");
|
||||
Services.prefs.setCharPref("log-manager-1.test.log.appender.file.level", "Trace");
|
||||
let lm1 = new LogManager("log-manager-1.test.", ["TestLog3"], "test");
|
||||
|
||||
// and the second.
|
||||
Services.prefs.setCharPref("log-manager-2.test.log.appender.console", "Debug");
|
||||
Services.prefs.setCharPref("log-manager-2.test.log.appender.dump", "Debug");
|
||||
Services.prefs.setCharPref("log-manager-2.test.log.appender.file.level", "Debug");
|
||||
let lm2 = new LogManager("log-manager-2.test.", ["TestLog3"], "test");
|
||||
|
||||
let log = Log.repository.getLogger("TestLog3");
|
||||
let [capp, dapp, fapps] = getAppenders(log);
|
||||
|
||||
// console and dump appenders should be "trace" as it is more verbose than
|
||||
// "debug"
|
||||
equal(capp.level, Log.Level.Trace);
|
||||
equal(dapp.level, Log.Level.Trace);
|
||||
|
||||
// Set the prefs on the -1 branch to "Error" - it should then end up with
|
||||
// "Debug" from the -2 branch.
|
||||
Services.prefs.setCharPref("log-manager-1.test.log.appender.console", "Error");
|
||||
Services.prefs.setCharPref("log-manager-1.test.log.appender.dump", "Error");
|
||||
Services.prefs.setCharPref("log-manager-1.test.log.appender.file.level", "Error");
|
||||
|
||||
equal(capp.level, Log.Level.Debug);
|
||||
equal(dapp.level, Log.Level.Debug);
|
||||
|
||||
lm1.finalize();
|
||||
lm2.finalize();
|
||||
|
||||
run_next_test();
|
||||
});
|
@ -29,6 +29,7 @@ skip-if = toolkit == 'gonk'
|
||||
[test_bagheera_client.js]
|
||||
[test_hawkclient.js]
|
||||
[test_hawkrequest.js]
|
||||
[test_logmanager.js]
|
||||
[test_observers.js]
|
||||
[test_restrequest.js]
|
||||
[test_tokenauthenticatedrequest.js]
|
||||
|
@ -13,8 +13,7 @@ Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://services-common/logmanager.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Status",
|
||||
"resource://services-sync/status.js");
|
||||
@ -539,9 +538,6 @@ SyncScheduler.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
const LOG_PREFIX_SUCCESS = "success-";
|
||||
const LOG_PREFIX_ERROR = "error-";
|
||||
|
||||
this.ErrorHandler = function ErrorHandler(service) {
|
||||
this.service = service;
|
||||
this.init();
|
||||
@ -574,33 +570,14 @@ ErrorHandler.prototype = {
|
||||
initLogs: function initLogs() {
|
||||
this._log = Log.repository.getLogger("Sync.ErrorHandler");
|
||||
this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
|
||||
this._cleaningUpFileLogs = false;
|
||||
|
||||
let root = Log.repository.getLogger("Sync");
|
||||
root.level = Log.Level[Svc.Prefs.get("log.rootLogger")];
|
||||
|
||||
let formatter = new Log.BasicFormatter();
|
||||
let capp = new Log.ConsoleAppender(formatter);
|
||||
capp.level = Log.Level[Svc.Prefs.get("log.appender.console")];
|
||||
root.addAppender(capp);
|
||||
let logs = ["Sync", "FirefoxAccounts", "Hawk", "Common.TokenServerClient",
|
||||
"Sync.SyncMigration"];
|
||||
|
||||
let dapp = new Log.DumpAppender(formatter);
|
||||
dapp.level = Log.Level[Svc.Prefs.get("log.appender.dump")];
|
||||
root.addAppender(dapp);
|
||||
|
||||
let fapp = this._logAppender = new Log.StorageStreamAppender(formatter);
|
||||
fapp.level = Log.Level[Svc.Prefs.get("log.appender.file.level")];
|
||||
root.addAppender(fapp);
|
||||
|
||||
// Arrange for a number of other sync-related logs to also go to our
|
||||
// appenders.
|
||||
for (let extra of ["FirefoxAccounts", "Hawk", "Common.TokenServerClient",
|
||||
"Sync.SyncMigration"]) {
|
||||
let log = Log.repository.getLogger(extra);
|
||||
for (let appender of [fapp, dapp, capp]) {
|
||||
log.addAppender(appender);
|
||||
}
|
||||
}
|
||||
this._logManager = new LogManager(Svc.Prefs, logs, "sync");
|
||||
},
|
||||
|
||||
observe: function observe(subject, topic, data) {
|
||||
@ -625,8 +602,7 @@ ErrorHandler.prototype = {
|
||||
this._log.debug(engine_name + " failed: " + Utils.exceptionStr(exception));
|
||||
break;
|
||||
case "weave:service:login:error":
|
||||
this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
|
||||
LOG_PREFIX_ERROR);
|
||||
this.resetFileLog(this._logManager.REASON_ERROR);
|
||||
|
||||
if (this.shouldReportError()) {
|
||||
this.notifyOnNextTick("weave:ui:login:error");
|
||||
@ -641,8 +617,7 @@ ErrorHandler.prototype = {
|
||||
this.service.logout();
|
||||
}
|
||||
|
||||
this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
|
||||
LOG_PREFIX_ERROR);
|
||||
this.resetFileLog(this._logManager.REASON_ERROR);
|
||||
|
||||
if (this.shouldReportError()) {
|
||||
this.notifyOnNextTick("weave:ui:sync:error");
|
||||
@ -668,8 +643,7 @@ ErrorHandler.prototype = {
|
||||
|
||||
if (Status.service == SYNC_FAILED_PARTIAL) {
|
||||
this._log.debug("Some engines did not sync correctly.");
|
||||
this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
|
||||
LOG_PREFIX_ERROR);
|
||||
this.resetFileLog(this._logManager.REASON_ERROR);
|
||||
|
||||
if (this.shouldReportError()) {
|
||||
this.dontIgnoreErrors = false;
|
||||
@ -677,8 +651,7 @@ ErrorHandler.prototype = {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnSuccess"),
|
||||
LOG_PREFIX_SUCCESS);
|
||||
this.resetFileLog(this._logManager.REASON_SUCCESS);
|
||||
}
|
||||
this.dontIgnoreErrors = false;
|
||||
this.notifyOnNextTick("weave:ui:sync:finish");
|
||||
@ -705,95 +678,22 @@ ErrorHandler.prototype = {
|
||||
Utils.nextTick(this.service.sync, this.service);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds all logs older than maxErrorAge and deletes them without tying up I/O.
|
||||
*/
|
||||
cleanupLogs: function cleanupLogs() {
|
||||
let direntries = FileUtils.getDir("ProfD", ["weave", "logs"]).directoryEntries;
|
||||
let oldLogs = [];
|
||||
let index = 0;
|
||||
let threshold = Date.now() - 1000 * Svc.Prefs.get("log.appender.file.maxErrorAge");
|
||||
|
||||
this._log.debug("Log cleanup threshold time: " + threshold);
|
||||
while (direntries.hasMoreElements()) {
|
||||
let logFile = direntries.getNext().QueryInterface(Ci.nsIFile);
|
||||
if (logFile.lastModifiedTime < threshold) {
|
||||
this._log.trace(" > Noting " + logFile.leafName +
|
||||
" for cleanup (" + logFile.lastModifiedTime + ")");
|
||||
oldLogs.push(logFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes a file from oldLogs each tick until there are none left.
|
||||
let errorHandler = this;
|
||||
function deleteFile() {
|
||||
if (index >= oldLogs.length) {
|
||||
errorHandler._log.debug("Done deleting files.");
|
||||
errorHandler._cleaningUpFileLogs = false;
|
||||
Svc.Obs.notify("weave:service:cleanup-logs");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let file = oldLogs[index];
|
||||
file.remove(false);
|
||||
errorHandler._log.trace("Deleted " + file.leafName + ".");
|
||||
} catch (ex) {
|
||||
errorHandler._log._debug("Encountered error trying to clean up old log file '"
|
||||
+ oldLogs[index].leafName + "':"
|
||||
+ Utils.exceptionStr(ex));
|
||||
}
|
||||
index++;
|
||||
Utils.nextTick(deleteFile);
|
||||
}
|
||||
|
||||
if (oldLogs.length > 0) {
|
||||
this._cleaningUpFileLogs = true;
|
||||
Utils.nextTick(deleteFile);
|
||||
} else {
|
||||
this._log.debug("No logs to clean up.");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a log file for the sync that just completed
|
||||
* and refresh the input & output streams.
|
||||
*
|
||||
* @param flushToFile
|
||||
* the log file to be flushed/reset
|
||||
*
|
||||
* @param filenamePrefix
|
||||
* a value of either LOG_PREFIX_SUCCESS or LOG_PREFIX_ERROR
|
||||
* to be used as the log filename prefix
|
||||
* @param reason
|
||||
* A constant from the LogManager that indicates the reason for the
|
||||
* reset.
|
||||
*/
|
||||
resetFileLog: function resetFileLog(flushToFile, filenamePrefix) {
|
||||
let inStream = this._logAppender.getInputStream();
|
||||
this._logAppender.reset();
|
||||
if (flushToFile && inStream) {
|
||||
this._log.debug("Flushing file log.");
|
||||
try {
|
||||
let filename = filenamePrefix + Date.now() + ".txt";
|
||||
let file = FileUtils.getFile("ProfD", ["weave", "logs", filename]);
|
||||
let outStream = FileUtils.openFileOutputStream(file);
|
||||
|
||||
this._log.trace("Beginning stream copy to " + file.leafName + ": " +
|
||||
Date.now());
|
||||
NetUtil.asyncCopy(inStream, outStream, function onCopyComplete() {
|
||||
this._log.trace("onCopyComplete: " + Date.now());
|
||||
this._log.trace("Output file timestamp: " + file.lastModifiedTime);
|
||||
Svc.Obs.notify("weave:service:reset-file-log");
|
||||
this._log.trace("Notified: " + Date.now());
|
||||
if (filenamePrefix == LOG_PREFIX_ERROR &&
|
||||
!this._cleaningUpFileLogs) {
|
||||
this._log.trace("Scheduling cleanup.");
|
||||
Utils.nextTick(this.cleanupLogs, this);
|
||||
}
|
||||
}.bind(this));
|
||||
} catch (ex) {
|
||||
Svc.Obs.notify("weave:service:reset-file-log");
|
||||
}
|
||||
} else {
|
||||
resetFileLog: function resetFileLog(reason) {
|
||||
let onComplete = () => {
|
||||
Svc.Obs.notify("weave:service:reset-file-log");
|
||||
}
|
||||
this._log.trace("Notified: " + Date.now());
|
||||
};
|
||||
// Note we do not return the promise here - the caller doesn't need to wait
|
||||
// for this to complete.
|
||||
this._logManager.resetFileLog(reason).then(onComplete, onComplete);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -14,8 +14,6 @@ Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
const FAKE_SERVER_URL = "http://dummy:9000/";
|
||||
const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
|
||||
const LOG_PREFIX_SUCCESS = "success-";
|
||||
const LOG_PREFIX_ERROR = "error-";
|
||||
|
||||
const PROLONGED_ERROR_DURATION =
|
||||
(Svc.Prefs.get('errorhandler.networkFailureReportTimeout') * 2) * 1000;
|
||||
@ -1772,8 +1770,7 @@ add_task(function test_sync_engine_generic_fail() {
|
||||
let entries = logsdir.directoryEntries;
|
||||
do_check_true(entries.hasMoreElements());
|
||||
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
|
||||
do_check_eq(logfile.leafName.slice(0, LOG_PREFIX_ERROR.length),
|
||||
LOG_PREFIX_ERROR);
|
||||
do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
|
||||
|
||||
clean();
|
||||
server.stop(deferred.resolve);
|
||||
@ -1804,8 +1801,7 @@ add_test(function test_logs_on_sync_error_despite_shouldReportError() {
|
||||
let entries = logsdir.directoryEntries;
|
||||
do_check_true(entries.hasMoreElements());
|
||||
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
|
||||
do_check_eq(logfile.leafName.slice(0, LOG_PREFIX_ERROR.length),
|
||||
LOG_PREFIX_ERROR);
|
||||
do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
|
||||
|
||||
clean();
|
||||
run_next_test();
|
||||
@ -1832,8 +1828,7 @@ add_test(function test_logs_on_login_error_despite_shouldReportError() {
|
||||
let entries = logsdir.directoryEntries;
|
||||
do_check_true(entries.hasMoreElements());
|
||||
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
|
||||
do_check_eq(logfile.leafName.slice(0, LOG_PREFIX_ERROR.length),
|
||||
LOG_PREFIX_ERROR);
|
||||
do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
|
||||
|
||||
clean();
|
||||
run_next_test();
|
||||
@ -1867,8 +1862,7 @@ add_task(function test_engine_applyFailed() {
|
||||
let entries = logsdir.directoryEntries;
|
||||
do_check_true(entries.hasMoreElements());
|
||||
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
|
||||
do_check_eq(logfile.leafName.slice(0, LOG_PREFIX_ERROR.length),
|
||||
LOG_PREFIX_ERROR);
|
||||
do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
|
||||
|
||||
clean();
|
||||
server.stop(deferred.resolve);
|
||||
|
@ -10,8 +10,6 @@ Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
|
||||
const LOG_PREFIX_SUCCESS = "success-";
|
||||
const LOG_PREFIX_ERROR = "error-";
|
||||
|
||||
// Delay to wait before cleanup, to allow files to age.
|
||||
// This is so large because the file timestamp granularity is per-second, and
|
||||
@ -32,6 +30,7 @@ function setLastSync(lastSyncValue) {
|
||||
function run_test() {
|
||||
initTestLogging("Trace");
|
||||
|
||||
Log.repository.getLogger("Sync.LogManager").level = Log.Level.Trace;
|
||||
Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
|
||||
Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
|
||||
Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
|
||||
@ -41,7 +40,7 @@ function run_test() {
|
||||
|
||||
add_test(function test_noOutput() {
|
||||
// Ensure that the log appender won't print anything.
|
||||
errorHandler._logAppender.level = Log.Level.Fatal + 1;
|
||||
errorHandler._logManager._fileAppender.level = Log.Level.Fatal + 1;
|
||||
|
||||
// Clear log output from startup.
|
||||
Svc.Prefs.set("log.appender.file.logOnSuccess", false);
|
||||
@ -53,7 +52,7 @@ add_test(function test_noOutput() {
|
||||
Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
|
||||
Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
|
||||
|
||||
errorHandler._logAppender.level = Log.Level.Trace;
|
||||
errorHandler._logManager._fileAppender.level = Log.Level.Trace;
|
||||
Svc.Prefs.resetBranch("");
|
||||
run_next_test();
|
||||
});
|
||||
@ -109,8 +108,7 @@ add_test(function test_logOnSuccess_true() {
|
||||
do_check_true(entries.hasMoreElements());
|
||||
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
|
||||
do_check_eq(logfile.leafName.slice(-4), ".txt");
|
||||
do_check_eq(logfile.leafName.slice(0, LOG_PREFIX_SUCCESS.length),
|
||||
LOG_PREFIX_SUCCESS);
|
||||
do_check_true(logfile.leafName.startsWith("sync-success-"), logfile.leafName);
|
||||
do_check_false(entries.hasMoreElements());
|
||||
|
||||
// Ensure the log message was actually written to file.
|
||||
@ -162,6 +160,13 @@ add_test(function test_sync_error_logOnError_true() {
|
||||
const MESSAGE = "this WILL show up";
|
||||
log.info(MESSAGE);
|
||||
|
||||
// We need to wait until the log cleanup started by this test is complete
|
||||
// or the next test will fail as it is ongoing.
|
||||
Svc.Obs.add("services-tests:common:log-manager:cleanup-logs", function onCleanupLogs() {
|
||||
Svc.Obs.remove("services-tests:common:log-manager:cleanup-logs", onCleanupLogs);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
|
||||
Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
|
||||
|
||||
@ -170,8 +175,7 @@ add_test(function test_sync_error_logOnError_true() {
|
||||
do_check_true(entries.hasMoreElements());
|
||||
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
|
||||
do_check_eq(logfile.leafName.slice(-4), ".txt");
|
||||
do_check_eq(logfile.leafName.slice(0, LOG_PREFIX_ERROR.length),
|
||||
LOG_PREFIX_ERROR);
|
||||
do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
|
||||
do_check_false(entries.hasMoreElements());
|
||||
|
||||
// Ensure the log message was actually written to file.
|
||||
@ -188,7 +192,6 @@ add_test(function test_sync_error_logOnError_true() {
|
||||
}
|
||||
|
||||
Svc.Prefs.resetBranch("");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
@ -224,6 +227,13 @@ add_test(function test_login_error_logOnError_true() {
|
||||
const MESSAGE = "this WILL show up";
|
||||
log.info(MESSAGE);
|
||||
|
||||
// We need to wait until the log cleanup started by this test is complete
|
||||
// or the next test will fail as it is ongoing.
|
||||
Svc.Obs.add("services-tests:common:log-manager:cleanup-logs", function onCleanupLogs() {
|
||||
Svc.Obs.remove("services-tests:common:log-manager:cleanup-logs", onCleanupLogs);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
|
||||
Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
|
||||
|
||||
@ -232,8 +242,7 @@ add_test(function test_login_error_logOnError_true() {
|
||||
do_check_true(entries.hasMoreElements());
|
||||
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
|
||||
do_check_eq(logfile.leafName.slice(-4), ".txt");
|
||||
do_check_eq(logfile.leafName.slice(0, LOG_PREFIX_ERROR.length),
|
||||
LOG_PREFIX_ERROR);
|
||||
do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
|
||||
do_check_false(entries.hasMoreElements());
|
||||
|
||||
// Ensure the log message was actually written to file.
|
||||
@ -250,7 +259,6 @@ add_test(function test_login_error_logOnError_true() {
|
||||
}
|
||||
|
||||
Svc.Prefs.resetBranch("");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
@ -273,7 +281,7 @@ add_test(function test_logErrorCleanup_age() {
|
||||
_("Making some files.");
|
||||
for (let i = 0; i < numLogs; i++) {
|
||||
let now = Date.now();
|
||||
let filename = LOG_PREFIX_ERROR + now + "" + i + ".txt";
|
||||
let filename = "sync-error-" + now + "" + i + ".txt";
|
||||
let newLog = FileUtils.getFile("ProfD", ["weave", "logs", filename]);
|
||||
let foStream = FileUtils.openFileOutputStream(newLog);
|
||||
foStream.write(errString, errString.length);
|
||||
@ -282,8 +290,8 @@ add_test(function test_logErrorCleanup_age() {
|
||||
oldLogs.push(newLog.leafName);
|
||||
}
|
||||
|
||||
Svc.Obs.add("weave:service:cleanup-logs", function onCleanupLogs() {
|
||||
Svc.Obs.remove("weave:service:cleanup-logs", onCleanupLogs);
|
||||
Svc.Obs.add("services-tests:common:log-manager:cleanup-logs", function onCleanupLogs() {
|
||||
Svc.Obs.remove("services-tests:common:log-manager:cleanup-logs", onCleanupLogs);
|
||||
|
||||
// Only the newest created log file remains.
|
||||
let entries = logsdir.directoryEntries;
|
||||
|
@ -1319,7 +1319,7 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
|
||||
}
|
||||
|
||||
let rect = this.currentQuads.border.bounds;
|
||||
let dim = Math.ceil(rect.width) + " \u00D7 " + Math.ceil(rect.height);
|
||||
let dim = parseFloat(rect.width.toPrecision(6)) + " \u00D7 " + parseFloat(rect.height.toPrecision(6));
|
||||
|
||||
let elementId = this.ID_CLASS_PREFIX + "nodeinfobar-";
|
||||
this.markup.setTextContentForElement(elementId + "tagname", tagName);
|
||||
|
@ -43,7 +43,12 @@ types.addDictType("AllocationsRecordingOptions", {
|
||||
// The probability we sample any given allocation when recording
|
||||
// allocations. Must be between 0.0 and 1.0. Defaults to 1.0, or sampling
|
||||
// every allocation.
|
||||
probability: "number"
|
||||
probability: "number",
|
||||
|
||||
// The maximum number of of allocation events to keep in the allocations
|
||||
// log. If new allocations arrive, when we are already at capacity, the oldest
|
||||
// allocation event is lost. This number must fit in a 32 bit signed integer.
|
||||
maxLogLength: "number"
|
||||
});
|
||||
|
||||
/**
|
||||
@ -164,6 +169,9 @@ let MemoryActor = protocol.ActorClass({
|
||||
this.dbg.memory.allocationSamplingProbability = options.probability != null
|
||||
? options.probability
|
||||
: 1.0;
|
||||
if (options.maxLogLength != null) {
|
||||
this.dbg.memory.maxAllocationsLogLength = options.maxLogLength;
|
||||
}
|
||||
this.dbg.memory.trackingAllocationSites = true;
|
||||
|
||||
return Date.now();
|
||||
@ -253,6 +261,15 @@ let MemoryActor = protocol.ActorClass({
|
||||
* profiling and done only when necessary.
|
||||
*/
|
||||
getAllocations: method(expectState("attached", function() {
|
||||
if (this.dbg.memory.allocationsLogOverflowed) {
|
||||
// Since the last time we drained the allocations log, there have been
|
||||
// more allocations than the log's capacity, and we lost some data. There
|
||||
// isn't anything actionable we can do about this, but put a message in
|
||||
// the browser console so we at least know that it occurred.
|
||||
reportException("MemoryActor.prototype.getAllocations",
|
||||
"Warning: allocations log overflowed and lost some data.");
|
||||
}
|
||||
|
||||
const allocations = this.dbg.memory.drainAllocationsLog()
|
||||
const packet = {
|
||||
allocations: [],
|
||||
|
@ -747,8 +747,8 @@ var PageStyleActor = protocol.ActorClass({
|
||||
// the size of the element.
|
||||
|
||||
let clientRect = node.rawNode.getBoundingClientRect();
|
||||
layout.width = Math.ceil(clientRect.width);
|
||||
layout.height = Math.ceil(clientRect.height);
|
||||
layout.width = parseFloat(clientRect.width.toPrecision(6));
|
||||
layout.height = parseFloat(clientRect.height.toPrecision(6));
|
||||
|
||||
// We compute and update the values of margins & co.
|
||||
let style = CssLogic.getComputedStyle(node.rawNode);
|
||||
@ -776,7 +776,7 @@ var PageStyleActor = protocol.ActorClass({
|
||||
|
||||
for (let i in this.map) {
|
||||
let property = this.map[i].property;
|
||||
this.map[i].value = parseInt(style.getPropertyValue(property));
|
||||
this.map[i].value = parseFloat(style.getPropertyValue(property));
|
||||
}
|
||||
|
||||
|
||||
|
@ -67,6 +67,7 @@ skip-if = buildapp == 'mulet'
|
||||
[test_memory_allocations_03.html]
|
||||
[test_memory_allocations_04.html]
|
||||
[test_memory_allocations_05.html]
|
||||
[test_memory_allocations_06.html]
|
||||
[test_memory_attach_01.html]
|
||||
[test_memory_attach_02.html]
|
||||
[test_memory_census.html]
|
||||
|
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1132764 - Test controlling the maximum allocations log length over the RDP.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Memory monitoring actor test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function* () {
|
||||
var { memory, client } = yield startServerAndGetSelectedTabMemory();
|
||||
yield memory.attach();
|
||||
|
||||
var allocs = [];
|
||||
function allocator() {
|
||||
allocs.push(new Object);
|
||||
}
|
||||
|
||||
yield memory.startRecordingAllocations({
|
||||
maxLogLength: 1
|
||||
});
|
||||
|
||||
allocator();
|
||||
allocator();
|
||||
allocator();
|
||||
|
||||
var response = yield memory.getAllocations();
|
||||
yield memory.stopRecordingAllocations();
|
||||
|
||||
is(response.allocations.length, 1,
|
||||
"There should only be one entry in the allocations log.");
|
||||
|
||||
yield memory.detach();
|
||||
destroyServerAndFinish(client);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -548,6 +548,7 @@ xul|*#categories[keyboard-navigation="true"]:-moz-focusring > xul|*.category[cur
|
||||
padding-bottom: 2px;
|
||||
-moz-padding-start: 9px;
|
||||
margin: 0;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
*|*.category-icon {
|
||||
@ -569,6 +570,7 @@ xul|*#categories[keyboard-navigation="true"]:-moz-focusring > xul|*.category[cur
|
||||
font-weight: normal;
|
||||
line-height: 40px;
|
||||
margin: 0;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
/* File fields */
|
||||
|
Loading…
Reference in New Issue
Block a user