mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-04 13:07:52 +00:00
Merge m-c to s-c
This commit is contained in:
commit
492a680813
2
CLOBBER
2
CLOBBER
@ -17,4 +17,4 @@
|
||||
#
|
||||
# Modifying this file will now automatically clobber the buildbot machines \o/
|
||||
#
|
||||
Bug 879831 needed to clobber for the removal of jsprobes.cpp
|
||||
Bug 877859 Valgrind header location updates for FxOS Valgrind
|
||||
|
@ -20,8 +20,6 @@ GARBAGE += $(MIDL_GENERATED_FILES)
|
||||
|
||||
FORCE_SHARED_LIB = 1
|
||||
|
||||
SRCS_IN_OBJDIR = 1
|
||||
|
||||
# Please keep this list in sync with the moz.build file until the rest of this
|
||||
# Makefile is ported over.
|
||||
MIDL_INTERFACES = \
|
||||
|
@ -18,8 +18,6 @@ GARBAGE += $(MIDL_GENERATED_FILES) done_gen dlldata.c
|
||||
|
||||
FORCE_SHARED_LIB = 1
|
||||
|
||||
SRCS_IN_OBJDIR = 1
|
||||
|
||||
CSRCS = \
|
||||
dlldata.c \
|
||||
ISimpleDOMNode_p.c \
|
||||
|
@ -2112,6 +2112,14 @@ HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartIndex,
|
||||
ENameValueFlag
|
||||
HyperTextAccessible::NativeName(nsString& aName)
|
||||
{
|
||||
// Check @alt attribute for invalid img elements.
|
||||
bool hasImgAlt = false;
|
||||
if (mContent->IsHTML(nsGkAtoms::img)) {
|
||||
hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName);
|
||||
if (!aName.IsEmpty())
|
||||
return eNameOK;
|
||||
}
|
||||
|
||||
ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName);
|
||||
if (!aName.IsEmpty())
|
||||
return nameFlag;
|
||||
@ -2123,7 +2131,7 @@ HyperTextAccessible::NativeName(nsString& aName)
|
||||
mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName))
|
||||
aName.CompressWhitespace();
|
||||
|
||||
return eNameOK;
|
||||
return hasImgAlt ? eNoNameOnPurpose : eNameOK;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -468,6 +468,10 @@ var Output = {
|
||||
Utils.win.navigator.vibrate(aDetails.pattern);
|
||||
},
|
||||
|
||||
Braille: function Braille(aDetails, aBrowser) {
|
||||
Logger.debug('Braille output: ' + aDetails.text);
|
||||
},
|
||||
|
||||
_adjustBounds: function(aJsonBounds, aBrowser) {
|
||||
let bounds = new Rect(aJsonBounds.left, aJsonBounds.top,
|
||||
aJsonBounds.right - aJsonBounds.left,
|
||||
|
@ -16,11 +16,11 @@ ACCESSFU_FILES := \
|
||||
EventManager.jsm \
|
||||
jar.mn \
|
||||
Makefile.in \
|
||||
OutputGenerator.jsm \
|
||||
Presentation.jsm \
|
||||
TouchAdapter.jsm \
|
||||
TraversalRules.jsm \
|
||||
Utils.jsm \
|
||||
UtteranceGenerator.jsm \
|
||||
$(NULL)
|
||||
|
||||
ACCESSFU_DEST = $(FINAL_TARGET)/modules/accessibility
|
||||
|
@ -14,9 +14,14 @@ const INCLUDE_NAME = 0x02;
|
||||
const INCLUDE_CUSTOM = 0x04;
|
||||
const NAME_FROM_SUBTREE_RULE = 0x08;
|
||||
|
||||
const UTTERANCE_DESC_FIRST = 0;
|
||||
const OUTPUT_DESC_FIRST = 0;
|
||||
const OUTPUT_DESC_LAST = 1;
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
|
||||
let gUtteranceOrder = new PrefCache('accessibility.accessfu.utterance');
|
||||
|
||||
@ -24,44 +29,12 @@ var gStringBundle = Cc['@mozilla.org/intl/stringbundle;1'].
|
||||
getService(Ci.nsIStringBundleService).
|
||||
createBundle('chrome://global/locale/AccessFu.properties');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['UtteranceGenerator'];
|
||||
this.EXPORTED_SYMBOLS = ['UtteranceGenerator', 'BrailleGenerator'];
|
||||
|
||||
|
||||
/**
|
||||
* Generates speech utterances from objects, actions and state changes.
|
||||
* An utterance is an array of strings.
|
||||
*
|
||||
* It should not be assumed that flattening an utterance array would create a
|
||||
* gramatically correct sentence. For example, {@link genForObject} might
|
||||
* return: ['graphic', 'Welcome to my home page'].
|
||||
* Each string element in an utterance should be gramatically correct in itself.
|
||||
* Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
|
||||
*
|
||||
* An utterance is ordered from the least to the most important. Speaking the
|
||||
* last string usually makes sense, but speaking the first often won't.
|
||||
* For example {@link genForAction} might return ['button', 'clicked'] for a
|
||||
* clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
|
||||
* not.
|
||||
*/
|
||||
this.UtteranceGenerator = {
|
||||
gActionMap: {
|
||||
jump: 'jumpAction',
|
||||
press: 'pressAction',
|
||||
check: 'checkAction',
|
||||
uncheck: 'uncheckAction',
|
||||
select: 'selectAction',
|
||||
open: 'openAction',
|
||||
close: 'closeAction',
|
||||
switch: 'switchAction',
|
||||
click: 'clickAction',
|
||||
collapse: 'collapseAction',
|
||||
expand: 'expandAction',
|
||||
activate: 'activateAction',
|
||||
cycle: 'cycleAction'
|
||||
},
|
||||
this.OutputGenerator = {
|
||||
|
||||
/**
|
||||
* Generates an utterance for a PivotContext.
|
||||
* Generates output for a PivotContext.
|
||||
* @param {PivotContext} aContext object that generates and caches
|
||||
* context information for a given accessible and its relationship with
|
||||
* another accessible.
|
||||
@ -70,43 +43,44 @@ this.UtteranceGenerator = {
|
||||
* starting from the accessible's ancestry or accessible's subtree.
|
||||
*/
|
||||
genForContext: function genForContext(aContext) {
|
||||
let utterance = [];
|
||||
let addUtterance = function addUtterance(aAccessible) {
|
||||
utterance.push.apply(utterance,
|
||||
UtteranceGenerator.genForObject(aAccessible));
|
||||
let output = [];
|
||||
let self = this;
|
||||
let addOutput = function addOutput(aAccessible) {
|
||||
output.push.apply(output, self.genForObject(aAccessible));
|
||||
};
|
||||
let ignoreSubtree = function ignoreSubtree(aAccessible) {
|
||||
let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
|
||||
let nameRule = UtteranceGenerator.roleRuleMap[roleString] || 0;
|
||||
let nameRule = self.roleRuleMap[roleString] || 0;
|
||||
// Ignore subtree if the name is explicit and the role's name rule is the
|
||||
// NAME_FROM_SUBTREE_RULE.
|
||||
return (nameRule & NAME_FROM_SUBTREE_RULE) &&
|
||||
(Utils.getAttributes(aAccessible)['explicit-name'] === 'true');
|
||||
};
|
||||
let utteranceOrder = gUtteranceOrder.value || UTTERANCE_DESC_FIRST;
|
||||
let outputOrder = typeof gUtteranceOrder.value == 'number' ?
|
||||
gUtteranceOrder.value : this.defaultOutputOrder;
|
||||
let contextStart = this._getContextStart(aContext);
|
||||
|
||||
if (utteranceOrder === UTTERANCE_DESC_FIRST) {
|
||||
aContext.newAncestry.forEach(addUtterance);
|
||||
addUtterance(aContext.accessible);
|
||||
[addUtterance(node) for
|
||||
if (outputOrder === OUTPUT_DESC_FIRST) {
|
||||
contextStart.forEach(addOutput);
|
||||
addOutput(aContext.accessible);
|
||||
[addOutput(node) for
|
||||
(node of aContext.subtreeGenerator(true, ignoreSubtree))];
|
||||
} else {
|
||||
[addUtterance(node) for
|
||||
[addOutput(node) for
|
||||
(node of aContext.subtreeGenerator(false, ignoreSubtree))];
|
||||
addUtterance(aContext.accessible);
|
||||
aContext.newAncestry.reverse().forEach(addUtterance);
|
||||
addOutput(aContext.accessible);
|
||||
contextStart.reverse().forEach(addOutput);
|
||||
}
|
||||
|
||||
// Clean up the white space.
|
||||
let trimmed;
|
||||
utterance = [trimmed for (word of utterance) if (trimmed = word.trim())];
|
||||
|
||||
return utterance;
|
||||
output = [trimmed for (word of output) if (trimmed = word.trim())];
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generates an utterance for an object.
|
||||
* Generates output for an object.
|
||||
* @param {nsIAccessible} aAccessible accessible object to generate utterance
|
||||
* for.
|
||||
* @return {Array} Two string array. The first string describes the object
|
||||
@ -116,9 +90,8 @@ this.UtteranceGenerator = {
|
||||
*/
|
||||
genForObject: function genForObject(aAccessible) {
|
||||
let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
|
||||
|
||||
let func = this.objectUtteranceFunctions[roleString] ||
|
||||
this.objectUtteranceFunctions.defaultFunc;
|
||||
let func = this.objectOutputFunctions[roleString.replace(' ', '')] ||
|
||||
this.objectOutputFunctions.defaultFunc;
|
||||
|
||||
let flags = this.roleRuleMap[roleString] || 0;
|
||||
|
||||
@ -134,68 +107,61 @@ this.UtteranceGenerator = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates an utterance for an action performed.
|
||||
* TODO: May become more verbose in the future.
|
||||
* Generates output for an action performed.
|
||||
* @param {nsIAccessible} aAccessible accessible object that the action was
|
||||
* invoked in.
|
||||
* @param {string} aActionName the name of the action, one of the keys in
|
||||
* {@link gActionMap}.
|
||||
* @return {Array} A one string array with the action.
|
||||
*/
|
||||
genForAction: function genForAction(aObject, aActionName) {
|
||||
return [gStringBundle.GetStringFromName(this.gActionMap[aActionName])];
|
||||
},
|
||||
genForAction: function genForAction(aObject, aActionName) {},
|
||||
|
||||
/**
|
||||
* Generates an utterance for an announcement. Basically attempts to localize
|
||||
* Generates output for an announcement. Basically attempts to localize
|
||||
* the announcement string.
|
||||
* @param {string} aAnnouncement unlocalized announcement.
|
||||
* @return {Array} A one string array with the announcement.
|
||||
*/
|
||||
genForAnnouncement: function genForAnnouncement(aAnnouncement) {
|
||||
try {
|
||||
return [gStringBundle.GetStringFromName(aAnnouncement)];
|
||||
} catch (x) {
|
||||
return [aAnnouncement];
|
||||
}
|
||||
},
|
||||
genForAnnouncement: function genForAnnouncement(aAnnouncement) {},
|
||||
|
||||
/**
|
||||
* Generates an utterance for a tab state change.
|
||||
* Generates output for a tab state change.
|
||||
* @param {nsIAccessible} aAccessible accessible object of the tab's attached
|
||||
* document.
|
||||
* @param {string} aTabState the tab state name, see
|
||||
* {@link Presenter.tabStateChanged}.
|
||||
* @return {Array} The tab state utterace.
|
||||
*/
|
||||
genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
|
||||
switch (aTabState) {
|
||||
case 'newtab':
|
||||
return [gStringBundle.GetStringFromName('tabNew')];
|
||||
case 'loading':
|
||||
return [gStringBundle.GetStringFromName('tabLoading')];
|
||||
case 'loaded':
|
||||
return [aObject.name || '',
|
||||
gStringBundle.GetStringFromName('tabLoaded')];
|
||||
case 'loadstopped':
|
||||
return [gStringBundle.GetStringFromName('tabLoadStopped')];
|
||||
case 'reload':
|
||||
return [gStringBundle.GetStringFromName('tabReload')];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
},
|
||||
genForTabStateChange: function genForTabStateChange(aObject, aTabState) {},
|
||||
|
||||
/**
|
||||
* Generates an utterance for announcing entering and leaving editing mode.
|
||||
* Generates output for announcing entering and leaving editing mode.
|
||||
* @param {aIsEditing} boolean true if we are in editing mode
|
||||
* @return {Array} The mode utterance
|
||||
*/
|
||||
genForEditingMode: function genForEditingMode(aIsEditing) {
|
||||
return [gStringBundle.GetStringFromName(
|
||||
aIsEditing ? 'editingMode' : 'navigationMode')];
|
||||
genForEditingMode: function genForEditingMode(aIsEditing) {},
|
||||
|
||||
_getContextStart: function getContextStart(aContext) {},
|
||||
|
||||
_addName: function _addName(aOutput, aAccessible, aFlags) {
|
||||
let name;
|
||||
if (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' ||
|
||||
(aFlags & INCLUDE_NAME)) {
|
||||
name = aAccessible.name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
let outputOrder = typeof gUtteranceOrder.value == 'number' ?
|
||||
gUtteranceOrder.value : this.defaultOutputOrder;
|
||||
aOutput[outputOrder === OUTPUT_DESC_FIRST ?
|
||||
'push' : 'unshift'](name);
|
||||
}
|
||||
},
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {},
|
||||
|
||||
_getLocalizedStates: function _getLocalizedStates(aStates) {},
|
||||
|
||||
roleRuleMap: {
|
||||
'menubar': INCLUDE_DESC,
|
||||
'scrollbar': INCLUDE_DESC,
|
||||
@ -268,35 +234,119 @@ this.UtteranceGenerator = {
|
||||
'listbox': INCLUDE_DESC,
|
||||
'definitionlist': INCLUDE_DESC | INCLUDE_NAME},
|
||||
|
||||
objectUtteranceFunctions: {
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let utterance = [];
|
||||
objectOutputFunctions: {
|
||||
_generateBaseOutput: function _generateBaseOutput(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let output = [];
|
||||
|
||||
if (aFlags & INCLUDE_DESC) {
|
||||
let desc = this._getLocalizedStates(aStates);
|
||||
let roleStr = this._getLocalizedRole(aRoleStr);
|
||||
if (roleStr)
|
||||
desc.push(roleStr);
|
||||
utterance.push(desc.join(' '));
|
||||
output.push(desc.join(' '));
|
||||
}
|
||||
|
||||
this._addName(utterance, aAccessible, aFlags);
|
||||
this._addName(output, aAccessible, aFlags);
|
||||
|
||||
return utterance;
|
||||
return output;
|
||||
},
|
||||
|
||||
entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let utterance = [];
|
||||
let output = [];
|
||||
let desc = this._getLocalizedStates(aStates);
|
||||
desc.push(this._getLocalizedRole(
|
||||
(aStates.ext & Ci.nsIAccessibleStates.EXT_STATE_MULTI_LINE) ?
|
||||
'textarea' : 'entry'));
|
||||
|
||||
utterance.push(desc.join(' '));
|
||||
output.push(desc.join(' '));
|
||||
|
||||
this._addName(utterance, aAccessible, aFlags);
|
||||
this._addName(output, aAccessible, aFlags);
|
||||
|
||||
return utterance;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates speech utterances from objects, actions and state changes.
|
||||
* An utterance is an array of strings.
|
||||
*
|
||||
* It should not be assumed that flattening an utterance array would create a
|
||||
* gramatically correct sentence. For example, {@link genForObject} might
|
||||
* return: ['graphic', 'Welcome to my home page'].
|
||||
* Each string element in an utterance should be gramatically correct in itself.
|
||||
* Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
|
||||
*
|
||||
* An utterance is ordered from the least to the most important. Speaking the
|
||||
* last string usually makes sense, but speaking the first often won't.
|
||||
* For example {@link genForAction} might return ['button', 'clicked'] for a
|
||||
* clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
|
||||
* not.
|
||||
*/
|
||||
this.UtteranceGenerator = {
|
||||
__proto__: OutputGenerator,
|
||||
|
||||
defaultOutputOrder: OUTPUT_DESC_FIRST,
|
||||
|
||||
gActionMap: {
|
||||
jump: 'jumpAction',
|
||||
press: 'pressAction',
|
||||
check: 'checkAction',
|
||||
uncheck: 'uncheckAction',
|
||||
select: 'selectAction',
|
||||
open: 'openAction',
|
||||
close: 'closeAction',
|
||||
switch: 'switchAction',
|
||||
click: 'clickAction',
|
||||
collapse: 'collapseAction',
|
||||
expand: 'expandAction',
|
||||
activate: 'activateAction',
|
||||
cycle: 'cycleAction'
|
||||
},
|
||||
|
||||
//TODO: May become more verbose in the future.
|
||||
genForAction: function genForAction(aObject, aActionName) {
|
||||
return [gStringBundle.GetStringFromName(this.gActionMap[aActionName])];
|
||||
},
|
||||
|
||||
genForAnnouncement: function genForAnnouncement(aAnnouncement) {
|
||||
try {
|
||||
return [gStringBundle.GetStringFromName(aAnnouncement)];
|
||||
} catch (x) {
|
||||
return [aAnnouncement];
|
||||
}
|
||||
},
|
||||
|
||||
genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
|
||||
switch (aTabState) {
|
||||
case 'newtab':
|
||||
return [gStringBundle.GetStringFromName('tabNew')];
|
||||
case 'loading':
|
||||
return [gStringBundle.GetStringFromName('tabLoading')];
|
||||
case 'loaded':
|
||||
return [aObject.name || '',
|
||||
gStringBundle.GetStringFromName('tabLoaded')];
|
||||
case 'loadstopped':
|
||||
return [gStringBundle.GetStringFromName('tabLoadStopped')];
|
||||
case 'reload':
|
||||
return [gStringBundle.GetStringFromName('tabReload')];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
genForEditingMode: function genForEditingMode(aIsEditing) {
|
||||
return [gStringBundle.GetStringFromName(
|
||||
aIsEditing ? 'editingMode' : 'navigationMode')];
|
||||
},
|
||||
|
||||
objectOutputFunctions: {
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
|
||||
},
|
||||
|
||||
entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions.entry.apply(this, arguments);
|
||||
},
|
||||
|
||||
heading: function heading(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
@ -338,25 +388,15 @@ this.UtteranceGenerator = {
|
||||
application: function application(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
// Don't utter location of applications, it gets tiring.
|
||||
if (aAccessible.name != aAccessible.DOMNode.location)
|
||||
return this.objectUtteranceFunctions.defaultFunc.apply(this,
|
||||
return this.objectOutputFunctions.defaultFunc.apply(this,
|
||||
[aAccessible, aRoleStr, aStates, aFlags]);
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
_addName: function _addName(utterance, aAccessible, aFlags) {
|
||||
let name;
|
||||
if (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' ||
|
||||
(aFlags & INCLUDE_NAME)) {
|
||||
name = aAccessible.name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
let utteranceOrder = gUtteranceOrder.value || UTTERANCE_DESC_FIRST;
|
||||
utterance[utteranceOrder === UTTERANCE_DESC_FIRST ?
|
||||
'push' : 'unshift'](name);
|
||||
}
|
||||
_getContextStart: function _getContextStart(aContext) {
|
||||
return aContext.newAncestry;
|
||||
},
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {
|
||||
@ -419,3 +459,118 @@ this.UtteranceGenerator = {
|
||||
return utterance;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.BrailleGenerator = {
|
||||
__proto__: OutputGenerator,
|
||||
|
||||
defaultOutputOrder: OUTPUT_DESC_LAST,
|
||||
|
||||
objectOutputFunctions: {
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let braille = OutputGenerator.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
|
||||
|
||||
if (aAccessible.indexInParent === 1 &&
|
||||
aAccessible.parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM &&
|
||||
aAccessible.previousSibling.role == Ci.nsIAccessibleRole.ROLE_STATICTEXT) {
|
||||
if (aAccessible.parent.parent && aAccessible.parent.parent.DOMNode &&
|
||||
aAccessible.parent.parent.DOMNode.nodeName == 'UL') {
|
||||
braille.unshift('*');
|
||||
} else {
|
||||
braille.unshift(aAccessible.previousSibling.name);
|
||||
}
|
||||
}
|
||||
|
||||
return braille;
|
||||
},
|
||||
|
||||
listitem: function listitem(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let braille = [];
|
||||
|
||||
this._addName(braille, aAccessible, aFlags);
|
||||
|
||||
return braille;
|
||||
},
|
||||
|
||||
statictext: function statictext(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
// Since we customize the list bullet's output, we add the static
|
||||
// text from the first node in each listitem, so skip it here.
|
||||
if (aAccessible.parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
_useStateNotRole: function _useStateNotRole(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let braille = [];
|
||||
|
||||
let desc = this._getLocalizedStates(aStates);
|
||||
braille.push(desc.join(' '));
|
||||
|
||||
this._addName(braille, aAccessible, aFlags);
|
||||
|
||||
return braille;
|
||||
},
|
||||
|
||||
checkbutton: function checkbutton(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
radiobutton: function radiobutton(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
togglebutton: function radiobutton(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions.entry.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
|
||||
_getContextStart: function _getContextStart(aContext) {
|
||||
if (aContext.accessible.parent.role == Ci.nsIAccessibleRole.ROLE_LINK) {
|
||||
return [aContext.accessible.parent];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {
|
||||
try {
|
||||
return gStringBundle.GetStringFromName(aRoleStr.replace(' ', '') + 'Abbr');
|
||||
} catch (x) {
|
||||
try {
|
||||
return gStringBundle.GetStringFromName(aRoleStr.replace(' ', ''));
|
||||
} catch (y) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getLocalizedStates: function _getLocalizedStates(aStates) {
|
||||
let stateBraille = [];
|
||||
|
||||
let getCheckedState = function getCheckedState() {
|
||||
let resultMarker = [];
|
||||
let state = aStates.base;
|
||||
let fill = !!(state & Ci.nsIAccessibleStates.STATE_CHECKED) ||
|
||||
!!(state & Ci.nsIAccessibleStates.STATE_PRESSED);
|
||||
|
||||
resultMarker.push('(');
|
||||
resultMarker.push(fill ? 'x' : ' ');
|
||||
resultMarker.push(')');
|
||||
|
||||
return resultMarker.join('');
|
||||
};
|
||||
|
||||
if (aStates.base & Ci.nsIAccessibleStates.STATE_CHECKABLE) {
|
||||
stateBraille.push(getCheckedState());
|
||||
}
|
||||
|
||||
return stateBraille;
|
||||
}
|
||||
|
||||
};
|
@ -9,8 +9,17 @@ const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import('resource://gre/modules/accessibility/UtteranceGenerator.jsm');
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PivotContext',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'UtteranceGenerator',
|
||||
'resource://gre/modules/accessibility/OutputGenerator.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'BrailleGenerator',
|
||||
'resource://gre/modules/accessibility/OutputGenerator.jsm');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['Presentation'];
|
||||
|
||||
@ -219,6 +228,15 @@ AndroidPresenter.prototype = {
|
||||
|
||||
let state = Utils.getStates(aContext.accessible)[0];
|
||||
|
||||
let brailleText = '';
|
||||
if (Utils.AndroidSdkVersion >= 16) {
|
||||
if (!this._braillePresenter) {
|
||||
this._braillePresenter = new BraillePresenter();
|
||||
}
|
||||
brailleText = this._braillePresenter.pivotChanged(aContext, aReason).
|
||||
details.text;
|
||||
}
|
||||
|
||||
androidEvents.push({eventType: (isExploreByTouch) ?
|
||||
this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
|
||||
text: UtteranceGenerator.genForContext(aContext),
|
||||
@ -227,7 +245,8 @@ AndroidPresenter.prototype = {
|
||||
checkable: !!(state &
|
||||
Ci.nsIAccessibleStates.STATE_CHECKABLE),
|
||||
checked: !!(state &
|
||||
Ci.nsIAccessibleStates.STATE_CHECKED)});
|
||||
Ci.nsIAccessibleStates.STATE_CHECKED),
|
||||
brailleText: brailleText});
|
||||
|
||||
|
||||
return {
|
||||
@ -367,6 +386,29 @@ HapticPresenter.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A braille presenter
|
||||
*/
|
||||
|
||||
this.BraillePresenter = function BraillePresenter() {};
|
||||
|
||||
BraillePresenter.prototype = {
|
||||
__proto__: Presenter.prototype,
|
||||
|
||||
type: 'Braille',
|
||||
|
||||
pivotChanged: function BraillePresenter_pivotChanged(aContext, aReason) {
|
||||
if (!aContext.accessible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let text = BrailleGenerator.genForContext(aContext);
|
||||
|
||||
return { type: this.type, details: {text: text.join(' ')} };
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.Presentation = {
|
||||
get presenters() {
|
||||
delete this.presenters;
|
||||
|
@ -14,6 +14,8 @@ this.EXPORTED_SYMBOLS = ['TraversalRules'];
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
|
||||
let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
|
||||
|
||||
function BaseTraversalRule(aRoles, aMatchFunc) {
|
||||
this._matchRoles = aRoles;
|
||||
this._matchFunc = aMatchFunc;
|
||||
@ -103,6 +105,8 @@ this.TraversalRules = {
|
||||
|
||||
return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
|
||||
}
|
||||
case Ci.nsIAccessibleRole.ROLE_GRAPHIC:
|
||||
return TraversalRules._shouldSkipImage(aAccessible);
|
||||
default:
|
||||
// Ignore the subtree, if there is one. So that we don't land on
|
||||
// the same content that was already presented by its parent.
|
||||
@ -168,7 +172,10 @@ this.TraversalRules = {
|
||||
Ci.nsIAccessibleRole.ROLE_CHECK_MENU_ITEM]),
|
||||
|
||||
Graphic: new BaseTraversalRule(
|
||||
[Ci.nsIAccessibleRole.ROLE_GRAPHIC]),
|
||||
[Ci.nsIAccessibleRole.ROLE_GRAPHIC],
|
||||
function Graphic_match(aAccessible) {
|
||||
return TraversalRules._shouldSkipImage(aAccessible);
|
||||
}),
|
||||
|
||||
Heading: new BaseTraversalRule(
|
||||
[Ci.nsIAccessibleRole.ROLE_HEADING]),
|
||||
@ -211,5 +218,12 @@ this.TraversalRules = {
|
||||
|
||||
Checkbox: new BaseTraversalRule(
|
||||
[Ci.nsIAccessibleRole.ROLE_CHECKBUTTON,
|
||||
Ci.nsIAccessibleRole.ROLE_CHECK_MENU_ITEM])
|
||||
Ci.nsIAccessibleRole.ROLE_CHECK_MENU_ITEM]),
|
||||
|
||||
_shouldSkipImage: function _shouldSkipImage(aAccessible) {
|
||||
if (gSkipEmptyImages.value && aAccessible.name === '') {
|
||||
return Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
|
||||
}
|
||||
return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
|
||||
}
|
||||
};
|
||||
|
@ -16,6 +16,8 @@ XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'EventManager',
|
||||
'resource://gre/modules/accessibility/EventManager.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'ObjectWrapper',
|
||||
'resource://gre/modules/ObjectWrapper.jsm');
|
||||
|
||||
Logger.debug('content-script.js');
|
||||
|
||||
@ -182,6 +184,21 @@ function scroll(aMessage) {
|
||||
while (acc) {
|
||||
let elem = acc.DOMNode;
|
||||
|
||||
// This is inspired by IndieUI events. Once they are
|
||||
// implemented, it should be easy to transition to them.
|
||||
// https://dvcs.w3.org/hg/IndieUI/raw-file/tip/src/indie-ui-events.html#scrollrequest
|
||||
let uiactions = elem.getAttribute ? elem.getAttribute('uiactions') : '';
|
||||
if (uiactions && uiactions.split(' ').indexOf('scroll') >= 0) {
|
||||
let evt = elem.ownerDocument.createEvent('CustomEvent');
|
||||
let details = horiz ? { deltaX: page * elem.clientWidth } :
|
||||
{ deltaY: page * elem.clientHeight };
|
||||
evt.initCustomEvent(
|
||||
'scrollrequest', true, true,
|
||||
ObjectWrapper.wrap(details, elem.ownerDocument.defaultView));
|
||||
if (!elem.dispatchEvent(evt))
|
||||
return;
|
||||
}
|
||||
|
||||
// We will do window scrolling next.
|
||||
if (elem == content.document)
|
||||
break;
|
||||
@ -202,25 +219,6 @@ function scroll(aMessage) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let controllers = acc.
|
||||
getRelationByType(
|
||||
Ci.nsIAccessibleRelation.RELATION_CONTROLLED_BY);
|
||||
for (let i = 0; controllers.targetsCount > i; i++) {
|
||||
let controller = controllers.getTarget(i);
|
||||
// If the section has a controlling slider, it should be considered
|
||||
// the page-turner.
|
||||
if (controller.role == Ci.nsIAccessibleRole.ROLE_SLIDER) {
|
||||
// Sliders are controlled with ctrl+right/left. I just decided :)
|
||||
let evt = content.document.createEvent('KeyboardEvent');
|
||||
evt.initKeyEvent(
|
||||
'keypress', true, true, null,
|
||||
true, false, false, false,
|
||||
(page > 0) ? evt.DOM_VK_RIGHT : evt.DOM_VK_LEFT, 0);
|
||||
controller.DOMNode.dispatchEvent(evt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
acc = acc.parent;
|
||||
}
|
||||
|
@ -13,10 +13,11 @@ include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MOCHITEST_A11Y_FILES =\
|
||||
jsatcommon.js \
|
||||
utterance.js \
|
||||
output.js \
|
||||
test_alive.html \
|
||||
test_explicit_names.html \
|
||||
test_utterance_order.html \
|
||||
test_braille.html \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
80
accessible/tests/mochitest/jsat/output.js
Normal file
80
accessible/tests/mochitest/jsat/output.js
Normal file
@ -0,0 +1,80 @@
|
||||
const Cu = Components.utils;
|
||||
const PREF_UTTERANCE_ORDER = "accessibility.accessfu.utterance";
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import("resource://gre/modules/accessibility/OutputGenerator.jsm", this);
|
||||
|
||||
/**
|
||||
* Test context output generation.
|
||||
*
|
||||
* @param expected {Array} expected output.
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aOldAccOrElmOrID optional identifier to get an accessible relative to
|
||||
* the |aAccOrElmOrID|.
|
||||
* @param aGenerator the output generator to use when generating accessible
|
||||
* output
|
||||
*
|
||||
* Note: if |aOldAccOrElmOrID| is not provided, the |aAccOrElmOrID| must be
|
||||
* scoped to the "root" element in markup.
|
||||
*/
|
||||
function testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aGenerator) {
|
||||
aOldAccOrElmOrID = aOldAccOrElmOrID || "root";
|
||||
var accessible = getAccessible(aAccOrElmOrID);
|
||||
var oldAccessible = getAccessible(aOldAccOrElmOrID);
|
||||
var context = new PivotContext(accessible, oldAccessible);
|
||||
var output = aGenerator.genForContext(context);
|
||||
isDeeply(output, expected,
|
||||
"Context output is correct for " + aAccOrElmOrID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object output generated array that includes names.
|
||||
* Note: test ignores outputs without the name.
|
||||
*
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aGenerator the output generator to use when generating accessible
|
||||
* output
|
||||
*/
|
||||
function testObjectOutput(aAccOrElmOrID, aGenerator) {
|
||||
var accessible = getAccessible(aAccOrElmOrID);
|
||||
var output = aGenerator.genForObject(accessible);
|
||||
var outputOrder;
|
||||
try {
|
||||
outputOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER);
|
||||
} catch (ex) {
|
||||
// PREF_UTTERANCE_ORDER not set.
|
||||
outputOrder = 0;
|
||||
}
|
||||
var expectedNameIndex = outputOrder === 0 ? output.length - 1 : 0;
|
||||
var nameIndex = output.indexOf(accessible.name);
|
||||
|
||||
if (nameIndex > -1) {
|
||||
ok(output.indexOf(accessible.name) === expectedNameIndex,
|
||||
"Object output is correct for " + aAccOrElmOrID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object and context output for an accessible.
|
||||
*
|
||||
* @param expected {Array} expected output.
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aOldAccOrElmOrID optional identifier to get an accessible relative to
|
||||
* the |aAccOrElmOrID|.
|
||||
* @param aOutputKind the type of output
|
||||
*/
|
||||
function testOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aOutputKind) {
|
||||
var generator;
|
||||
if (aOutputKind === 1) {
|
||||
generator = UtteranceGenerator;
|
||||
} else {
|
||||
generator = BrailleGenerator;
|
||||
}
|
||||
testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, generator);
|
||||
// Just need to test object output for individual
|
||||
// accOrElmOrID.
|
||||
if (aOldAccOrElmOrID) {
|
||||
return;
|
||||
}
|
||||
testObjectOutput(aAccOrElmOrID, generator);
|
||||
}
|
114
accessible/tests/mochitest/jsat/test_braille.html
Normal file
114
accessible/tests/mochitest/jsat/test_braille.html
Normal file
@ -0,0 +1,114 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=876475
|
||||
-->
|
||||
<head>
|
||||
<title>[AccessFu] braille generation test</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="./output.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function doTest() {
|
||||
// Test the following accOrElmOrID (with optional old accOrElmOrID).
|
||||
// Note: each accOrElmOrID entry maps to a unique object braille
|
||||
// generator function within the BrailleGenerator.
|
||||
var tests = [{
|
||||
accOrElmOrID: "link",
|
||||
expected: [["lnk", "Link"], ["Link", "lnk"]]
|
||||
},{
|
||||
accOrElmOrID: "button",
|
||||
expected: [["btn", "I am a button"], ["I am a button", "btn"]]
|
||||
},{
|
||||
accOrElmOrID: "password_input",
|
||||
expected: [["passwdtxt", "Secret Password"], ["Secret Password", "passwdtxt"]]
|
||||
},{
|
||||
accOrElmOrID: "checkbox_unchecked",
|
||||
expected: [["( )", "checkboxtext"], ["checkboxtext", "( )"]]
|
||||
},{
|
||||
accOrElmOrID: "checkbox_checked",
|
||||
expected: [["(x)", "some more checkbox text"], ["some more checkbox text", "(x)"]]
|
||||
},{
|
||||
accOrElmOrID: "radio_unselected",
|
||||
expected: [["( )", "any old radio button"], ["any old radio button", "( )"]]
|
||||
},{
|
||||
accOrElmOrID: "radio_selected",
|
||||
expected: [["(x)", "a unique radio button"], ["a unique radio button", "(x)"]]
|
||||
},{
|
||||
accOrElmOrID: "togglebutton_notpressed",
|
||||
expected: [["( )", "I ain't pressed"], ["I ain't pressed", "( )"]]
|
||||
},{
|
||||
accOrElmOrID: "togglebutton_pressed",
|
||||
expected: [["(x)", "I am pressed!"], ["I am pressed!", "(x)"]]
|
||||
},{
|
||||
accOrElmOrID: "ul_li_one",
|
||||
expected: [["*", "ul item 1"], ["*", "ul item 1"]]
|
||||
},{
|
||||
accOrElmOrID: "ol_li_one",
|
||||
expected: [["1.", "ol item 1"], ["1.", "ol item 1"]]
|
||||
},{
|
||||
accOrElmOrID: "textarea",
|
||||
expected: [["txtarea", "Here lies treasure."], ["Here lies treasure.", "txtarea"]]
|
||||
}];
|
||||
|
||||
// Test all possible braille order preference values.
|
||||
tests.forEach(function run(test) {
|
||||
var brailleOrderValues = [0, 1];
|
||||
brailleOrderValues.forEach(
|
||||
function testBrailleOrder(brailleOrder) {
|
||||
SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, brailleOrder);
|
||||
var expected = test.expected[brailleOrder];
|
||||
testOutput(expected, test.accOrElmOrID, test.oldAccOrElmOrID, 2);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// If there was an original utterance order preference, revert to it.
|
||||
SpecialPowers.clearUserPref(PREF_UTTERANCE_ORDER);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<a href="example.com" id="link">Link</a>
|
||||
<button id="button">I am a button</button>
|
||||
<label for="password_input">Secret Password</label><input id="password_input" type="password"></input>
|
||||
<label for="checkbox_unchecked">checkboxtext</label><input id="checkbox_unchecked" type="checkbox"></input>
|
||||
<label for="checkbox_checked">some more checkbox text</label><input id="checkbox_checked" type="checkbox" checked></input>
|
||||
<label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input>
|
||||
<label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input>
|
||||
<div id="togglebutton_notpressed" aria-pressed="false" role="button" tabindex="-1">I ain't pressed</div>
|
||||
<div id="togglebutton_pressed" aria-pressed="true" role="button" tabindex="-1">I am pressed!</div>
|
||||
<ol id="ordered_list">
|
||||
<li id="ol_li_one">ol item 1</li>
|
||||
<li id="ol_li_two">ol item 2</li>
|
||||
<li id="ol_li_three">ol item 3</li>
|
||||
<li id="ol_li_three">ol item 4</li>
|
||||
</ol>
|
||||
<ul id="unordered_list">
|
||||
<li id="ul_li_one">ul item 1</li>
|
||||
<li id="ul_li_two">ul item 2</li>
|
||||
<li id="ul_li_three">ul item 3</li>
|
||||
<li id="ul_li_three">ul item 4</li>
|
||||
</ul>
|
||||
<textarea id="textarea" cols="80" rows="5">
|
||||
Here lies treasure.
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -9,7 +9,7 @@
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="utterance.js"></script>
|
||||
src="output.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function doTest() {
|
||||
@ -87,11 +87,14 @@
|
||||
"Plums"]
|
||||
}];
|
||||
|
||||
SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, 0);
|
||||
|
||||
// Test various explicit names vs the utterance generated from subtrees.
|
||||
tests.forEach(function run(test) {
|
||||
testUtterance(test.expected, test.accOrElmOrID, test.oldAccOrElmOrID);
|
||||
testOutput(test.expected, test.accOrElmOrID, test.oldAccOrElmOrID, 1);
|
||||
});
|
||||
|
||||
SpecialPowers.clearUserPref(PREF_UTTERANCE_ORDER);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
@ -163,4 +166,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -13,7 +13,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="./utterance.js"></script>
|
||||
src="./output.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function doTest() {
|
||||
@ -120,7 +120,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
|
||||
function testUtteranceOrder(utteranceOrder) {
|
||||
SpecialPowers.setIntPref(PREF_UTTERANCE_ORDER, utteranceOrder);
|
||||
var expected = test.expected[utteranceOrder];
|
||||
testUtterance(expected, test.accOrElmOrID, test.oldAccOrElmOrID);
|
||||
testOutput(expected, test.accOrElmOrID, test.oldAccOrElmOrID, 1);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1,70 +0,0 @@
|
||||
const Cu = Components.utils;
|
||||
const PREF_UTTERANCE_ORDER = "accessibility.accessfu.utterance";
|
||||
|
||||
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
||||
Cu.import("resource://gre/modules/accessibility/UtteranceGenerator.jsm",
|
||||
this);
|
||||
|
||||
/**
|
||||
* Test context utterance generation.
|
||||
*
|
||||
* @param expected {Array} expected utterance.
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aOldAccOrElmOrID optional identifier to get an accessible relative to
|
||||
* the |aAccOrElmOrID|.
|
||||
*
|
||||
* Note: if |aOldAccOrElmOrID| is not provided, the |aAccOrElmOrID| must be
|
||||
* scoped to the "root" element in markup.
|
||||
*/
|
||||
function testContextUtterance(expected, aAccOrElmOrID, aOldAccOrElmOrID) {
|
||||
aOldAccOrElmOrID = aOldAccOrElmOrID || "root";
|
||||
var accessible = getAccessible(aAccOrElmOrID);
|
||||
var oldAccessible = getAccessible(aOldAccOrElmOrID);
|
||||
var context = new PivotContext(accessible, oldAccessible);
|
||||
var utterance = UtteranceGenerator.genForContext(context);
|
||||
isDeeply(utterance, expected,
|
||||
"Context utterance is correct for " + aAccOrElmOrID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object utterance generated array that includes names.
|
||||
* Note: test ignores utterances without the name.
|
||||
*
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
*/
|
||||
function testObjectUtterance(aAccOrElmOrID) {
|
||||
var accessible = getAccessible(aAccOrElmOrID);
|
||||
var utterance = UtteranceGenerator.genForObject(accessible);
|
||||
var utteranceOrder;
|
||||
try {
|
||||
utteranceOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER);
|
||||
} catch (ex) {
|
||||
// PREF_UTTERANCE_ORDER not set.
|
||||
utteranceOrder = 0;
|
||||
}
|
||||
var expectedNameIndex = utteranceOrder === 0 ? utterance.length - 1 : 0;
|
||||
var nameIndex = utterance.indexOf(accessible.name);
|
||||
|
||||
if (nameIndex > -1) {
|
||||
ok(utterance.indexOf(accessible.name) === expectedNameIndex,
|
||||
"Object utterance is correct for " + aAccOrElmOrID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object and context utterance for an accessible.
|
||||
*
|
||||
* @param expected {Array} expected utterance.
|
||||
* @param aAccOrElmOrID identifier to get an accessible to test.
|
||||
* @param aOldAccOrElmOrID optional identifier to get an accessible relative to
|
||||
* the |aAccOrElmOrID|.
|
||||
*/
|
||||
function testUtterance(expected, aAccOrElmOrID, aOldAccOrElmOrID) {
|
||||
testContextUtterance(expected, aAccOrElmOrID, aOldAccOrElmOrID);
|
||||
// Just need to test object utterance for individual
|
||||
// accOrElmOrID.
|
||||
if (aOldAccOrElmOrID) {
|
||||
return;
|
||||
}
|
||||
testObjectUtterance(aAccOrElmOrID);
|
||||
}
|
@ -208,6 +208,7 @@
|
||||
|
||||
// Test equation image
|
||||
testName("img_eq", "x^2 + y^2 + z^2")
|
||||
testName("input_img_eq", "x^2 + y^2 + z^2")
|
||||
testName("txt_eq", "x^2 + y^2 + z^2")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
@ -606,6 +607,7 @@
|
||||
|
||||
<p>Image:
|
||||
<img id="img_eq" role="math" src="foo" alt="x^2 + y^2 + z^2">
|
||||
<input type="image" id="input_img_eq" src="foo" alt="x^2 + y^2 + z^2">
|
||||
</p>
|
||||
|
||||
<p>Text:
|
||||
|
@ -34,6 +34,7 @@ MOCHITEST_A11Y_FILES =\
|
||||
test_groupbox.xul \
|
||||
test_iframe.html \
|
||||
test_img.html \
|
||||
test_invalid_img.xhtml \
|
||||
test_invalidationlist.html \
|
||||
test_list.html \
|
||||
test_map.html \
|
||||
|
50
accessible/tests/mochitest/tree/test_invalid_img.xhtml
Normal file
50
accessible/tests/mochitest/tree/test_invalid_img.xhtml
Normal file
@ -0,0 +1,50 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>invalid html img</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="../common.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="../role.js"></script>
|
||||
|
||||
<script>
|
||||
<![CDATA[
|
||||
function doTest()
|
||||
{
|
||||
document.getElementsByTagName("img")[0].firstChild.data = "2";
|
||||
|
||||
var accTree = {
|
||||
role: ROLE_TEXT_CONTAINER,
|
||||
children: [ { role: ROLE_TEXT_LEAF } ]
|
||||
};
|
||||
testAccessibleTree("the_img", accTree);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addA11yLoadEvent(doTest);
|
||||
]]>
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a target="_blank"
|
||||
title="use HyperTextAccessible for invalid img"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=852129">
|
||||
Mozilla Bug 852129
|
||||
</a>
|
||||
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
|
||||
<img id="the_img">1</img>
|
||||
</body>
|
||||
</html>
|
@ -1150,3 +1150,7 @@ function closeBrowserWindow(window, callback) {
|
||||
}, false);
|
||||
window.close();
|
||||
}
|
||||
|
||||
// Test disabled on Linux because of bug 882867
|
||||
if (require("sdk/system/runtime").OS == "Linux")
|
||||
module.exports = {};
|
||||
|
@ -51,7 +51,6 @@ STL_FLAGS=
|
||||
LIBS += $(JEMALLOC_LIBS)
|
||||
|
||||
LIBS += \
|
||||
$(EXTRA_DSO_LIBS) \
|
||||
$(XPCOM_STANDALONE_GLUE_LDOPTS) \
|
||||
$(NULL)
|
||||
|
||||
|
@ -377,23 +377,32 @@ pref("dom.ipc.processCount", 100000);
|
||||
|
||||
pref("dom.ipc.browser_frames.oop_by_default", false);
|
||||
|
||||
// Temporary permission hack for WebSMS
|
||||
// WebSMS
|
||||
pref("dom.sms.enabled", true);
|
||||
pref("dom.sms.strict7BitEncoding", false); // Disabled by default.
|
||||
pref("dom.sms.requestStatusReport", true); // Enabled by default.
|
||||
|
||||
// Temporary permission hack for WebContacts
|
||||
// WebContacts
|
||||
pref("dom.mozContacts.enabled", true);
|
||||
pref("dom.navigator-property.disable.mozContacts", false);
|
||||
pref("dom.global-constructor.disable.mozContact", false);
|
||||
|
||||
// Shortnumber matching needed for e.g. Brazil:
|
||||
// 01187654321 can be found with 87654321
|
||||
pref("dom.phonenumber.substringmatching.BR", 8);
|
||||
pref("dom.phonenumber.substringmatching.CO", 10);
|
||||
pref("dom.phonenumber.substringmatching.VE", 7);
|
||||
|
||||
// WebAlarms
|
||||
pref("dom.mozAlarms.enabled", true);
|
||||
|
||||
// SimplePush
|
||||
pref("services.push.enabled", true);
|
||||
// Is the network connection allowed to be up?
|
||||
// This preference should be used in UX to enable/disable push.
|
||||
pref("services.push.connection.enabled", true);
|
||||
// serverURL to be assigned by services team
|
||||
pref("services.push.serverURL", "");
|
||||
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
|
||||
pref("services.push.userAgentID", "");
|
||||
// Exponential back-off start is 5 seconds like in HTTP/1.1.
|
||||
// Maximum back-off is pingInterval.
|
||||
@ -635,6 +644,8 @@ pref("dom.disable_window_open_dialog_feature", true);
|
||||
|
||||
// Screen reader support
|
||||
pref("accessibility.accessfu.activate", 2);
|
||||
// Whether to skip images with empty alt text
|
||||
pref("accessibility.accessfu.skip_empty_images", true);
|
||||
|
||||
// Enable hit-target fluffing
|
||||
pref("ui.touch.radius.enabled", false);
|
||||
|
@ -174,6 +174,11 @@ SettingsListener.observe('language.current', 'en-US', function(value) {
|
||||
function(value) {
|
||||
Services.prefs.setBoolPref('ril.cellbroadcast.disabled', value);
|
||||
});
|
||||
|
||||
SettingsListener.observe('ril.radio.disabled', false,
|
||||
function(value) {
|
||||
Services.prefs.setBoolPref('ril.radio.disabled', value);
|
||||
});
|
||||
})();
|
||||
|
||||
//=================== DeviceInfo ====================
|
||||
|
@ -379,8 +379,27 @@ var shell = {
|
||||
case evt.DOM_VK_F1: // headset button
|
||||
type = 'headset-button';
|
||||
break;
|
||||
default: // Anything else is a real key
|
||||
return; // Don't filter it at all; let it propagate to Gaia
|
||||
}
|
||||
|
||||
let mediaKeys = {
|
||||
'MediaNextTrack': 'media-next-track-button',
|
||||
'MediaPreviousTrack': 'media-previous-track-button',
|
||||
'MediaPause': 'media-pause-button',
|
||||
'MediaPlay': 'media-play-button',
|
||||
'MediaPlayPause': 'media-play-pause-button',
|
||||
'MediaStop': 'media-stop-button',
|
||||
'MediaRewind': 'media-rewind-button',
|
||||
'FastFwd': 'media-fast-forward-button'
|
||||
};
|
||||
|
||||
let isMediaKey = false;
|
||||
if (mediaKeys[evt.key]) {
|
||||
isMediaKey = true;
|
||||
type = mediaKeys[evt.key];
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we didn't return, then the key event represents a hardware key
|
||||
@ -408,6 +427,12 @@ var shell = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMediaKey) {
|
||||
this.lastHardwareButtonEventType = type;
|
||||
gSystemMessenger.broadcastMessage('media-button', type);
|
||||
return;
|
||||
}
|
||||
|
||||
// On my device, the physical hardware buttons (sleep and volume)
|
||||
// send multiple events (press press release release), but the
|
||||
// soft home button just sends one. This hack is to manually
|
||||
|
@ -9,7 +9,7 @@ VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
EXTRA_PP_COMPONENTS = \
|
||||
DISABLED_EXTRA_PP_COMPONENTS = \
|
||||
ActivitiesGlue.js \
|
||||
AlertsService.js \
|
||||
B2GAboutRedirector.js \
|
||||
@ -36,7 +36,7 @@ EXTRA_JS_MODULES = \
|
||||
$(NULL)
|
||||
|
||||
ifdef MOZ_UPDATER
|
||||
EXTRA_PP_COMPONENTS += UpdatePrompt.js
|
||||
DISABLED_EXTRA_PP_COMPONENTS += UpdatePrompt.js
|
||||
endif
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
@ -12,3 +12,26 @@ XPIDL_SOURCES += [
|
||||
|
||||
MODULE = 'B2GComponents'
|
||||
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'ActivitiesGlue.js',
|
||||
'AlertsService.js',
|
||||
'B2GAboutRedirector.js',
|
||||
'B2GComponents.manifest',
|
||||
'ContentHandler.js',
|
||||
'ContentPermissionPrompt.js',
|
||||
'DirectoryProvider.js',
|
||||
'FilePicker.js',
|
||||
'MailtoProtocolHandler.js',
|
||||
'MozKeyboard.js',
|
||||
'PaymentGlue.js',
|
||||
'ProcessGlobal.js',
|
||||
'RecoveryService.js',
|
||||
'SmsProtocolHandler.js',
|
||||
'TelProtocolHandler.js',
|
||||
'YoutubeProtocolHandler.js',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_UPDATER']:
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'UpdatePrompt.js',
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "a7f713158285156858dcf774ce51782ac01c938c",
|
||||
"revision": "393b5209f9bc300017f2e037b1d0beeda4da7519",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
36
b2g/config/inari/config.json
Normal file
36
b2g/config/inari/config.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"config_version": 2,
|
||||
"tooltool_manifest": "releng-inari.tt",
|
||||
"mock_target": "mozilla-centos6-i386",
|
||||
"mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "java-1.6.0-openjdk-devel", "git"],
|
||||
"mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]],
|
||||
"build_targets": [],
|
||||
"upload_files": [
|
||||
"{objdir}/dist/b2g-*.crashreporter-symbols.zip",
|
||||
"{objdir}/dist/b2g-*.tar.gz",
|
||||
"{workdir}/sources.xml"
|
||||
],
|
||||
"zip_files": [
|
||||
["{workdir}/out/target/product/inari/*.img", "out/target/product/inari/"],
|
||||
["{workdir}/boot.img", "out/target/product/inari/"],
|
||||
"{workdir}/flash.sh",
|
||||
"{workdir}/load-config.sh",
|
||||
"{workdir}/.config",
|
||||
"{workdir}/sources.xml"
|
||||
],
|
||||
"env": {
|
||||
"VARIANT": "user",
|
||||
"MOZILLA_OFFICIAL": "1",
|
||||
"B2GUPDATER": "1"
|
||||
},
|
||||
"b2g_manifest": "inari.xml",
|
||||
"b2g_manifest_branch": "master",
|
||||
"additional_source_tarballs": ["backup-inari.tar.xz"],
|
||||
"gecko_l10n_root": "http://hg.mozilla.org/l10n-central",
|
||||
"gaia": {
|
||||
"l10n": {
|
||||
"vcs": "hgtool",
|
||||
"root": "http://hg.mozilla.org/gaia-l10n"
|
||||
}
|
||||
}
|
||||
}
|
20
b2g/config/inari/releng-inari.tt
Normal file
20
b2g/config/inari/releng-inari.tt
Normal file
@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"size": 4249600,
|
||||
"digest": "9f2150350e6fb2e8fe8744f47c02799865de16f8539844374188c2b421d8a044c213af57c8fe9dc7ad2038150687b50eba251dc782fa606674a86d8acc9e3dad",
|
||||
"algorithm": "sha512",
|
||||
"filename": "boot.img"
|
||||
},
|
||||
{
|
||||
"size": 33335060,
|
||||
"digest": "aa60b13458fabcb60c671e80db4dfc0cda3090fc5df27215cec3fb01f9cf2ac4403c93be99d1816a1f2d903febd4b027f7193a5fcec1aacf18ebb419ee9f32d7",
|
||||
"algorithm": "sha512",
|
||||
"filename": "backup-inari.tar.xz"
|
||||
},
|
||||
{
|
||||
"size": 1570553,
|
||||
"digest": "ea03de74df73b05e939c314cd15c54aac7b5488a407b7cc4f5f263f3049a1f69642c567dd35c43d0bc3f0d599d0385a26ab2dd947a6b18f9044e4918b382eea7",
|
||||
"algorithm": "sha512",
|
||||
"filename": "Adreno200-AU_LINUX_ANDROID_ICS_CHOCO_CS.04.00.03.06.001.zip"
|
||||
}
|
||||
]
|
@ -54,7 +54,6 @@ DEFINES += -DXPCOM_GLUE
|
||||
STL_FLAGS=
|
||||
|
||||
LIBS += \
|
||||
$(EXTRA_DSO_LIBS) \
|
||||
$(XPCOM_STANDALONE_GLUE_LDOPTS) \
|
||||
$(NULL)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1370548649000">
|
||||
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1370990544000">
|
||||
<emItems>
|
||||
<emItem blockID="i350" id="sqlmoz@facebook.com">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
@ -222,6 +222,10 @@
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i364" id="{FE1DEEEA-DB6D-44b8-83F0-34FC0F9D1052}">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i59" id="ghostviewer@youtube2.com">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
</versionRange>
|
||||
@ -253,6 +257,10 @@
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i370" id="happylyrics@hpyproductions.net">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
</emItem>
|
||||
<emItem blockID="i22" id="ShopperReports@ShopperReports.com">
|
||||
<versionRange minVersion="3.1.22.0" maxVersion="3.1.22.0">
|
||||
</versionRange>
|
||||
@ -803,7 +811,7 @@
|
||||
</versionRange>
|
||||
</pluginItem>
|
||||
<pluginItem blockID="p248">
|
||||
<match name="filename" exp="Scorch\.plugin" /> <versionRange minVersion="0" maxVersion="6.2.0" severity="1"></versionRange>
|
||||
<match name="filename" exp="Scorch\.plugin" /> <versionRange minVersion="0" maxVersion="6.2.0b88" severity="1"></versionRange>
|
||||
</pluginItem>
|
||||
<pluginItem blockID="p250">
|
||||
<match name="filename" exp="npFoxitReaderPlugin\.dll" /> <versionRange minVersion="0" maxVersion="2.2.1.530" severity="0" vulnerabilitystatus="2"></versionRange>
|
||||
@ -930,6 +938,9 @@
|
||||
</targetApplication>
|
||||
</versionRange>
|
||||
</pluginItem>
|
||||
<pluginItem blockID="p366">
|
||||
<match name="filename" exp="Scorch\.plugin" /> <versionRange minVersion="6.2.0" maxVersion="6.2.0" severity="1"></versionRange>
|
||||
</pluginItem>
|
||||
</pluginItems>
|
||||
|
||||
<gfxItems>
|
||||
|
@ -3612,6 +3612,7 @@ function mimeTypeIsTextBased(aMimeType)
|
||||
aMimeType.endsWith("+xml") ||
|
||||
aMimeType == "application/x-javascript" ||
|
||||
aMimeType == "application/javascript" ||
|
||||
aMimeType == "application/json" ||
|
||||
aMimeType == "application/xml" ||
|
||||
aMimeType == "mozilla.application/cached-xul";
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ function sendNotifyRequest(name) {
|
||||
|
||||
service.healthReporter.onInit().then(function onInit() {
|
||||
is(policy.ensureNotifyResponse(new Date()), false, "User has not responded to policy.");
|
||||
is(policy.notifyState, policy.STATE_NOTIFY_WAIT, "Policy is waiting for notification response.");
|
||||
});
|
||||
|
||||
return policy;
|
||||
@ -53,15 +52,17 @@ function waitForNotificationClose(notification, cb) {
|
||||
observer.observe(parent, {childList: true});
|
||||
}
|
||||
|
||||
let dumpAppender, rootLogger;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let ns = {};
|
||||
Components.utils.import("resource://services-common/log4moz.js", ns);
|
||||
let rootLogger = ns.Log4Moz.repository.rootLogger;
|
||||
let appender = new ns.Log4Moz.DumpAppender();
|
||||
appender.level = ns.Log4Moz.Level.All;
|
||||
rootLogger.addAppender(appender);
|
||||
rootLogger = ns.Log4Moz.repository.rootLogger;
|
||||
dumpAppender = new ns.Log4Moz.DumpAppender();
|
||||
dumpAppender.level = ns.Log4Moz.Level.All;
|
||||
rootLogger.addAppender(dumpAppender);
|
||||
|
||||
let notification = document.getElementById("global-notificationbox");
|
||||
let policy;
|
||||
@ -126,6 +127,9 @@ function test_multiple_windows() {
|
||||
}
|
||||
|
||||
dump("Finishing multiple window test.\n");
|
||||
rootLogger.removeAppender(dumpAppender);
|
||||
delete dumpAppender;
|
||||
delete rootLogger;
|
||||
finish();
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ function runAltLeftClickTest() {
|
||||
function runShiftLeftClickTest() {
|
||||
let listener = new WindowListener(getBrowserURL(), function(aWindow) {
|
||||
Services.wm.removeListener(listener);
|
||||
addPageShowListener(aWindow.gBrowser, function() {
|
||||
addPageShowListener(aWindow.gBrowser.selectedBrowser, function() {
|
||||
info("URL should be loaded in a new window");
|
||||
is(gURLBar.value, "", "Urlbar reverted to original value");
|
||||
is(gFocusManager.focusedElement, null, "There should be no focused element");
|
||||
@ -43,7 +43,7 @@ function runShiftLeftClickTest() {
|
||||
|
||||
aWindow.close();
|
||||
runNextTest();
|
||||
});
|
||||
}, "http://example.com/");
|
||||
});
|
||||
Services.wm.addListener(listener);
|
||||
|
||||
@ -61,7 +61,7 @@ function runNextTest() {
|
||||
info("Running test: " + test.desc);
|
||||
// Tab will be blank if test.startValue is null
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab(test.startValue);
|
||||
addPageShowListener(gBrowser, function() {
|
||||
addPageShowListener(gBrowser.selectedBrowser, function() {
|
||||
triggerCommand(test.click, test.event);
|
||||
test.check(tab);
|
||||
|
||||
@ -163,10 +163,13 @@ function checkNewTab(aTab) {
|
||||
isnot(gBrowser.selectedTab, aTab, "New URL was loaded in a new tab");
|
||||
}
|
||||
|
||||
function addPageShowListener(aBrowser, aFunc) {
|
||||
aBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
|
||||
aBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
|
||||
aFunc();
|
||||
function addPageShowListener(browser, cb, expectedURL) {
|
||||
browser.addEventListener("pageshow", function pageShowListener() {
|
||||
info("pageshow: " + browser.currentURI.spec);
|
||||
if (expectedURL && browser.currentURI.spec != expectedURL)
|
||||
return; // ignore pageshows for non-expected URLs
|
||||
browser.removeEventListener("pageshow", pageShowListener, false);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ function test() {
|
||||
gURLBar.value = "firefox health report";
|
||||
gURLBar.handleCommand();
|
||||
|
||||
executeSoon(function afterSearch() {
|
||||
executeSoon(() => executeSoon(() => {
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
m.getValues().then(function onData(data) {
|
||||
@ -58,7 +58,7 @@ function test() {
|
||||
is(newCount, oldCount + 1, "Exactly one search has been recorded.");
|
||||
finish();
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -712,10 +712,10 @@ function runTest(testNum) {
|
||||
var full_screen_element = subwindow.document.getElementById("test-dom-full-screen");
|
||||
var openDomFullScreen = function() {
|
||||
subwindow.removeEventListener("mozfullscreenchange", openDomFullScreen, false);
|
||||
SpecialPowers.clearUserPref("full-screen-api.allow-trusted-requests-only");
|
||||
openContextMenuFor(dom_full_screen, true); // Invoke context menu for next test.
|
||||
}
|
||||
subwindow.addEventListener("mozfullscreenchange", openDomFullScreen, false);
|
||||
SpecialPowers.setBoolPref("full-screen-api.approval-required", false);
|
||||
SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
|
||||
full_screen_element.mozRequestFullScreen();
|
||||
break;
|
||||
@ -741,11 +741,11 @@ function runTest(testNum) {
|
||||
var full_screen_element = subwindow.document.getElementById("test-dom-full-screen");
|
||||
var openPagemenu = function() {
|
||||
subwindow.removeEventListener("mozfullscreenchange", openPagemenu, false);
|
||||
SpecialPowers.clearUserPref("full-screen-api.approval-required");
|
||||
SpecialPowers.clearUserPref("full-screen-api.allow-trusted-requests-only");
|
||||
openContextMenuFor(pagemenu, true); // Invoke context menu for next test.
|
||||
}
|
||||
subwindow.addEventListener("mozfullscreenchange", openPagemenu, false);
|
||||
SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
|
||||
subwindow.document.mozCancelFullScreen();
|
||||
break;
|
||||
|
||||
|
@ -13,7 +13,7 @@ DISABLED_EXTRA_COMPONENTS = \
|
||||
BrowserComponents.manifest \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_COMPONENTS = \
|
||||
DISABLED_EXTRA_PP_COMPONENTS = \
|
||||
nsBrowserContentHandler.js \
|
||||
nsBrowserGlue.js \
|
||||
$(NULL)
|
||||
|
@ -25,7 +25,7 @@ DISABLED_EXTRA_COMPONENTS = \
|
||||
WebContentConverter.js \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_COMPONENTS = \
|
||||
DISABLED_EXTRA_PP_COMPONENTS = \
|
||||
FeedWriter.js \
|
||||
$(NULL)
|
||||
|
||||
|
@ -15,3 +15,7 @@ EXTRA_COMPONENTS += [
|
||||
'FeedConverter.js',
|
||||
'WebContentConverter.js',
|
||||
]
|
||||
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'FeedWriter.js',
|
||||
]
|
||||
|
@ -19,7 +19,7 @@ DISABLED_EXTRA_COMPONENTS = \
|
||||
FirefoxProfileMigrator.js \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_COMPONENTS = \
|
||||
DISABLED_EXTRA_PP_COMPONENTS = \
|
||||
ChromeProfileMigrator.js \
|
||||
$(NULL)
|
||||
|
||||
@ -27,19 +27,19 @@ ifeq ($(OS_ARCH),WINNT)
|
||||
DISABLED_EXTRA_COMPONENTS += IEProfileMigrator.js \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_COMPONENTS += SafariProfileMigrator.js \
|
||||
DISABLED_EXTRA_PP_COMPONENTS += SafariProfileMigrator.js \
|
||||
$(NULL)
|
||||
|
||||
DEFINES += -DHAS_IE_MIGRATOR -DHAS_SAFARI_MIGRATOR
|
||||
endif
|
||||
|
||||
ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
|
||||
EXTRA_PP_COMPONENTS += SafariProfileMigrator.js \
|
||||
DISABLED_EXTRA_PP_COMPONENTS += SafariProfileMigrator.js \
|
||||
$(NULL)
|
||||
DEFINES += -DHAS_SAFARI_MIGRATOR
|
||||
endif
|
||||
|
||||
EXTRA_PP_COMPONENTS += \
|
||||
DISABLED_EXTRA_PP_COMPONENTS += \
|
||||
BrowserProfileMigrators.manifest \
|
||||
$(NULL)
|
||||
|
||||
|
@ -20,3 +20,18 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
EXTRA_COMPONENTS += [
|
||||
'IEProfileMigrator.js',
|
||||
]
|
||||
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'BrowserProfileMigrators.manifest',
|
||||
'ChromeProfileMigrator.js',
|
||||
]
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'SafariProfileMigrator.js',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'SafariProfileMigrator.js',
|
||||
]
|
||||
|
@ -39,3 +39,7 @@ MODULE = 'browsercomps'
|
||||
EXTRA_COMPONENTS += [
|
||||
'BrowserComponents.manifest',
|
||||
]
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'nsBrowserContentHandler.js',
|
||||
'nsBrowserGlue.js',
|
||||
]
|
||||
|
@ -317,6 +317,23 @@ BrowserGlue.prototype = {
|
||||
});
|
||||
break;
|
||||
#endif
|
||||
case "browser-search-engine-modified":
|
||||
if (data != "engine-default" && data != "engine-current") {
|
||||
break;
|
||||
}
|
||||
// Enforce that the search service's defaultEngine is always equal to
|
||||
// its currentEngine. The search service will notify us any time either
|
||||
// of them are changed (either by directly setting the relevant prefs,
|
||||
// i.e. if add-ons try to change this directly, or if the
|
||||
// nsIBrowserSearchService setters are called).
|
||||
let ss = Services.search;
|
||||
if (ss.currentEngine.name == ss.defaultEngine.name)
|
||||
return;
|
||||
if (data == "engine-current")
|
||||
ss.defaultEngine = ss.currentEngine;
|
||||
else
|
||||
ss.currentEngine = ss.defaultEngine;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -351,6 +368,7 @@ BrowserGlue.prototype = {
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
os.addObserver(this, "keyword-search", false);
|
||||
#endif
|
||||
os.addObserver(this, "browser-search-engine-modified", false);
|
||||
},
|
||||
|
||||
// cleanup (called on application shutdown)
|
||||
@ -384,6 +402,7 @@ BrowserGlue.prototype = {
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
os.removeObserver(this, "keyword-search");
|
||||
#endif
|
||||
os.removeObserver(this, "browser-search-engine-modified");
|
||||
},
|
||||
|
||||
_onAppDefaults: function BG__onAppDefaults() {
|
||||
|
@ -71,7 +71,7 @@
|
||||
</hbox>
|
||||
|
||||
<!-- Tracking -->
|
||||
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
|
||||
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start">
|
||||
<caption label="&tracking.label;"/>
|
||||
<radiogroup id="doNotTrackSelection" orient="vertical"
|
||||
preference="privacy.donottrackheader.value"
|
||||
|
@ -82,7 +82,7 @@
|
||||
<script type="application/javascript" src="chrome://browser/content/preferences/privacy.js"/>
|
||||
|
||||
<!-- Tracking -->
|
||||
<groupbox id="trackingGroup">
|
||||
<groupbox id="trackingGroup" align="start">
|
||||
<caption label="&tracking.label;"/>
|
||||
<radiogroup id="doNotTrackSelection" orient="vertical"
|
||||
preference="privacy.donottrackheader.value"
|
||||
|
@ -488,17 +488,22 @@
|
||||
<handlers>
|
||||
<handler event="command"><![CDATA[
|
||||
const target = event.originalTarget;
|
||||
if (target.classList.contains("addengine-item")) {
|
||||
if (target.engine) {
|
||||
this.currentEngine = target.engine;
|
||||
} else if (target.classList.contains("addengine-item")) {
|
||||
var searchService =
|
||||
Components.classes["@mozilla.org/browser/search-service;1"]
|
||||
.getService(Components.interfaces.nsIBrowserSearchService);
|
||||
// We only detect OpenSearch files
|
||||
var type = Components.interfaces.nsISearchEngine.DATA_XML;
|
||||
// Select the installed engine if the installation succeeds
|
||||
var installCallback = {
|
||||
onSuccess: engine => this.currentEngine = engine
|
||||
}
|
||||
searchService.addEngine(target.getAttribute("uri"), type,
|
||||
target.getAttribute("src"), false);
|
||||
target.getAttribute("src"), false,
|
||||
installCallback);
|
||||
}
|
||||
else if (target.engine)
|
||||
this.currentEngine = target.engine;
|
||||
else
|
||||
return;
|
||||
|
||||
|
@ -30,8 +30,7 @@ function test() {
|
||||
case "engine-added":
|
||||
var engine = ss.getEngineByName("Bug 426329");
|
||||
ok(engine, "Engine was added.");
|
||||
//XXX Bug 493051
|
||||
//ss.currentEngine = engine;
|
||||
ss.currentEngine = engine;
|
||||
break;
|
||||
case "engine-current":
|
||||
ok(ss.currentEngine.name == "Bug 426329", "currentEngine set");
|
||||
|
@ -16,8 +16,7 @@ function test() {
|
||||
case "engine-added":
|
||||
var engine = ss.getEngineByName(ENGINE_NAME);
|
||||
ok(engine, "Engine was added.");
|
||||
//XXX Bug 493051
|
||||
//ss.currentEngine = engine;
|
||||
ss.currentEngine = engine;
|
||||
break;
|
||||
case "engine-current":
|
||||
is(ss.currentEngine.name, ENGINE_NAME, "currentEngine set");
|
||||
|
@ -66,7 +66,7 @@ function test() {
|
||||
}
|
||||
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
executeSoon(afterSearch);
|
||||
executeSoon(() => executeSoon(afterSearch));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -42,20 +42,18 @@ function test() {
|
||||
}
|
||||
|
||||
function addEngine(aCallback) {
|
||||
function observer(aSub, aTopic, aData) {
|
||||
switch (aData) {
|
||||
case "engine-current":
|
||||
ok(Services.search.currentEngine.name == "Bug 426329",
|
||||
"currentEngine set");
|
||||
aCallback();
|
||||
break;
|
||||
let installCallback = {
|
||||
onSuccess: function (engine) {
|
||||
Services.search.currentEngine = engine;
|
||||
aCallback();
|
||||
},
|
||||
onError: function (errorCode) {
|
||||
ok(false, "failed to install engine: " + errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.addObserver(observer, "browser-search-engine-modified", false);
|
||||
Services.search.addEngine(
|
||||
engineURL + "426329.xml", Ci.nsISearchEngine.DATA_XML,
|
||||
"data:image/x-icon,%00", false);
|
||||
};
|
||||
Services.search.addEngine(engineURL + "426329.xml",
|
||||
Ci.nsISearchEngine.DATA_XML,
|
||||
"data:image/x-icon,%00", false, installCallback);
|
||||
}
|
||||
|
||||
function testOnWindow(aIsPrivate, aCallback) {
|
||||
|
@ -494,23 +494,16 @@ let SessionStoreInternal = {
|
||||
},
|
||||
|
||||
_initWindow: function ssi_initWindow(aWindow) {
|
||||
if (!aWindow || this._loadState == STATE_RUNNING) {
|
||||
// make sure that all browser windows which try to initialize
|
||||
// SessionStore are really tracked by it
|
||||
if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
|
||||
this.onLoad(aWindow);
|
||||
if (aWindow) {
|
||||
this.onLoad(aWindow);
|
||||
} else if (this._loadState == STATE_STOPPED) {
|
||||
// If init is being called with a null window, it's possible that we
|
||||
// just want to tell sessionstore that a session is live (as is the case
|
||||
// with starting Firefox with -private, for example; see bug 568816),
|
||||
// so we should mark the load state as running to make sure that
|
||||
// things like setBrowserState calls will succeed in restoring the session.
|
||||
if (!aWindow && this._loadState == STATE_STOPPED)
|
||||
this._loadState = STATE_RUNNING;
|
||||
return;
|
||||
this._loadState = STATE_RUNNING;
|
||||
}
|
||||
|
||||
// As this is called at delayedStartup, restoration must be initiated here
|
||||
this.onLoad(aWindow);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -747,7 +740,7 @@ let SessionStoreInternal = {
|
||||
this._deferredInitialState._firstTabs = true;
|
||||
this._restoreCount = this._deferredInitialState.windows ?
|
||||
this._deferredInitialState.windows.length : 0;
|
||||
this.restoreWindow(aWindow, this._deferredInitialState, true);
|
||||
this.restoreWindow(aWindow, this._deferredInitialState, false);
|
||||
this._deferredInitialState = null;
|
||||
}
|
||||
else if (this._restoreLastWindow && aWindow.toolbar.visible &&
|
||||
|
@ -1920,6 +1920,7 @@ let GroupItems = {
|
||||
minGroupHeight: 110,
|
||||
minGroupWidth: 125,
|
||||
_lastActiveList: null,
|
||||
_lastGroupToUpdateTabBar: null,
|
||||
|
||||
// ----------
|
||||
// Function: toString
|
||||
@ -2285,6 +2286,10 @@ let GroupItems = {
|
||||
});
|
||||
|
||||
this._lastActiveList.remove(groupItem);
|
||||
|
||||
if (this._lastGroupToUpdateTabBar == groupItem)
|
||||
this._lastGroupToUpdateTabBar = null;
|
||||
|
||||
UI.updateTabButton();
|
||||
},
|
||||
|
||||
@ -2418,8 +2423,13 @@ let GroupItems = {
|
||||
|
||||
Utils.assert(this._activeGroupItem, "There must be something to show in the tab bar!");
|
||||
|
||||
// Update list of visible tabs only once after switching to another group.
|
||||
if (this._activeGroupItem == this._lastGroupToUpdateTabBar)
|
||||
return;
|
||||
|
||||
let tabItems = this._activeGroupItem._children;
|
||||
gBrowser.showOnlyTheseTabs(tabItems.map(function(item) item.tab));
|
||||
this._lastGroupToUpdateTabBar = this._activeGroupItem;
|
||||
},
|
||||
|
||||
// ----------
|
||||
@ -2537,7 +2547,7 @@ let GroupItems = {
|
||||
if (tab._tabViewTabItem.parent && tab._tabViewTabItem.parent.id == groupItemId)
|
||||
return;
|
||||
|
||||
let shouldUpdateTabBar = false;
|
||||
let shouldHideTab = false;
|
||||
let shouldShowTabView = false;
|
||||
let groupItem;
|
||||
|
||||
@ -2545,12 +2555,12 @@ let GroupItems = {
|
||||
if (tab.selected) {
|
||||
if (gBrowser.visibleTabs.length > 1) {
|
||||
gBrowser._blurTab(tab);
|
||||
shouldUpdateTabBar = true;
|
||||
shouldHideTab = true;
|
||||
} else {
|
||||
shouldShowTabView = true;
|
||||
}
|
||||
} else {
|
||||
shouldUpdateTabBar = true
|
||||
shouldHideTab = true;
|
||||
}
|
||||
|
||||
// remove tab item from a groupItem
|
||||
@ -2573,8 +2583,8 @@ let GroupItems = {
|
||||
new GroupItem([ tab._tabViewTabItem ], { bounds: box, immediately: true });
|
||||
}
|
||||
|
||||
if (shouldUpdateTabBar)
|
||||
this._updateTabBar();
|
||||
if (shouldHideTab)
|
||||
gBrowser.hideTab(tab);
|
||||
else if (shouldShowTabView)
|
||||
UI.showTabView();
|
||||
},
|
||||
|
@ -99,47 +99,14 @@ function test() {
|
||||
}, aWindow);
|
||||
}
|
||||
|
||||
// [624102] check state after return from private browsing
|
||||
let testPrivateBrowsing = function (aWindow) {
|
||||
aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#1', {inBackground: true});
|
||||
aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#2', {inBackground: true});
|
||||
|
||||
let cw = getContentWindow(aWindow);
|
||||
let box = new cw.Rect(20, 20, 250, 200);
|
||||
let groupItem = new cw.GroupItem([], {bounds: box, immediately: true});
|
||||
cw.UI.setActive(groupItem);
|
||||
|
||||
aWindow.gBrowser.selectedTab = aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#3', {inBackground: true});
|
||||
aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#4', {inBackground: true});
|
||||
|
||||
afterAllTabsLoaded(function () {
|
||||
assertNumberOfVisibleTabs(aWindow, 2);
|
||||
|
||||
enterAndLeavePrivateBrowsing(function () {
|
||||
assertNumberOfVisibleTabs(aWindow, 2);
|
||||
aWindow.gBrowser.selectedTab = aWindow.gBrowser.tabs[0];
|
||||
closeGroupItem(cw.GroupItems.groupItems[1], function() {
|
||||
next(aWindow);
|
||||
});
|
||||
});
|
||||
}, aWindow);
|
||||
}
|
||||
|
||||
function testOnWindow(aIsPrivate, aCallback) {
|
||||
let win = OpenBrowserWindow({private: aIsPrivate});
|
||||
function testOnWindow(aCallback) {
|
||||
let win = OpenBrowserWindow({private: false});
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad, false);
|
||||
executeSoon(function() { aCallback(win) });
|
||||
}, false);
|
||||
}
|
||||
|
||||
function enterAndLeavePrivateBrowsing(callback) {
|
||||
testOnWindow(true, function (aWindow) {
|
||||
aWindow.close();
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
// Tests for #624265
|
||||
@ -149,10 +116,7 @@ function test() {
|
||||
tests.push(testDuplicateTab);
|
||||
tests.push(testBackForwardDuplicateTab);
|
||||
|
||||
// Tests for #624102
|
||||
tests.push(testPrivateBrowsing);
|
||||
|
||||
testOnWindow(false, function(aWindow) {
|
||||
testOnWindow(function(aWindow) {
|
||||
loadTabView(function() {
|
||||
next(aWindow);
|
||||
}, aWindow);
|
||||
@ -163,4 +127,4 @@ function loadTabView(callback, aWindow) {
|
||||
showTabView(function () {
|
||||
hideTabView(callback, aWindow);
|
||||
}, aWindow);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
ac_add_options --enable-debug
|
||||
ac_add_options --enable-trace-malloc
|
||||
ac_add_options --enable-signmar
|
||||
ENABLE_MARIONETTE=1
|
||||
|
||||
. $topsrcdir/build/unix/mozconfig.linux32
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
ac_add_options --enable-debug
|
||||
ac_add_options --enable-trace-malloc
|
||||
ac_add_options --enable-signmar
|
||||
ENABLE_MARIONETTE=1
|
||||
|
||||
. $topsrcdir/build/unix/mozconfig.linux
|
||||
|
||||
|
@ -4,7 +4,6 @@ ac_add_options --enable-debug
|
||||
ac_add_options --enable-trace-malloc
|
||||
ac_add_options --enable-accessibility
|
||||
ac_add_options --enable-signmar
|
||||
ENABLE_MARIONETTE=1
|
||||
|
||||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
@ -5,8 +5,6 @@ ac_add_options --enable-trace-malloc
|
||||
ac_add_options --enable-signmar
|
||||
ac_add_options --enable-metro
|
||||
|
||||
ENABLE_MARIONETTE=1
|
||||
|
||||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
|
@ -7,7 +7,6 @@ ac_add_options --enable-debug
|
||||
ac_add_options --enable-trace-malloc
|
||||
ac_add_options --enable-signmar
|
||||
ac_add_options --enable-metro
|
||||
ENABLE_MARIONETTE=1
|
||||
|
||||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
@ -24,6 +24,7 @@ Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Parser",
|
||||
@ -73,6 +74,24 @@ let DebuggerController = {
|
||||
DebuggerView.initialize(() => {
|
||||
DebuggerView._isInitialized = true;
|
||||
|
||||
VariablesViewController.attach(DebuggerView.Variables, {
|
||||
getGripClient: aObject => {
|
||||
return this.activeThread.pauseGrip(aObject);
|
||||
}
|
||||
});
|
||||
|
||||
// Relay events from the VariablesView.
|
||||
DebuggerView.Variables.on("fetched", (aEvent, aType) => {
|
||||
switch (aType) {
|
||||
case "variables":
|
||||
window.dispatchEvent(document, "Debugger:FetchedVariables");
|
||||
break;
|
||||
case "properties":
|
||||
window.dispatchEvent(document, "Debugger:FetchedProperties");
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Chrome debugging needs to initiate the connection by itself.
|
||||
if (window._isChromeDebugger) {
|
||||
this.connect().then(deferred.resolve);
|
||||
@ -403,6 +422,7 @@ ThreadState.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Keeps the stack frame list up-to-date, using the thread client's
|
||||
* stack frame cache.
|
||||
@ -413,9 +433,6 @@ function StackFrames() {
|
||||
this._onFrames = this._onFrames.bind(this);
|
||||
this._onFramesCleared = this._onFramesCleared.bind(this);
|
||||
this._afterFramesCleared = this._afterFramesCleared.bind(this);
|
||||
this._fetchScopeVariables = this._fetchScopeVariables.bind(this);
|
||||
this._fetchVarProperties = this._fetchVarProperties.bind(this);
|
||||
this._addVarExpander = this._addVarExpander.bind(this);
|
||||
this.evaluate = this.evaluate.bind(this);
|
||||
}
|
||||
|
||||
@ -588,7 +605,12 @@ StackFrames.prototype = {
|
||||
DebuggerView.StackFrames.empty();
|
||||
|
||||
for (let frame of this.activeThread.cachedFrames) {
|
||||
this._addFrame(frame);
|
||||
let depth = frame.depth;
|
||||
let { url, line } = frame.where;
|
||||
let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
|
||||
let frameTitle = StackFrameUtils.getFrameTitle(frame);
|
||||
|
||||
DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth);
|
||||
}
|
||||
if (this.currentFrame == null) {
|
||||
DebuggerView.StackFrames.selectedDepth = 0;
|
||||
@ -661,6 +683,7 @@ StackFrames.prototype = {
|
||||
// Clear existing scopes and create each one dynamically.
|
||||
DebuggerView.Variables.empty();
|
||||
|
||||
|
||||
// If watch expressions evaluation results are available, create a scope
|
||||
// to contain all the values.
|
||||
if (this.syncedWatchExpressions && watchExpressionsEvaluation) {
|
||||
@ -684,18 +707,20 @@ StackFrames.prototype = {
|
||||
// Create a scope to contain all the inspected variables.
|
||||
let label = StackFrameUtils.getScopeLabel(environment);
|
||||
let scope = DebuggerView.Variables.addScope(label);
|
||||
let innermost = environment == frame.environment;
|
||||
|
||||
// Handle additions to the innermost scope.
|
||||
if (environment == frame.environment) {
|
||||
// Handle special additions to the innermost scope.
|
||||
if (innermost) {
|
||||
this._insertScopeFrameReferences(scope, frame);
|
||||
this._addScopeExpander(scope, environment);
|
||||
// Always expand the innermost scope by default.
|
||||
scope.expand();
|
||||
}
|
||||
// Lazily add nodes for every other environment scope.
|
||||
else {
|
||||
this._addScopeExpander(scope, environment);
|
||||
this.autoScopeExpand && scope.expand();
|
||||
|
||||
DebuggerView.Variables.controller.addExpander(scope, environment);
|
||||
|
||||
// The innermost scope is always automatically expanded, because it
|
||||
// contains the variables in the current stack frame which are likely to
|
||||
// be inspected.
|
||||
if (innermost || this.autoScopeExpand) {
|
||||
scope.expand();
|
||||
}
|
||||
} while ((environment = environment.parent));
|
||||
|
||||
@ -704,49 +729,6 @@ StackFrames.prototype = {
|
||||
DebuggerView.Variables.commitHierarchy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an 'onexpand' callback for a scope, lazily handling
|
||||
* the addition of new variables.
|
||||
*
|
||||
* @param Scope aScope
|
||||
* The scope where the variables will be placed into.
|
||||
* @param object aEnv
|
||||
* The scope's environment.
|
||||
*/
|
||||
_addScopeExpander: function(aScope, aEnv) {
|
||||
aScope._sourceEnvironment = aEnv;
|
||||
|
||||
// It's a good idea to be prepared in case of an expansion.
|
||||
aScope.addEventListener("mouseover", this._fetchScopeVariables, false);
|
||||
// Make sure that variables are always available on expansion.
|
||||
aScope.onexpand = this._fetchScopeVariables;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an 'onexpand' callback for a variable, lazily handling
|
||||
* the addition of new properties.
|
||||
*
|
||||
* @param Variable aVar
|
||||
* The variable where the properties will be placed into.
|
||||
* @param any aGrip
|
||||
* The grip of the variable.
|
||||
*/
|
||||
_addVarExpander: function(aVar, aGrip) {
|
||||
// No need for expansion for primitive values.
|
||||
if (VariablesView.isPrimitive({ value: aGrip })) {
|
||||
return;
|
||||
}
|
||||
aVar._sourceGrip = aGrip;
|
||||
|
||||
// Some variables are likely to contain a very large number of properties.
|
||||
// It's a good idea to be prepared in case of an expansion.
|
||||
if (aVar.name == "window" || aVar.name == "this") {
|
||||
aVar.addEventListener("mouseover", this._fetchVarProperties, false);
|
||||
}
|
||||
// Make sure that properties are always available on expansion.
|
||||
aVar.onexpand = this._fetchVarProperties;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the watch expressions evaluation results to a scope in the view.
|
||||
*
|
||||
@ -770,8 +752,8 @@ StackFrames.prototype = {
|
||||
for (let i = 0; i < totalExpressions; i++) {
|
||||
let name = DebuggerView.WatchExpressions.getExpression(i);
|
||||
let expVal = ownProperties[i].value;
|
||||
let expRef = aScope.addVar(name, ownProperties[i]);
|
||||
this._addVarExpander(expRef, expVal);
|
||||
let expRef = aScope.addItem(name, ownProperties[i]);
|
||||
DebuggerView.Variables.controller.addExpander(expRef, expVal);
|
||||
|
||||
// Revert some of the custom watch expressions scope presentation flags.
|
||||
expRef.switch = null;
|
||||
@ -786,51 +768,6 @@ StackFrames.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds variables to a scope in the view. Triggered when a scope is
|
||||
* expanded or is hovered. It does not expand the scope.
|
||||
*
|
||||
* @param Scope aScope
|
||||
* The scope where the variables will be placed into.
|
||||
*/
|
||||
_fetchScopeVariables: function(aScope) {
|
||||
// Fetch the variables only once.
|
||||
if (aScope._fetched) {
|
||||
return;
|
||||
}
|
||||
aScope._fetched = true;
|
||||
let env = aScope._sourceEnvironment;
|
||||
|
||||
switch (env.type) {
|
||||
case "with":
|
||||
case "object":
|
||||
// Add nodes for every variable in scope.
|
||||
this.activeThread.pauseGrip(env.object).getPrototypeAndProperties((aResponse) => {
|
||||
let { ownProperties, safeGetterValues } = aResponse;
|
||||
this._mergeSafeGetterValues(ownProperties, safeGetterValues);
|
||||
this._insertScopeVariables(ownProperties, aScope);
|
||||
|
||||
// Signal that variables have been fetched.
|
||||
window.dispatchEvent(document, "Debugger:FetchedVariables");
|
||||
DebuggerView.Variables.commitHierarchy();
|
||||
});
|
||||
break;
|
||||
case "block":
|
||||
case "function":
|
||||
// Add nodes for every argument and every other variable in scope.
|
||||
this._insertScopeArguments(env.bindings.arguments, aScope);
|
||||
this._insertScopeVariables(env.bindings.variables, aScope);
|
||||
|
||||
// No need to signal that variables have been fetched, since
|
||||
// the scope arguments and variables are already attached to the
|
||||
// environment bindings, so pausing the active thread is unnecessary.
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("Unknown Debugger.Environment type: " + env.type);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add nodes for special frame references in the innermost scope.
|
||||
*
|
||||
@ -842,154 +779,21 @@ StackFrames.prototype = {
|
||||
_insertScopeFrameReferences: function(aScope, aFrame) {
|
||||
// Add any thrown exception.
|
||||
if (this.currentException) {
|
||||
let excRef = aScope.addVar("<exception>", { value: this.currentException });
|
||||
this._addVarExpander(excRef, this.currentException);
|
||||
let excRef = aScope.addItem("<exception>", { value: this.currentException });
|
||||
DebuggerView.Variables.controller.addExpander(excRef, this.currentException);
|
||||
}
|
||||
// Add any returned value.
|
||||
if (this.currentReturnedValue) {
|
||||
let retRef = aScope.addVar("<return>", { value: this.currentReturnedValue });
|
||||
this._addVarExpander(retRef, this.currentReturnedValue);
|
||||
let retRef = aScope.addItem("<return>", { value: this.currentReturnedValue });
|
||||
DebuggerView.Variables.controller.addExpander(retRef, this.currentReturnedValue);
|
||||
}
|
||||
// Add "this".
|
||||
if (aFrame.this) {
|
||||
let thisRef = aScope.addVar("this", { value: aFrame.this });
|
||||
this._addVarExpander(thisRef, aFrame.this);
|
||||
let thisRef = aScope.addItem("this", { value: aFrame.this });
|
||||
DebuggerView.Variables.controller.addExpander(thisRef, aFrame.this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add nodes for every argument in scope.
|
||||
*
|
||||
* @param object aArguments
|
||||
* The map of names to arguments, as specified in the protocol.
|
||||
* @param Scope aScope
|
||||
* The scope where the nodes will be placed into.
|
||||
*/
|
||||
_insertScopeArguments: function(aArguments, aScope) {
|
||||
if (!aArguments) {
|
||||
return;
|
||||
}
|
||||
for (let argument of aArguments) {
|
||||
let name = Object.getOwnPropertyNames(argument)[0];
|
||||
let argRef = aScope.addVar(name, argument[name]);
|
||||
let argVal = argument[name].value;
|
||||
this._addVarExpander(argRef, argVal);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add nodes for every variable in scope.
|
||||
*
|
||||
* @param object aVariables
|
||||
* The map of names to variables, as specified in the protocol.
|
||||
* @param Scope aScope
|
||||
* The scope where the nodes will be placed into.
|
||||
*/
|
||||
_insertScopeVariables: function(aVariables, aScope) {
|
||||
if (!aVariables) {
|
||||
return;
|
||||
}
|
||||
let variableNames = Object.keys(aVariables);
|
||||
|
||||
// Sort all of the variables before adding them, if preferred.
|
||||
if (Prefs.variablesSortingEnabled) {
|
||||
variableNames.sort();
|
||||
}
|
||||
// Add the variables to the specified scope.
|
||||
for (let name of variableNames) {
|
||||
let varRef = aScope.addVar(name, aVariables[name]);
|
||||
let varVal = aVariables[name].value;
|
||||
this._addVarExpander(varRef, varVal);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties to a variable in the view. Triggered when a variable is
|
||||
* expanded or certain variables are hovered. It does not expand the variable.
|
||||
*
|
||||
* @param Variable aVar
|
||||
* The variable where the properties will be placed into.
|
||||
*/
|
||||
_fetchVarProperties: function(aVar) {
|
||||
// Fetch the properties only once.
|
||||
if (aVar._fetched) {
|
||||
return;
|
||||
}
|
||||
aVar._fetched = true;
|
||||
let grip = aVar._sourceGrip;
|
||||
|
||||
this.activeThread.pauseGrip(grip).getPrototypeAndProperties((aResponse) => {
|
||||
let { ownProperties, prototype, safeGetterValues } = aResponse;
|
||||
let sortable = VariablesView.NON_SORTABLE_CLASSES.indexOf(grip.class) == -1;
|
||||
|
||||
this._mergeSafeGetterValues(ownProperties, safeGetterValues);
|
||||
|
||||
// Add all the variable properties.
|
||||
if (ownProperties) {
|
||||
aVar.addProperties(ownProperties, {
|
||||
// Not all variables need to force sorted properties.
|
||||
sorted: sortable,
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
callback: this._addVarExpander
|
||||
});
|
||||
}
|
||||
|
||||
// Add the variable's __proto__.
|
||||
if (prototype && prototype.type != "null") {
|
||||
aVar.addProperty("__proto__", { value: prototype });
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
this._addVarExpander(aVar.get("__proto__"), prototype);
|
||||
}
|
||||
|
||||
// Mark the variable as having retrieved all its properties.
|
||||
aVar._retrieved = true;
|
||||
|
||||
// Signal that properties have been fetched.
|
||||
window.dispatchEvent(document, "Debugger:FetchedProperties");
|
||||
DebuggerView.Variables.commitHierarchy();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Merge the safe getter values descriptors into the "own properties" object
|
||||
* that comes from a "prototypeAndProperties" response packet. This is needed
|
||||
* for Variables View.
|
||||
*
|
||||
* @private
|
||||
* @param object aOwnProperties
|
||||
* The |ownProperties| object that will get the new safe getter values.
|
||||
* @param object aSafeGetterValues
|
||||
* The |safeGetterValues| object.
|
||||
*/
|
||||
_mergeSafeGetterValues: function(aOwnProperties, aSafeGetterValues) {
|
||||
// Merge the safe getter values into one object such that we can use it
|
||||
// in VariablesView.
|
||||
for (let name of Object.keys(aSafeGetterValues)) {
|
||||
if (name in aOwnProperties) {
|
||||
aOwnProperties[name].getterValue = aSafeGetterValues[name].getterValue;
|
||||
aOwnProperties[name].getterPrototypeLevel =
|
||||
aSafeGetterValues[name].getterPrototypeLevel;
|
||||
} else {
|
||||
aOwnProperties[name] = aSafeGetterValues[name];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the specified stack frame to the list.
|
||||
*
|
||||
* @param object aFrame
|
||||
* The new frame to add.
|
||||
*/
|
||||
_addFrame: function(aFrame) {
|
||||
let depth = aFrame.depth;
|
||||
let { url, line } = aFrame.where;
|
||||
let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
|
||||
let frameTitle = StackFrameUtils.getFrameTitle(aFrame);
|
||||
|
||||
DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth);
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads more stack frames from the debugger server cache.
|
||||
*/
|
||||
|
@ -24,9 +24,9 @@ function testNonEnumProperties() {
|
||||
Services.tm.currentThread.dispatch({ run: function() {
|
||||
|
||||
let testScope = gDebugger.DebuggerView.Variables.addScope("test-scope");
|
||||
let testVar = testScope.addVar("foo");
|
||||
let testVar = testScope.addItem("foo");
|
||||
|
||||
testVar.addProperties({
|
||||
testVar.addItems({
|
||||
foo: {
|
||||
value: "bar",
|
||||
enumerable: true
|
||||
|
@ -24,8 +24,8 @@ function testSimpleCall() {
|
||||
Services.tm.currentThread.dispatch({ run: function() {
|
||||
|
||||
let testScope = gDebugger.DebuggerView.Variables.addScope("test-scope");
|
||||
let testVar = testScope.addVar("something");
|
||||
let duplVar = testScope.addVar("something");
|
||||
let testVar = testScope.addItem("something");
|
||||
let duplVar = testScope.addItem("something");
|
||||
|
||||
info("Scope id: " + testScope.target.id);
|
||||
info("Scope name: " + testScope.target.name);
|
||||
@ -61,8 +61,8 @@ function testSimpleCall() {
|
||||
"Any new variable should have a details container with no child nodes.");
|
||||
|
||||
|
||||
let properties = testVar.addProperties({ "child": { "value": { "type": "object",
|
||||
"class": "Object" } } });
|
||||
let properties = testVar.addItems({ "child": { "value": { "type": "object",
|
||||
"class": "Object" } } });
|
||||
|
||||
|
||||
ok(!testVar.expanded,
|
||||
|
@ -24,9 +24,9 @@ function testSimpleCall() {
|
||||
Services.tm.currentThread.dispatch({ run: function() {
|
||||
|
||||
let testScope = gDebugger.DebuggerView.Variables.addScope("test");
|
||||
let testVar = testScope.addVar("something");
|
||||
let testVar = testScope.addItem("something");
|
||||
|
||||
let properties = testVar.addProperties({
|
||||
let properties = testVar.addItems({
|
||||
"child": {
|
||||
"value": {
|
||||
"type": "object",
|
||||
@ -43,7 +43,7 @@ function testSimpleCall() {
|
||||
"The added detail property should be accessible from the variable.");
|
||||
|
||||
|
||||
let properties2 = testVar.get("child").addProperties({
|
||||
let properties2 = testVar.get("child").addItems({
|
||||
"grandchild": {
|
||||
"value": {
|
||||
"type": "object",
|
||||
|
@ -24,7 +24,7 @@ function testSimpleCall() {
|
||||
Services.tm.currentThread.dispatch({ run: function() {
|
||||
|
||||
let testScope = gDebugger.DebuggerView.Variables.addScope("test");
|
||||
let testVar = testScope.addVar("something");
|
||||
let testVar = testScope.addItem("something");
|
||||
|
||||
testVar.setGrip(1.618);
|
||||
|
||||
@ -44,32 +44,32 @@ function testSimpleCall() {
|
||||
"The information for the variable wasn't set correctly.");
|
||||
|
||||
|
||||
testVar.addProperties({ "helloWorld": { "value": "hello world", "enumerable": true } });
|
||||
testVar.addItems({ "helloWorld": { "value": "hello world", "enumerable": true } });
|
||||
|
||||
is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1,
|
||||
"A new detail node should have been added in the variable tree.");
|
||||
|
||||
|
||||
testVar.addProperties({ "helloWorld": { "value": "hello jupiter", "enumerable": true } });
|
||||
testVar.addItems({ "helloWorld": { "value": "hello jupiter", "enumerable": true } });
|
||||
|
||||
is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1,
|
||||
"Shouldn't be able to duplicate nodes added in the variable tree.");
|
||||
|
||||
|
||||
testVar.addProperties({ "someProp0": { "value": "random string", "enumerable": true },
|
||||
"someProp1": { "value": "another string", "enumerable": true } });
|
||||
testVar.addItems({ "someProp0": { "value": "random string", "enumerable": true },
|
||||
"someProp1": { "value": "another string", "enumerable": true } });
|
||||
|
||||
is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 3,
|
||||
"Two new detail nodes should have been added in the variable tree.");
|
||||
|
||||
|
||||
testVar.addProperties({ "someProp2": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true },
|
||||
"someProp4": {
|
||||
"value": { "type": "object", "class": "Object" },
|
||||
"enumerable": true
|
||||
}
|
||||
});
|
||||
testVar.addItems({ "someProp2": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true },
|
||||
"someProp4": {
|
||||
"value": { "type": "object", "class": "Object" },
|
||||
"enumerable": true
|
||||
}
|
||||
});
|
||||
|
||||
is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 6,
|
||||
"Three new detail nodes should have been added in the variable tree.");
|
||||
|
@ -26,14 +26,14 @@ function testSimpleCall() {
|
||||
let globalScope = gDebugger.DebuggerView.Variables.addScope("Test-Global");
|
||||
let localScope = gDebugger.DebuggerView.Variables.addScope("Test-Local");
|
||||
|
||||
let windowVar = globalScope.addVar("window");
|
||||
let documentVar = globalScope.addVar("document");
|
||||
let localVar0 = localScope.addVar("localVariable");
|
||||
let localVar1 = localScope.addVar("localVar1");
|
||||
let localVar2 = localScope.addVar("localVar2");
|
||||
let localVar3 = localScope.addVar("localVar3");
|
||||
let localVar4 = localScope.addVar("localVar4");
|
||||
let localVar5 = localScope.addVar("localVar5");
|
||||
let windowVar = globalScope.addItem("window");
|
||||
let documentVar = globalScope.addItem("document");
|
||||
let localVar0 = localScope.addItem("localVariable");
|
||||
let localVar1 = localScope.addItem("localVar1");
|
||||
let localVar2 = localScope.addItem("localVar2");
|
||||
let localVar3 = localScope.addItem("localVar3");
|
||||
let localVar4 = localScope.addItem("localVar4");
|
||||
let localVar5 = localScope.addItem("localVar5");
|
||||
|
||||
localVar0.setGrip(42);
|
||||
localVar1.setGrip(true);
|
||||
@ -43,36 +43,36 @@ function testSimpleCall() {
|
||||
localVar4.setGrip({ "type": "null" });
|
||||
localVar5.setGrip({ "type": "object", "class": "Object" });
|
||||
|
||||
localVar5.addProperties({ "someProp0": { "value": 42, "enumerable": true },
|
||||
"someProp1": { "value": true , "enumerable": true},
|
||||
"someProp2": { "value": "nasu", "enumerable": true},
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true},
|
||||
"someProp4": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someProp5": {
|
||||
"value": { "type": "object", "class": "Object" },
|
||||
"enumerable": true
|
||||
}
|
||||
});
|
||||
localVar5.addItems({ "someProp0": { "value": 42, "enumerable": true },
|
||||
"someProp1": { "value": true , "enumerable": true},
|
||||
"someProp2": { "value": "nasu", "enumerable": true},
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true},
|
||||
"someProp4": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someProp5": {
|
||||
"value": { "type": "object", "class": "Object" },
|
||||
"enumerable": true
|
||||
}
|
||||
});
|
||||
|
||||
localVar5.get("someProp5").addProperties({ "someProp0": { "value": 42, "enumerable": true },
|
||||
"someProp1": { "value": true, "enumerable": true },
|
||||
"someProp2": { "value": "nasu", "enumerable": true },
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true },
|
||||
"someProp4": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someAccessor": { "get": { "type": "object", "class": "Function" },
|
||||
"set": { "type": "undefined" },
|
||||
"enumerable": true } });
|
||||
localVar5.get("someProp5").addItems({ "someProp0": { "value": 42, "enumerable": true },
|
||||
"someProp1": { "value": true, "enumerable": true },
|
||||
"someProp2": { "value": "nasu", "enumerable": true },
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true },
|
||||
"someProp4": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someAccessor": { "get": { "type": "object", "class": "Function" },
|
||||
"set": { "type": "undefined" }, "enumerable": true }
|
||||
});
|
||||
|
||||
windowVar.setGrip({ "type": "object", "class": "Window" });
|
||||
windowVar.addProperties({ "helloWorld": { "value": "hello world" } });
|
||||
windowVar.addItems({ "helloWorld": { "value": "hello world" } });
|
||||
|
||||
documentVar.setGrip({ "type": "object", "class": "HTMLDocument" });
|
||||
documentVar.addProperties({ "onload": { "value": { "type": "null" } },
|
||||
"onunload": { "value": { "type": "null" } },
|
||||
"onfocus": { "value": { "type": "null" } },
|
||||
"onblur": { "value": { "type": "null" } },
|
||||
"onclick": { "value": { "type": "null" } },
|
||||
"onkeypress": { "value": { "type": "null" } } });
|
||||
documentVar.addItems({ "onload": { "value": { "type": "null" } },
|
||||
"onunload": { "value": { "type": "null" } },
|
||||
"onfocus": { "value": { "type": "null" } },
|
||||
"onblur": { "value": { "type": "null" } },
|
||||
"onclick": { "value": { "type": "null" } },
|
||||
"onkeypress": { "value": { "type": "null" } } });
|
||||
|
||||
|
||||
ok(windowVar, "The windowVar hasn't been created correctly.");
|
||||
|
@ -75,11 +75,11 @@ function testVariablesView()
|
||||
testIntegrity(arr, obj);
|
||||
|
||||
let fooScope = gVariablesView.addScope("foo");
|
||||
let anonymousVar = fooScope.addVar();
|
||||
let anonymousVar = fooScope.addItem();
|
||||
|
||||
let anonymousScope = gVariablesView.addScope();
|
||||
let barVar = anonymousScope.addVar("bar");
|
||||
let bazProperty = barVar.addProperty("baz");
|
||||
let barVar = anonymousScope.addItem("bar");
|
||||
let bazProperty = barVar.addItem("baz");
|
||||
|
||||
testAnonymousHeaders(fooScope, anonymousVar, anonymousScope, barVar, bazProperty);
|
||||
testPropertyInheritance(fooScope, anonymousVar, anonymousScope, barVar, bazProperty);
|
||||
|
@ -110,8 +110,9 @@ function testVariablesFiltering()
|
||||
is(gSearchBox.value, "*",
|
||||
"Searchbox value is incorrect after 3 backspaces");
|
||||
|
||||
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 3,
|
||||
"There should be 3 variables displayed in the inner scope");
|
||||
// variable count includes `__proto__` for object scopes
|
||||
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 4,
|
||||
"There should be 4 variables displayed in the inner scope");
|
||||
isnot(mathScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
|
||||
"There should be some variables displayed in the math scope");
|
||||
isnot(testScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
|
||||
@ -140,8 +141,9 @@ function testVariablesFiltering()
|
||||
is(gSearchBox.value, "",
|
||||
"Searchbox value is incorrect after 1 backspace");
|
||||
|
||||
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 3,
|
||||
"There should be 3 variables displayed in the inner scope");
|
||||
// variable count includes `__proto__` for object scopes
|
||||
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 4,
|
||||
"There should be 4 variables displayed in the inner scope");
|
||||
isnot(mathScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
|
||||
"There should be some variables displayed in the math scope");
|
||||
isnot(testScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
|
||||
|
@ -158,7 +158,7 @@ Tools.jsprofiler = {
|
||||
icon: "chrome://browser/skin/devtools/tool-profiler.png",
|
||||
url: "chrome://browser/content/devtools/profiler.xul",
|
||||
label: l10n("profiler.label", profilerStrings),
|
||||
tooltip: l10n("profiler.tooltip", profilerStrings),
|
||||
tooltip: l10n("profiler.tooltip2", profilerStrings),
|
||||
|
||||
isTargetSupported: function (target) {
|
||||
return true;
|
||||
|
@ -1446,7 +1446,7 @@ NetworkDetailsView.prototype = {
|
||||
headersScope.expanded = true;
|
||||
|
||||
for (let header of aResponse.headers) {
|
||||
let headerVar = headersScope.addVar(header.name, { null: true }, true);
|
||||
let headerVar = headersScope.addItem(header.name, { null: true }, true);
|
||||
gNetwork.getString(header.value).then((aString) => headerVar.setGrip(aString));
|
||||
}
|
||||
},
|
||||
@ -1489,7 +1489,7 @@ NetworkDetailsView.prototype = {
|
||||
cookiesScope.expanded = true;
|
||||
|
||||
for (let cookie of aResponse.cookies) {
|
||||
let cookieVar = cookiesScope.addVar(cookie.name, { null: true }, true);
|
||||
let cookieVar = cookiesScope.addItem(cookie.name, { null: true }, true);
|
||||
gNetwork.getString(cookie.value).then((aString) => cookieVar.setGrip(aString));
|
||||
|
||||
// By default the cookie name and value are shown. If this is the only
|
||||
@ -1591,7 +1591,7 @@ NetworkDetailsView.prototype = {
|
||||
paramsScope.expanded = true;
|
||||
|
||||
for (let param of paramsArray) {
|
||||
let headerVar = paramsScope.addVar(param.name, { null: true }, true);
|
||||
let headerVar = paramsScope.addItem(param.name, { null: true }, true);
|
||||
headerVar.setGrip(param.value);
|
||||
}
|
||||
},
|
||||
@ -1634,7 +1634,7 @@ NetworkDetailsView.prototype = {
|
||||
: L10N.getStr("jsonScopeName");
|
||||
|
||||
let jsonScope = this._json.addScope(jsonScopeName);
|
||||
jsonScope.addVar().populate(jsonObject, { expanded: true });
|
||||
jsonScope.addItem().populate(jsonObject, { expanded: true });
|
||||
jsonScope.expanded = true;
|
||||
}
|
||||
// Malformed JSON.
|
||||
|
@ -10,6 +10,8 @@ Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
Cu.import("resource:///modules/devtools/ProfilerController.jsm");
|
||||
Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
@ -24,6 +26,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
const PROFILE_IDLE = 0;
|
||||
const PROFILE_RUNNING = 1;
|
||||
const PROFILE_COMPLETED = 2;
|
||||
|
||||
/**
|
||||
* An instance of a profile UI. Profile UI consists of
|
||||
* an iframe with Cleopatra loaded in it and some
|
||||
@ -159,18 +165,6 @@ ProfileUI.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update profile's label in the sidebar.
|
||||
*
|
||||
* @param string text
|
||||
* New text for the label.
|
||||
*/
|
||||
updateLabel: function PUI_udpateLabel(text) {
|
||||
let doc = this.panel.document;
|
||||
let label = doc.querySelector("li#profile-" + this.uid + "> h1");
|
||||
label.textContent = text;
|
||||
},
|
||||
|
||||
/**
|
||||
* Start profiling and, once started, notify the underlying page
|
||||
* so that it could update the UI. Also, once started, we add a
|
||||
@ -192,7 +186,7 @@ ProfileUI.prototype = {
|
||||
startFn = startFn || this.panel.startProfiling.bind(this.panel);
|
||||
startFn(this.name, () => {
|
||||
this.isStarted = true;
|
||||
this.updateLabel(this.name + " *");
|
||||
this.panel.sidebar.setProfileState(this, PROFILE_RUNNING);
|
||||
this.panel.broadcast(this.uid, {task: "onStarted"}); // Do we really need this?
|
||||
this.emit("started");
|
||||
});
|
||||
@ -219,7 +213,7 @@ ProfileUI.prototype = {
|
||||
stopFn(this.name, () => {
|
||||
this.isStarted = false;
|
||||
this.isFinished = true;
|
||||
this.updateLabel(this.name);
|
||||
this.panel.sidebar.setProfileState(this, PROFILE_COMPLETED);
|
||||
this.panel.broadcast(this.uid, {task: "onStopped"});
|
||||
this.emit("stopped");
|
||||
});
|
||||
@ -274,6 +268,39 @@ ProfileUI.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
function SidebarView(el) {
|
||||
EventEmitter.decorate(this);
|
||||
this.node = new SideMenuWidget(el);
|
||||
}
|
||||
|
||||
ViewHelpers.create({ constructor: SidebarView, proto: MenuContainer.prototype }, {
|
||||
getItemByProfile: function (profile) {
|
||||
return this.orderedItems.filter((item) => item.attachment.uid === profile.uid)[0];
|
||||
},
|
||||
|
||||
setProfileState: function (profile, state) {
|
||||
let item = this.getItemByProfile(profile);
|
||||
let label = item.target.querySelector(".profiler-sidebar-item > span");
|
||||
|
||||
switch (state) {
|
||||
case PROFILE_IDLE:
|
||||
label.textContent = L10N.getStr("profiler.stateIdle");
|
||||
break;
|
||||
case PROFILE_RUNNING:
|
||||
label.textContent = L10N.getStr("profiler.stateRunning");
|
||||
break;
|
||||
case PROFILE_COMPLETED:
|
||||
label.textContent = L10N.getStr("profiler.stateCompleted");
|
||||
break;
|
||||
default: // Wrong state, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
item.attachment.state = state;
|
||||
this.emit("stateChanged", item);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Profiler panel. It is responsible for creating and managing
|
||||
* different profile instances (see ProfileUI).
|
||||
@ -320,6 +347,7 @@ ProfilerPanel.prototype = {
|
||||
target: null,
|
||||
controller: null,
|
||||
profiles: null,
|
||||
sidebar: null,
|
||||
|
||||
_uid: null,
|
||||
_activeUid: null,
|
||||
@ -332,7 +360,14 @@ ProfilerPanel.prototype = {
|
||||
},
|
||||
|
||||
set activeProfile(profile) {
|
||||
if (this._activeUid === profile.uid)
|
||||
return;
|
||||
|
||||
if (this.activeProfile)
|
||||
this.activeProfile.hide();
|
||||
|
||||
this._activeUid = profile.uid;
|
||||
profile.show();
|
||||
},
|
||||
|
||||
get browserWindow() {
|
||||
@ -357,42 +392,59 @@ ProfilerPanel.prototype = {
|
||||
* @return Promise
|
||||
*/
|
||||
open: function PP_open() {
|
||||
let promise;
|
||||
|
||||
// Local profiling needs to make the target remote.
|
||||
if (!this.target.isRemote) {
|
||||
promise = this.target.makeRemote();
|
||||
} else {
|
||||
promise = Promise.resolve(this.target);
|
||||
}
|
||||
let target = this.target;
|
||||
let promise = !target.isRemote ? target.makeRemote() : Promise.resolve(target);
|
||||
|
||||
return promise
|
||||
.then(function(target) {
|
||||
.then((target) => {
|
||||
let deferred = Promise.defer();
|
||||
this.controller = new ProfilerController(this.target);
|
||||
|
||||
this.controller.connect(function onConnect() {
|
||||
this.controller = new ProfilerController(this.target);
|
||||
this.sidebar = new SidebarView(this.document.querySelector("#profiles-list"));
|
||||
this.sidebar.node.addEventListener("select", (ev) => {
|
||||
if (!ev.detail)
|
||||
return;
|
||||
|
||||
let profile = this.profiles.get(ev.detail.attachment.uid);
|
||||
this.activeProfile = profile;
|
||||
|
||||
if (profile.isReady) {
|
||||
profile.flushMessages();
|
||||
return void this.emit("profileSwitched", profile.uid);
|
||||
}
|
||||
|
||||
profile.once("ready", () => {
|
||||
profile.flushMessages();
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
});
|
||||
});
|
||||
|
||||
this.controller.connect(() => {
|
||||
let create = this.document.getElementById("profiler-create");
|
||||
create.addEventListener("click", function (ev) {
|
||||
this.createProfile()
|
||||
}.bind(this), false);
|
||||
create.addEventListener("click", () => this.createProfile(), false);
|
||||
create.removeAttribute("disabled");
|
||||
|
||||
let profile = this.createProfile();
|
||||
this.switchToProfile(profile, function () {
|
||||
let onSwitch = (_, uid) => {
|
||||
if (profile.uid !== uid)
|
||||
return;
|
||||
|
||||
this.off("profileSwitched", onSwitch);
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
|
||||
deferred.resolve(this);
|
||||
}.bind(this))
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
this.on("profileSwitched", onSwitch);
|
||||
this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}.bind(this))
|
||||
.then(null, function onError(reason) {
|
||||
Cu.reportError("ProfilerPanel open failed. " +
|
||||
reason.error + ": " + reason.message);
|
||||
});
|
||||
})
|
||||
.then(null, (reason) =>
|
||||
Cu.reportError("ProfilePanel open failed: " + reason.message));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -427,22 +479,17 @@ ProfilerPanel.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
let list = this.document.getElementById("profiles-list");
|
||||
let item = this.document.createElement("li");
|
||||
let wrap = this.document.createElement("h1");
|
||||
name = name || L10N.getFormatStr("profiler.profileName", [uid]);
|
||||
let box = this.document.createElement("vbox");
|
||||
box.className = "profiler-sidebar-item";
|
||||
box.id = "profile-" + uid;
|
||||
let h3 = this.document.createElement("h3");
|
||||
h3.textContent = name;
|
||||
let span = this.document.createElement("span");
|
||||
span.textContent = L10N.getStr("profiler.stateIdle");
|
||||
box.appendChild(h3);
|
||||
box.appendChild(span);
|
||||
|
||||
item.setAttribute("id", "profile-" + uid);
|
||||
item.setAttribute("data-uid", uid);
|
||||
item.addEventListener("click", function (ev) {
|
||||
this.switchToProfile(this.profiles.get(uid));
|
||||
}.bind(this), false);
|
||||
|
||||
wrap.className = "profile-name";
|
||||
wrap.textContent = name;
|
||||
|
||||
item.appendChild(wrap);
|
||||
list.appendChild(item);
|
||||
this.sidebar.push(box, { attachment: { uid: uid, name: name, state: PROFILE_IDLE } });
|
||||
|
||||
let profile = new ProfileUI(uid, name, this);
|
||||
this.profiles.set(uid, profile);
|
||||
@ -451,47 +498,6 @@ ProfilerPanel.prototype = {
|
||||
return profile;
|
||||
},
|
||||
|
||||
/**
|
||||
* Switches to a different profile by making its instance an
|
||||
* active one.
|
||||
*
|
||||
* @param ProfileUI profile
|
||||
* A profile instance to switch to.
|
||||
* @param function onLoad
|
||||
* A function to call when profile instance is ready.
|
||||
* If the instance is already loaded, onLoad will be
|
||||
* called synchronously.
|
||||
*/
|
||||
switchToProfile: function PP_switchToProfile(profile, onLoad=function() {}) {
|
||||
let doc = this.document;
|
||||
|
||||
if (this.activeProfile) {
|
||||
this.activeProfile.hide();
|
||||
}
|
||||
|
||||
let active = doc.querySelector("#profiles-list > li.splitview-active");
|
||||
if (active) {
|
||||
active.className = "";
|
||||
}
|
||||
|
||||
doc.getElementById("profile-" + profile.uid).className = "splitview-active";
|
||||
profile.show();
|
||||
this.activeProfile = profile;
|
||||
|
||||
if (profile.isReady) {
|
||||
profile.flushMessages();
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
onLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
profile.once("ready", () => {
|
||||
profile.flushMessages();
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
onLoad();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Start collecting profile data.
|
||||
*
|
||||
|
@ -83,7 +83,15 @@ gcli.addCommand({
|
||||
throw gcli.lookup("profilerAlreadyFinished");
|
||||
}
|
||||
|
||||
panel.switchToProfile(profile, function () profile.start());
|
||||
let item = panel.sidebar.getItemByProfile(profile);
|
||||
|
||||
if (panel.sidebar.selectedItem === item) {
|
||||
profile.start();
|
||||
} else {
|
||||
panel.on("profileSwitched", () => profile.start());
|
||||
panel.sidebar.selectedItem = item;
|
||||
}
|
||||
|
||||
return gcli.lookup("profilerStarting2");
|
||||
}
|
||||
|
||||
@ -124,7 +132,15 @@ gcli.addCommand({
|
||||
throw gcli.lookup("profilerNotStarted2");
|
||||
}
|
||||
|
||||
panel.switchToProfile(profile, function () profile.stop());
|
||||
let item = panel.sidebar.getItemByProfile(profile);
|
||||
|
||||
if (panel.sidebar.selectedItem === item) {
|
||||
profile.stop();
|
||||
} else {
|
||||
panel.on("profileSwitched", () => profile.stop());
|
||||
panel.sidebar.selectedItem = item;
|
||||
}
|
||||
|
||||
return gcli.lookup("profilerStopping2");
|
||||
}
|
||||
|
||||
@ -193,7 +209,7 @@ gcli.addCommand({
|
||||
throw gcli.lookup("profilerNotFound");
|
||||
}
|
||||
|
||||
panel.switchToProfile(profile);
|
||||
panel.sidebar.selectedItem = panel.sidebar.getItemByProfile(profile);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -6,9 +6,9 @@
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/splitview.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/profiler.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/devtools/splitview.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
|
||||
@ -16,37 +16,31 @@
|
||||
]>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<box flex="1" id="profiler-chrome" class="splitview-root">
|
||||
<box class="splitview-controller" width="180px">
|
||||
<box class="splitview-main"></box>
|
||||
<box flex="1" id="profiler-chrome" class="devtools-responsive-container">
|
||||
<vbox class="profiler-sidebar">
|
||||
<toolbar class="devtools-toolbar">
|
||||
<toolbarbutton id="profiler-create"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerNew.label;"
|
||||
disabled="true"/>
|
||||
</toolbar>
|
||||
|
||||
<box class="splitview-nav-container">
|
||||
<ol class="splitview-nav" id="profiles-list">
|
||||
<!-- Example:
|
||||
<li class="splitview-active" id="profile-1" data-uid="1">
|
||||
<h1 class="profile-name">Profile 1</h1>
|
||||
</li>
|
||||
-->
|
||||
</ol>
|
||||
<vbox id="profiles-list" flex="1">
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
||||
<spacer flex="1"/>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
|
||||
<toolbar class="devtools-toolbar" mode="full">
|
||||
<toolbarbutton id="profiler-create"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerNew.label;"
|
||||
disabled="true"/>
|
||||
</toolbar>
|
||||
</box> <!-- splitview-nav-container -->
|
||||
</box> <!-- splitview-controller -->
|
||||
<vbox flex="1">
|
||||
<toolbar class="devtools-toolbar">
|
||||
</toolbar>
|
||||
|
||||
<box flex="1">
|
||||
<vbox flex="1" id="profiler-report">
|
||||
<!-- Example:
|
||||
<iframe id="profiler-cleo-1"
|
||||
src="devtools/cleopatra.html" flex="1"></iframe>
|
||||
-->
|
||||
</vbox>
|
||||
</box>
|
||||
</vbox>
|
||||
</box>
|
||||
</window>
|
||||
|
@ -26,13 +26,6 @@ function test() {
|
||||
});
|
||||
}
|
||||
|
||||
function getCleoControls(doc) {
|
||||
return [
|
||||
doc.querySelector("#startWrapper button"),
|
||||
doc.querySelector("#profilerMessage")
|
||||
];
|
||||
}
|
||||
|
||||
function startProfiling() {
|
||||
gPanel.profiles.get(gPanel.activeProfile.uid).once("started", function () {
|
||||
setTimeout(function () {
|
||||
@ -40,36 +33,27 @@ function startProfiling() {
|
||||
gPanel.profiles.get(2).once("started", function () setTimeout(stopProfiling, 50));
|
||||
}, 50);
|
||||
});
|
||||
|
||||
sendFromProfile(gPanel.activeProfile.uid, "start");
|
||||
}
|
||||
|
||||
function stopProfiling() {
|
||||
let [win, doc] = getProfileInternals(gUid);
|
||||
let [btn, msg] = getCleoControls(doc);
|
||||
|
||||
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
|
||||
"Profile 1 *", "Profile 1 has a star next to it.");
|
||||
is(gPanel.document.querySelector("li#profile-2 > h1").textContent,
|
||||
"Profile 2 *", "Profile 2 has a star next to it.");
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_RUNNING);
|
||||
is(getSidebarItem(2).attachment.state, PROFILE_RUNNING);
|
||||
|
||||
gPanel.profiles.get(gPanel.activeProfile.uid).once("stopped", function () {
|
||||
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
|
||||
"Profile 1", "Profile 1 doesn't have a star next to it anymore.");
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
sendFromProfile(2, "stop");
|
||||
gPanel.profiles.get(2).once("stopped", confirmAndFinish);
|
||||
});
|
||||
|
||||
sendFromProfile(gPanel.activeProfile.uid, "stop");
|
||||
}
|
||||
|
||||
function confirmAndFinish(ev, data) {
|
||||
let [win, doc] = getProfileInternals(gUid);
|
||||
let [btn, msg] = getCleoControls(doc);
|
||||
|
||||
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
|
||||
"Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(gPanel.document.querySelector("li#profile-2 > h1").textContent,
|
||||
"Profile 2", "Profile 2 doesn't have a star next to it.");
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
|
||||
is(getSidebarItem(2).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
tearDown(gTab, function onTearDown() {
|
||||
gPanel = null;
|
||||
|
@ -48,17 +48,15 @@ function testConsoleProfile(hud) {
|
||||
|
||||
function checkProfiles(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let getTitle = (uid) =>
|
||||
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
|
||||
|
||||
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(getTitle(2), "Profile 2 *", "Profile 2 doesn't have a star next to it.");
|
||||
is(getTitle(3), "Profile 3", "Profile 3 doesn't have a star next to it.");
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_IDLE);
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_RUNNING);
|
||||
is(getSidebarItem(3, panel).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
// Make sure we can still stop profiles via the UI.
|
||||
|
||||
gPanel.profiles.get(2).once("stopped", () => {
|
||||
is(getTitle(2), "Profile 2", "Profile 2 doesn't have a star next to it.");
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
|
@ -17,18 +17,20 @@ function test() {
|
||||
openProfiler(tab, (toolbox) => {
|
||||
gToolbox = toolbox;
|
||||
loadUrl(PAGE, tab, () => {
|
||||
gPanel.on("profileCreated", runTests);
|
||||
gPanel.sidebar.on("stateChanged", (_, item) => {
|
||||
if (item.attachment.state !== PROFILE_COMPLETED)
|
||||
return;
|
||||
|
||||
runTests();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
let getTitle = (uid) =>
|
||||
gPanel.document.querySelector("li#profile-" + uid + " > h1").textContent;
|
||||
|
||||
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(getTitle(2), "Profile 2", "Profile 2 doesn't have a star next to it.");
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_IDLE);
|
||||
is(getSidebarItem(2).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
function assertSampleAndFinish() {
|
||||
@ -49,5 +51,6 @@ function runTests() {
|
||||
assertSampleAndFinish();
|
||||
});
|
||||
|
||||
gPanel.switchToProfile(gPanel.profiles.get(2));
|
||||
let profile = gPanel.profiles.get(2);
|
||||
gPanel.sidebar.selectedItem = gPanel.sidebar.getItemByProfile(profile);
|
||||
}
|
@ -18,15 +18,13 @@ function test() {
|
||||
|
||||
function runTests(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let getTitle = (uid) =>
|
||||
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
|
||||
|
||||
panel.profiles.get(1).once("started", () => {
|
||||
is(getTitle(1), "Profile 1 *", "Profile 1 has a start next to it.");
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_RUNNING);
|
||||
|
||||
openConsole(gTab, (hud) => {
|
||||
panel.profiles.get(1).once("stopped", () => {
|
||||
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
|
@ -46,18 +46,18 @@ function testConsoleProfile(hud) {
|
||||
|
||||
function checkProfiles(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let getTitle = (uid) =>
|
||||
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
|
||||
|
||||
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(getTitle(2), "Second", "Second doesn't have a star next to it.");
|
||||
is(getTitle(3), "Third *", "Third does have a star next to it.");
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_IDLE);
|
||||
is(getSidebarItem(2, panel).attachment.name, "Second");
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_COMPLETED);
|
||||
is(getSidebarItem(3, panel).attachment.name, "Third");
|
||||
is(getSidebarItem(3, panel).attachment.state, PROFILE_RUNNING);
|
||||
|
||||
// Make sure we can still stop profiles via the queue pop.
|
||||
|
||||
gPanel.profiles.get(3).once("stopped", () => {
|
||||
openProfiler(gTab, () => {
|
||||
is(getTitle(3), "Third", "Third doesn't have a star next to it.");
|
||||
is(getSidebarItem(3, panel).attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
});
|
||||
|
@ -48,8 +48,11 @@ function onNamedProfileCreated(name, uid) {
|
||||
is(gPanel.profiles.size, 3, "There are three profiles now");
|
||||
is(gPanel.getProfileByUID(uid).name, "Custom Profile", "Name is correct");
|
||||
|
||||
let label = gPanel.document.querySelector("li#profile-" + uid + "> h1");
|
||||
is(label.textContent, "Custom Profile", "Name is correct on the label");
|
||||
let profile = gPanel.profiles.get(uid);
|
||||
let data = gPanel.sidebar.getItemByProfile(profile).attachment;
|
||||
|
||||
is(data.uid, uid, "UID is correct");
|
||||
is(data.name, "Custom Profile", "Name is correct on the label");
|
||||
|
||||
let btn = gPanel.document.getElementById("profile-" + uid);
|
||||
ok(btn, "Profile item has been added to the sidebar");
|
||||
|
@ -2,8 +2,12 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let temp = {};
|
||||
|
||||
const PROFILER_ENABLED = "devtools.profiler.enabled";
|
||||
const REMOTE_ENABLED = "devtools.debugger.remote-enabled";
|
||||
const PROFILE_IDLE = 0;
|
||||
const PROFILE_RUNNING = 1;
|
||||
const PROFILE_COMPLETED = 2;
|
||||
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
|
||||
let gDevTools = temp.gDevTools;
|
||||
@ -36,6 +40,11 @@ function getProfileInternals(uid) {
|
||||
return [win, doc];
|
||||
}
|
||||
|
||||
function getSidebarItem(uid, panel=gPanel) {
|
||||
let profile = panel.profiles.get(uid);
|
||||
return panel.sidebar.getItemByProfile(profile);
|
||||
}
|
||||
|
||||
function sendFromProfile(uid, msg) {
|
||||
let [win, doc] = getProfileInternals(uid);
|
||||
win.parent.postMessage({ uid: uid, status: msg }, "*");
|
||||
|
@ -20,6 +20,8 @@ const SEARCH_ACTION_MAX_DELAY = 300; // ms
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper",
|
||||
"resource://gre/modules/devtools/NetworkHelper.jsm");
|
||||
@ -74,6 +76,8 @@ this.VariablesView = function VariablesView(aParentNode, aFlags = {}) {
|
||||
for (let name in aFlags) {
|
||||
this[name] = aFlags[name];
|
||||
}
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
};
|
||||
|
||||
VariablesView.prototype = {
|
||||
@ -86,7 +90,7 @@ VariablesView.prototype = {
|
||||
*/
|
||||
set rawObject(aObject) {
|
||||
this.empty();
|
||||
this.addScope().addVar().populate(aObject);
|
||||
this.addScope().addItem().populate(aObject);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -180,6 +184,11 @@ VariablesView.prototype = {
|
||||
}, aTimeout);
|
||||
},
|
||||
|
||||
/**
|
||||
* The controller for this VariablesView, if it has one.
|
||||
*/
|
||||
controller: null,
|
||||
|
||||
/**
|
||||
* The amount of time (in milliseconds) it takes to empty this view lazily.
|
||||
*/
|
||||
@ -587,7 +596,8 @@ VariablesView.prototype = {
|
||||
*/
|
||||
getScopeForNode: function(aNode) {
|
||||
let item = this._itemsByElement.get(aNode);
|
||||
if (item && !(item instanceof Variable) && !(item instanceof Property)) {
|
||||
// Match only Scopes, not Variables or Properties.
|
||||
if (item && !(item instanceof Variable)) {
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
@ -790,9 +800,8 @@ VariablesView.prototype = {
|
||||
|
||||
case e.DOM_VK_RETURN:
|
||||
case e.DOM_VK_ENTER:
|
||||
// Start editing the value or name of the variable or property.
|
||||
if (item instanceof Variable ||
|
||||
item instanceof Property) {
|
||||
// Start editing the value or name of the Variable or Property.
|
||||
if (item instanceof Variable) {
|
||||
if (e.metaKey || e.altKey || e.shiftKey) {
|
||||
item._activateNameInput();
|
||||
} else {
|
||||
@ -803,9 +812,8 @@ VariablesView.prototype = {
|
||||
|
||||
case e.DOM_VK_DELETE:
|
||||
case e.DOM_VK_BACK_SPACE:
|
||||
// Delete the variable or property if allowed.
|
||||
if (item instanceof Variable ||
|
||||
item instanceof Property) {
|
||||
// Delete the Variable or Property if allowed.
|
||||
if (item instanceof Variable) {
|
||||
item._onDelete(e);
|
||||
}
|
||||
return;
|
||||
@ -902,6 +910,7 @@ VariablesView.NON_SORTABLE_CLASSES = [
|
||||
"Array",
|
||||
"Int8Array",
|
||||
"Uint8Array",
|
||||
"Uint8ClampedArray",
|
||||
"Int16Array",
|
||||
"Uint16Array",
|
||||
"Int32Array",
|
||||
@ -910,6 +919,16 @@ VariablesView.NON_SORTABLE_CLASSES = [
|
||||
"Float64Array"
|
||||
];
|
||||
|
||||
/**
|
||||
* Determine whether an object's properties should be sorted based on its class.
|
||||
*
|
||||
* @param string aClassName
|
||||
* The class of the object.
|
||||
*/
|
||||
VariablesView.isSortable = function(aClassName) {
|
||||
return VariablesView.NON_SORTABLE_CLASSES.indexOf(aClassName) == -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the string evaluated when performing simple value changes.
|
||||
*
|
||||
@ -917,11 +936,13 @@ VariablesView.NON_SORTABLE_CLASSES = [
|
||||
* The current variable or property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @param string aPrefix [optional]
|
||||
* Prefix for the symbolic name.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
VariablesView.simpleValueEvalMacro = function(aItem, aCurrentString) {
|
||||
return aItem._symbolicName + "=" + aCurrentString;
|
||||
VariablesView.simpleValueEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
|
||||
return aPrefix + aItem._symbolicName + "=" + aCurrentString;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -932,12 +953,14 @@ VariablesView.simpleValueEvalMacro = function(aItem, aCurrentString) {
|
||||
* The current getter or setter property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @param string aPrefix [optional]
|
||||
* Prefix for the symbolic name.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
VariablesView.overrideValueEvalMacro = function(aItem, aCurrentString) {
|
||||
VariablesView.overrideValueEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
|
||||
let property = "\"" + aItem._nameString + "\"";
|
||||
let parent = aItem.ownerView._symbolicName || "this";
|
||||
let parent = aPrefix + aItem.ownerView._symbolicName || "this";
|
||||
|
||||
return "Object.defineProperty(" + parent + "," + property + "," +
|
||||
"{ value: " + aCurrentString +
|
||||
@ -954,15 +977,17 @@ VariablesView.overrideValueEvalMacro = function(aItem, aCurrentString) {
|
||||
* The current getter or setter property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @param string aPrefix [optional]
|
||||
* Prefix for the symbolic name.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString) {
|
||||
VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
|
||||
let type = aItem._nameString;
|
||||
let propertyObject = aItem.ownerView;
|
||||
let parentObject = propertyObject.ownerView;
|
||||
let property = "\"" + propertyObject._nameString + "\"";
|
||||
let parent = parentObject._symbolicName || "this";
|
||||
let parent = aPrefix + parentObject._symbolicName || "this";
|
||||
|
||||
switch (aCurrentString) {
|
||||
case "":
|
||||
@ -976,7 +1001,7 @@ VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString) {
|
||||
if ((type == "set" && propertyObject.getter.type == "undefined") ||
|
||||
(type == "get" && propertyObject.setter.type == "undefined")) {
|
||||
// Make sure the right getter/setter to value override macro is applied to the target object.
|
||||
return propertyObject.evaluationMacro(propertyObject, "undefined");
|
||||
return propertyObject.evaluationMacro(propertyObject, "undefined", aPrefix);
|
||||
}
|
||||
|
||||
// Construct and return the getter/setter removal evaluation string.
|
||||
@ -995,16 +1020,16 @@ VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString) {
|
||||
|
||||
default:
|
||||
// Wrap statements inside a function declaration if not already wrapped.
|
||||
if (aCurrentString.indexOf("function") != 0) {
|
||||
if (!aCurrentString.startsWith("function")) {
|
||||
let header = "function(" + (type == "set" ? "value" : "") + ")";
|
||||
let body = "";
|
||||
// If there's a return statement explicitly written, always use the
|
||||
// standard function definition syntax
|
||||
if (aCurrentString.indexOf("return ") != -1) {
|
||||
if (aCurrentString.contains("return ")) {
|
||||
body = "{" + aCurrentString + "}";
|
||||
}
|
||||
// If block syntax is used, use the whole string as the function body.
|
||||
else if (aCurrentString.indexOf("{") == 0) {
|
||||
else if (aCurrentString.startsWith("{")) {
|
||||
body = aCurrentString;
|
||||
}
|
||||
// Prefer an expression closure.
|
||||
@ -1042,6 +1067,7 @@ VariablesView.getterOrSetterDeleteCallback = function(aItem) {
|
||||
return true; // Don't hide the element.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A Scope is an object holding Variable instances.
|
||||
* Iterable via "for (let [name, variable] in instance) { }".
|
||||
@ -1083,12 +1109,31 @@ function Scope(aView, aName, aFlags = {}) {
|
||||
|
||||
Scope.prototype = {
|
||||
/**
|
||||
* Adds a variable to contain any inspected properties.
|
||||
* Whether this Scope should be prefetched when it is remoted.
|
||||
*/
|
||||
shouldPrefetch: true,
|
||||
|
||||
/**
|
||||
* Create a new Variable that is a child of this Scope.
|
||||
*
|
||||
* @param string aName
|
||||
* The variable's name.
|
||||
* The name of the new Property.
|
||||
* @param object aDescriptor
|
||||
* Specifies the value and/or type & class of the variable,
|
||||
* The variable's descriptor.
|
||||
* @return Variable
|
||||
* The newly created child Variable.
|
||||
*/
|
||||
_createChild: function(aName, aDescriptor) {
|
||||
return new Variable(this, aName, aDescriptor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a child to contain any inspected properties.
|
||||
*
|
||||
* @param string aName
|
||||
* The child's name.
|
||||
* @param object aDescriptor
|
||||
* Specifies the value and/or type & class of the child,
|
||||
* or 'get' & 'set' accessor properties. If the type is implicit,
|
||||
* it will be inferred from the value.
|
||||
* e.g. - { value: 42 }
|
||||
@ -1104,17 +1149,56 @@ Scope.prototype = {
|
||||
* @return Variable
|
||||
* The newly created Variable instance, null if it already exists.
|
||||
*/
|
||||
addVar: function(aName = "", aDescriptor = {}, aRelaxed = false) {
|
||||
addItem: function(aName = "", aDescriptor = {}, aRelaxed = false) {
|
||||
if (this._store.has(aName) && !aRelaxed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let variable = new Variable(this, aName, aDescriptor);
|
||||
this._store.set(aName, variable);
|
||||
this._variablesView._itemsByElement.set(variable._target, variable);
|
||||
this._variablesView._currHierarchy.set(variable._absoluteName, variable);
|
||||
variable.header = !!aName;
|
||||
return variable;
|
||||
let child = this._createChild(aName, aDescriptor);
|
||||
this._store.set(aName, child);
|
||||
this._variablesView._itemsByElement.set(child._target, child);
|
||||
this._variablesView._currHierarchy.set(child._absoluteName, child);
|
||||
child.header = !!aName;
|
||||
return child;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds items for this variable.
|
||||
*
|
||||
* @param object aItems
|
||||
* An object containing some { name: descriptor } data properties,
|
||||
* specifying the value and/or type & class of the variable,
|
||||
* or 'get' & 'set' accessor properties. If the type is implicit,
|
||||
* it will be inferred from the value.
|
||||
* e.g. - { someProp0: { value: 42 },
|
||||
* someProp1: { value: true },
|
||||
* someProp2: { value: "nasu" },
|
||||
* someProp3: { value: { type: "undefined" } },
|
||||
* someProp4: { value: { type: "null" } },
|
||||
* someProp5: { value: { type: "object", class: "Object" } },
|
||||
* someProp6: { get: { type: "object", class: "Function" },
|
||||
* set: { type: "undefined" } } }
|
||||
* @param object aOptions [optional]
|
||||
* Additional options for adding the properties. Supported options:
|
||||
* - sorted: true to sort all the properties before adding them
|
||||
* - callback: function invoked after each item is added
|
||||
*/
|
||||
addItems: function(aItems, aOptions = {}) {
|
||||
let names = Object.keys(aItems);
|
||||
|
||||
// Sort all of the properties before adding them, if preferred.
|
||||
if (aOptions.sorted) {
|
||||
names.sort();
|
||||
}
|
||||
// Add the properties to the current scope.
|
||||
for (let name of names) {
|
||||
let descriptor = aItems[name];
|
||||
let item = this.addItem(name, descriptor);
|
||||
|
||||
if (aOptions.callback) {
|
||||
aOptions.callback(item, descriptor.value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1179,11 +1263,13 @@ Scope.prototype = {
|
||||
if (this.isChildOf(aParent)) {
|
||||
return true;
|
||||
}
|
||||
if (this.ownerView instanceof Scope ||
|
||||
this.ownerView instanceof Variable ||
|
||||
this.ownerView instanceof Property) {
|
||||
|
||||
// Recurse to parent if it is a Scope, Variable, or Property.
|
||||
if (this.ownerView instanceof Scope) {
|
||||
return this.ownerView.isDescendantOf(aParent);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1405,10 +1491,9 @@ Scope.prototype = {
|
||||
}
|
||||
// Check if all parent objects are expanded.
|
||||
let item = this;
|
||||
while ((item = item.ownerView) && /* Parent object exists. */
|
||||
(item instanceof Scope ||
|
||||
item instanceof Variable ||
|
||||
item instanceof Property)) {
|
||||
|
||||
// Recurse while parent is a Scope, Variable, or Property
|
||||
while ((item = item.ownerView) && item instanceof Scope) {
|
||||
if (!item._isExpanded) {
|
||||
return false;
|
||||
}
|
||||
@ -1722,14 +1807,11 @@ Scope.prototype = {
|
||||
variable._wasToggled = true;
|
||||
}
|
||||
|
||||
// If the variable is contained in another scope (variable or property),
|
||||
// If the variable is contained in another Scope, Variable, or Property,
|
||||
// the parent may not be a match, thus hidden. It should be visible
|
||||
// ("expand upwards").
|
||||
|
||||
while ((variable = variable.ownerView) && /* Parent object exists. */
|
||||
(variable instanceof Scope ||
|
||||
variable instanceof Variable ||
|
||||
variable instanceof Property)) {
|
||||
variable instanceof Scope) {
|
||||
|
||||
// Show and expand the parent, as it is certainly accessible.
|
||||
variable._matched = true;
|
||||
@ -1971,79 +2053,24 @@ function Variable(aScope, aName, aDescriptor) {
|
||||
|
||||
ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
||||
/**
|
||||
* Adds a property for this variable.
|
||||
*
|
||||
* @param string aName
|
||||
* The property's name.
|
||||
* @param object aDescriptor
|
||||
* Specifies the value and/or type & class of the property,
|
||||
* or 'get' & 'set' accessor properties. If the type is implicit,
|
||||
* it will be inferred from the value.
|
||||
* e.g. - { value: 42 }
|
||||
* - { value: true }
|
||||
* - { value: "nasu" }
|
||||
* - { value: { type: "undefined" } }
|
||||
* - { value: { type: "null" } }
|
||||
* - { value: { type: "object", class: "Object" } }
|
||||
* - { get: { type: "object", class: "Function" },
|
||||
* set: { type: "undefined" } }
|
||||
* - { get: { type "object", class: "Function" },
|
||||
* getterValue: "foo", getterPrototypeLevel: 2 }
|
||||
* @param boolean aRelaxed
|
||||
* True if name duplicates should be allowed.
|
||||
* @return Property
|
||||
* The newly created Property instance, null if it already exists.
|
||||
* Whether this Scope should be prefetched when it is remoted.
|
||||
*/
|
||||
addProperty: function(aName = "", aDescriptor = {}, aRelaxed = false) {
|
||||
if (this._store.has(aName) && !aRelaxed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let property = new Property(this, aName, aDescriptor);
|
||||
this._store.set(aName, property);
|
||||
this._variablesView._itemsByElement.set(property._target, property);
|
||||
this._variablesView._currHierarchy.set(property._absoluteName, property);
|
||||
property.header = !!aName;
|
||||
return property;
|
||||
get shouldPrefetch(){
|
||||
return this.name == "window" || this.name == "this";
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties for this variable.
|
||||
* Create a new Property that is a child of Variable.
|
||||
*
|
||||
* @param object aProperties
|
||||
* An object containing some { name: descriptor } data properties,
|
||||
* specifying the value and/or type & class of the variable,
|
||||
* or 'get' & 'set' accessor properties. If the type is implicit,
|
||||
* it will be inferred from the value.
|
||||
* e.g. - { someProp0: { value: 42 },
|
||||
* someProp1: { value: true },
|
||||
* someProp2: { value: "nasu" },
|
||||
* someProp3: { value: { type: "undefined" } },
|
||||
* someProp4: { value: { type: "null" } },
|
||||
* someProp5: { value: { type: "object", class: "Object" } },
|
||||
* someProp6: { get: { type: "object", class: "Function" },
|
||||
* set: { type: "undefined" } } }
|
||||
* @param object aOptions [optional]
|
||||
* Additional options for adding the properties. Supported options:
|
||||
* - sorted: true to sort all the properties before adding them
|
||||
* - callback: function invoked after each property is added
|
||||
* @param string aName
|
||||
* The name of the new Property.
|
||||
* @param object aDescriptor
|
||||
* The property's descriptor.
|
||||
* @return Property
|
||||
* The newly created child Property.
|
||||
*/
|
||||
addProperties: function(aProperties, aOptions = {}) {
|
||||
let propertyNames = Object.keys(aProperties);
|
||||
|
||||
// Sort all of the properties before adding them, if preferred.
|
||||
if (aOptions.sorted) {
|
||||
propertyNames.sort();
|
||||
}
|
||||
// Add the properties to the current scope.
|
||||
for (let name of propertyNames) {
|
||||
let descriptor = aProperties[name];
|
||||
let property = this.addProperty(name, descriptor);
|
||||
|
||||
if (aOptions.callback) {
|
||||
aOptions.callback(property, descriptor.value);
|
||||
}
|
||||
}
|
||||
_createChild: function(aName, aDescriptor) {
|
||||
return new Property(this, aName, aDescriptor);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2122,7 +2149,7 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
||||
let descriptor = Object.create(aDescriptor);
|
||||
descriptor.value = VariablesView.getGrip(aValue);
|
||||
|
||||
let propertyItem = this.addProperty(aName, descriptor);
|
||||
let propertyItem = this.addItem(aName, descriptor);
|
||||
propertyItem._sourceValue = aValue;
|
||||
|
||||
// Add an 'onexpand' callback for the property, lazily handling
|
||||
@ -2149,7 +2176,7 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
||||
descriptor.get = VariablesView.getGrip(aDescriptor.get);
|
||||
descriptor.set = VariablesView.getGrip(aDescriptor.set);
|
||||
|
||||
return this.addProperty(aName, descriptor);
|
||||
return this.addItem(aName, descriptor);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2311,8 +2338,8 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
||||
this.evaluationMacro = null;
|
||||
}
|
||||
|
||||
let getter = this.addProperty("get", { value: descriptor.get });
|
||||
let setter = this.addProperty("set", { value: descriptor.set });
|
||||
let getter = this.addItem("get", { value: descriptor.get });
|
||||
let setter = this.addItem("set", { value: descriptor.set });
|
||||
getter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
|
||||
setter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
|
||||
|
||||
@ -2852,9 +2879,8 @@ VariablesView.prototype.commitHierarchy = function() {
|
||||
if (prevVariable) {
|
||||
expanded = prevVariable._isExpanded;
|
||||
|
||||
// Only analyze variables and properties for displayed value changes.
|
||||
if (currVariable instanceof Variable ||
|
||||
currVariable instanceof Property) {
|
||||
// Only analyze Variables and Properties for displayed value changes.
|
||||
if (currVariable instanceof Variable) {
|
||||
changed = prevVariable._valueString != currVariable._valueString;
|
||||
}
|
||||
}
|
||||
@ -2974,6 +3000,16 @@ VariablesView.isFalsy = function(aDescriptor) {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the value is an instance of Variable or Property.
|
||||
*
|
||||
* @param any aValue
|
||||
* The value to test.
|
||||
*/
|
||||
VariablesView.isVariable = function(aValue) {
|
||||
return aValue instanceof Variable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a standard grip for a value.
|
||||
*
|
||||
|
350
browser/devtools/shared/widgets/VariablesViewController.jsm
Normal file
350
browser/devtools/shared/widgets/VariablesViewController.jsm
Normal file
@ -0,0 +1,350 @@
|
||||
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "VARIABLES_SORTING_ENABLED", () =>
|
||||
Services.prefs.getBoolPref("devtools.debugger.ui.variables-sorting-enabled")
|
||||
);
|
||||
|
||||
const MAX_LONG_STRING_LENGTH = 200000;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["VariablesViewController"];
|
||||
|
||||
|
||||
/**
|
||||
* Controller for a VariablesView that handles interfacing with the debugger
|
||||
* protocol. Is able to populate scopes and variables via the protocol as well
|
||||
* as manage actor lifespans.
|
||||
*
|
||||
* @param VariablesView aView
|
||||
* The view to attach to.
|
||||
* @param object aOptions
|
||||
* Options for configuring the controller. Supported options:
|
||||
* - getGripClient: callback for creating an object grip client
|
||||
* - getLongStringClient: callback for creating a long string grip client
|
||||
* - releaseActor: callback for releasing an actor when it's no longer needed
|
||||
* - overrideValueEvalMacro: callback for creating an overriding eval macro
|
||||
* - getterOrSetterEvalMacro: callback for creating a getter/setter eval macro
|
||||
* - simpleValueEvalMacro: callback for creating a simple value eval macro
|
||||
*/
|
||||
function VariablesViewController(aView, aOptions) {
|
||||
this.addExpander = this.addExpander.bind(this);
|
||||
|
||||
this._getGripClient = aOptions.getGripClient;
|
||||
this._getLongStringClient = aOptions.getLongStringClient;
|
||||
this._releaseActor = aOptions.releaseActor;
|
||||
|
||||
if (aOptions.overrideValueEvalMacro) {
|
||||
this._overrideValueEvalMacro = aOptions.overrideValueEvalMacro;
|
||||
}
|
||||
if (aOptions.getterOrSetterEvalMacro) {
|
||||
this._getterOrSetterEvalMacro = aOptions.getterOrSetterEvalMacro;
|
||||
}
|
||||
if (aOptions.simpleValueEvalMacro) {
|
||||
this._simpleValueEvalMacro = aOptions.simpleValueEvalMacro;
|
||||
}
|
||||
|
||||
this._actors = new Set();
|
||||
this.view = aView;
|
||||
this.view.controller = this;
|
||||
}
|
||||
|
||||
VariablesViewController.prototype = {
|
||||
/**
|
||||
* The default getter/setter evaluation macro.
|
||||
*/
|
||||
_getterOrSetterEvalMacro: VariablesView.getterOrSetterEvalMacro,
|
||||
|
||||
/**
|
||||
* The default override value evaluation macro.
|
||||
*/
|
||||
_overrideValueEvalMacro: VariablesView.overrideValueEvalMacro,
|
||||
|
||||
/**
|
||||
* The default simple value evaluation macro.
|
||||
*/
|
||||
_simpleValueEvalMacro: VariablesView.simpleValueEvalMacro,
|
||||
|
||||
/**
|
||||
* Populate a long string into a target using a grip.
|
||||
*
|
||||
* @param Variable aTarget
|
||||
* The target Variable/Property to put the retrieved string into.
|
||||
* @param LongStringActor aGrip
|
||||
* The long string grip that use to retrieve the full string.
|
||||
* @return Promise
|
||||
* The promise that will be resolved when the string is retrieved.
|
||||
*/
|
||||
_populateFromLongString: function(aTarget, aGrip){
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let from = aGrip.initial.length;
|
||||
let to = Math.min(aGrip.length, MAX_LONG_STRING_LENGTH);
|
||||
|
||||
this._getLongStringClient(aGrip).substring(from, to, aResponse => {
|
||||
// Stop tracking the actor because it's no longer needed.
|
||||
this.releaseActor(aGrip);
|
||||
|
||||
// Replace the preview with the full string and make it non-expandable.
|
||||
aTarget.onexpand = null;
|
||||
aTarget.setGrip(aGrip.initial + aResponse.substring);
|
||||
aTarget.hideArrow();
|
||||
|
||||
// Mark the string as having retrieved.
|
||||
aTarget._retrieved = true;
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties to a Scope, Variable, or Property in the view. Triggered
|
||||
* when a scope is expanded or certain variables are hovered.
|
||||
*
|
||||
* @param Scope aTarget
|
||||
* The Scope where the properties will be placed into.
|
||||
* @param object aGrip
|
||||
* The grip to use to populate the target.
|
||||
*/
|
||||
_populateFromObject: function(aTarget, aGrip) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
this._getGripClient(aGrip).getPrototypeAndProperties(aResponse => {
|
||||
let { ownProperties, prototype, safeGetterValues } = aResponse;
|
||||
let sortable = VariablesView.isSortable(aGrip.class);
|
||||
|
||||
// Merge the safe getter values into one object such that we can use it
|
||||
// in VariablesView.
|
||||
for (let name of Object.keys(safeGetterValues)) {
|
||||
if (name in ownProperties) {
|
||||
ownProperties[name].getterValue = safeGetterValues[name].getterValue;
|
||||
ownProperties[name].getterPrototypeLevel = safeGetterValues[name]
|
||||
.getterPrototypeLevel;
|
||||
} else {
|
||||
ownProperties[name] = safeGetterValues[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the variable properties.
|
||||
if (ownProperties) {
|
||||
aTarget.addItems(ownProperties, {
|
||||
// Not all variables need to force sorted properties.
|
||||
sorted: sortable,
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
callback: this.addExpander
|
||||
});
|
||||
}
|
||||
|
||||
// Add the variable's __proto__.
|
||||
if (prototype && prototype.type != "null") {
|
||||
let proto = aTarget.addItem("__proto__", { value: prototype });
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
this.addExpander(proto, prototype);
|
||||
}
|
||||
|
||||
// Mark the variable as having retrieved all its properties.
|
||||
aTarget._retrieved = true;
|
||||
this.view.commitHierarchy();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an 'onexpand' callback for a variable, lazily handling
|
||||
* the addition of new properties.
|
||||
*
|
||||
* @param Variable aVar
|
||||
* The variable where the properties will be placed into.
|
||||
* @param any aSource
|
||||
* The source to use to populate the target.
|
||||
*/
|
||||
addExpander: function(aTarget, aSource) {
|
||||
// Attach evaluation macros as necessary.
|
||||
if (aTarget.getter || aTarget.setter) {
|
||||
aTarget.evaluationMacro = this._overrideValueEvalMacro;
|
||||
|
||||
let getter = aTarget.get("get");
|
||||
if (getter) {
|
||||
getter.evaluationMacro = this._getterOrSetterEvalMacro;
|
||||
}
|
||||
|
||||
let setter = aTarget.get("set");
|
||||
if (setter) {
|
||||
setter.evaluationMacro = this._getterOrSetterEvalMacro;
|
||||
}
|
||||
} else {
|
||||
aTarget.evaluationMacro = this._simpleValueEvalMacro;
|
||||
}
|
||||
|
||||
// If the source is primitive then an expander is not needed.
|
||||
if (VariablesView.isPrimitive({ value: aSource })) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the source is a long string then show the arrow.
|
||||
if (WebConsoleUtils.isActorGrip(aSource) && aSource.type == "longString") {
|
||||
aTarget.showArrow();
|
||||
}
|
||||
|
||||
// Make sure that properties are always available on expansion.
|
||||
aTarget.onexpand = () => this.expand(aTarget, aSource);
|
||||
|
||||
// Some variables are likely to contain a very large number of properties.
|
||||
// It's a good idea to be prepared in case of an expansion.
|
||||
if (aTarget.shouldPrefetch) {
|
||||
aTarget.addEventListener("mouseover", aTarget.onexpand, false);
|
||||
}
|
||||
|
||||
// Register all the actors that this controller now depends on.
|
||||
for (let grip of [aTarget.value, aTarget.getter, aTarget.setter]) {
|
||||
if (WebConsoleUtils.isActorGrip(grip)) {
|
||||
this._actors.add(grip.actor);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties to a Scope, Variable, or Property in the view. Triggered
|
||||
* when a scope is expanded or certain variables are hovered.
|
||||
*
|
||||
* @param Scope aTarget
|
||||
* The Scope to be expanded.
|
||||
* @param object aSource
|
||||
* The source to use to populate the target.
|
||||
* @return Promise
|
||||
* The promise that is resolved once the target has been expanded.
|
||||
*/
|
||||
expand: function(aTarget, aSource) {
|
||||
// Fetch the variables only once.
|
||||
if (aTarget._fetched) {
|
||||
return aTarget._fetched;
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
aTarget._fetched = deferred.promise;
|
||||
|
||||
if (!aSource) {
|
||||
throw new Error("No actor grip was given for the variable.");
|
||||
}
|
||||
|
||||
// If the target a Variable or Property then we're fetching properties
|
||||
if (VariablesView.isVariable(aTarget)) {
|
||||
this._populateFromObject(aTarget, aSource).then(() => {
|
||||
deferred.resolve();
|
||||
// Signal that properties have been fetched.
|
||||
this.view.emit("fetched", "properties", aTarget);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
switch (aSource.type) {
|
||||
case "longString":
|
||||
this._populateFromLongString(aTarget, aSource).then(() => {
|
||||
deferred.resolve();
|
||||
// Signal that a long string has been fetched.
|
||||
this.view.emit("fetched", "longString", aTarget);
|
||||
});
|
||||
break;
|
||||
case "with":
|
||||
case "object":
|
||||
this._populateFromObject(aTarget, aSource.object).then(() => {
|
||||
deferred.resolve();
|
||||
// Signal that variables have been fetched.
|
||||
this.view.emit("fetched", "variables", aTarget);
|
||||
});
|
||||
break;
|
||||
case "block":
|
||||
case "function":
|
||||
// Add nodes for every argument and every other variable in scope.
|
||||
let args = aSource.bindings.arguments;
|
||||
if (args) {
|
||||
for (let arg of args) {
|
||||
let name = Object.getOwnPropertyNames(arg)[0];
|
||||
let ref = aTarget.addItem(name, arg[name]);
|
||||
let val = arg[name].value;
|
||||
this.addExpander(ref, val);
|
||||
}
|
||||
}
|
||||
|
||||
aTarget.addItems(aSource.bindings.variables, {
|
||||
// Not all variables need to force sorted properties.
|
||||
sorted: VARIABLES_SORTING_ENABLED,
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
callback: this.addExpander
|
||||
});
|
||||
|
||||
// No need to signal that variables have been fetched, since
|
||||
// the scope arguments and variables are already attached to the
|
||||
// environment bindings, so pausing the active thread is unnecessary.
|
||||
|
||||
deferred.resolve();
|
||||
break;
|
||||
default:
|
||||
let error = "Unknown Debugger.Environment type: " + aSource.type;
|
||||
Cu.reportError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Release an actor from the controller.
|
||||
*
|
||||
* @param object aActor
|
||||
* The actor to release.
|
||||
*/
|
||||
releaseActor: function(aActor){
|
||||
if (this._releaseActor) {
|
||||
this._releaseActor(aActor);
|
||||
}
|
||||
this._actors.delete(aActor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Release all the actors referenced by the controller, optionally filtered.
|
||||
*
|
||||
* @param function aFilter [optional]
|
||||
* Callback to filter which actors are released.
|
||||
*/
|
||||
releaseActors: function(aFilter) {
|
||||
for (let actor of this._actors) {
|
||||
if (!aFilter || aFilter(actor)) {
|
||||
this.releaseActor(actor);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attaches a VariablesViewController to a VariablesView if it doesn't already
|
||||
* have one.
|
||||
*
|
||||
* @param VariablesView aView
|
||||
* The view to attach to.
|
||||
* @param object aOptions
|
||||
* The options to use in creating the controller.
|
||||
* @return VariablesViewController
|
||||
*/
|
||||
VariablesViewController.attach = function(aView, aOptions) {
|
||||
if (aView.controller) {
|
||||
return aView.controller;
|
||||
}
|
||||
return new VariablesViewController(aView, aOptions);
|
||||
};
|
@ -91,16 +91,16 @@ StyleEditorPanel.prototype = {
|
||||
* @param {string} href
|
||||
* Url of stylesheet to find and select in editor
|
||||
* @param {number} line
|
||||
* Line number to jump to after selecting
|
||||
* Line number to jump to after selecting. One-indexed
|
||||
* @param {number} col
|
||||
* Column number to jump to after selecting
|
||||
* Column number to jump to after selecting. One-indexed
|
||||
*/
|
||||
selectStyleSheet: function(href, line, col) {
|
||||
if (!this._debuggee || !this.UI) {
|
||||
return;
|
||||
}
|
||||
let stylesheet = this._debuggee.styleSheetFromHref(href);
|
||||
this.UI.selectStyleSheet(href, line, col);
|
||||
this.UI.selectStyleSheet(href, line - 1, col - 1);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -48,7 +48,7 @@ function StyleEditorUI(debuggee, panelDoc) {
|
||||
this._root = this._panelDoc.getElementById("style-editor-chrome");
|
||||
|
||||
this.editors = [];
|
||||
this.selectedStyleSheetIndex = -1;
|
||||
this.selectedEditor = null;
|
||||
|
||||
this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
|
||||
this._onStyleSheetsCleared = this._onStyleSheetsCleared.bind(this);
|
||||
@ -84,6 +84,14 @@ StyleEditorUI.prototype = {
|
||||
this._markedDirty = value;
|
||||
},
|
||||
|
||||
/*
|
||||
* Index of selected stylesheet in document.styleSheets
|
||||
*/
|
||||
get selectedStyleSheetIndex() {
|
||||
return this.selectedEditor ?
|
||||
this.selectedEditor.styleSheet.styleSheetIndex : -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Build the initial UI and wire buttons with event handlers.
|
||||
*/
|
||||
@ -140,10 +148,17 @@ StyleEditorUI.prototype = {
|
||||
* Handler for debuggee's 'stylesheets-cleared' event. Remove all editors.
|
||||
*/
|
||||
_onStyleSheetsCleared: function() {
|
||||
this._clearStyleSheetEditors();
|
||||
// remember selected sheet and line number for next load
|
||||
if (this.selectedEditor) {
|
||||
let href = this.selectedEditor.styleSheet.href;
|
||||
let {line, col} = this.selectedEditor.sourceEditor.getCaretPosition();
|
||||
this.selectStyleSheet(href, line, col);
|
||||
}
|
||||
|
||||
this._clearStyleSheetEditors();
|
||||
this._view.removeAll();
|
||||
this.selectedStyleSheetIndex = -1;
|
||||
|
||||
this.selectedEditor = null;
|
||||
|
||||
this._root.classList.add("loading");
|
||||
},
|
||||
@ -166,11 +181,22 @@ StyleEditorUI.prototype = {
|
||||
* StyleSheet object for new sheet
|
||||
*/
|
||||
_onDocumentLoad: function(event, styleSheets) {
|
||||
if (this._styleSheetToSelect) {
|
||||
// if selected stylesheet from previous load isn't here,
|
||||
// just set first stylesheet to be selected instead
|
||||
let selectedExists = styleSheets.some((sheet) => {
|
||||
return this._styleSheetToSelect.href == sheet.href;
|
||||
})
|
||||
if (!selectedExists) {
|
||||
this._styleSheetToSelect = null;
|
||||
}
|
||||
}
|
||||
for (let sheet of styleSheets) {
|
||||
this._addStyleSheetEditor(sheet);
|
||||
}
|
||||
// this might be the first stylesheet, so remove loading indicator
|
||||
|
||||
this._root.classList.remove("loading");
|
||||
|
||||
this.emit("document-load");
|
||||
},
|
||||
|
||||
@ -295,13 +321,18 @@ StyleEditorUI.prototype = {
|
||||
|
||||
onShow: function(summary, details, data) {
|
||||
let editor = data.editor;
|
||||
this.selectedEditor = editor;
|
||||
this._styleSheetToSelect = null;
|
||||
|
||||
if (!editor.sourceEditor) {
|
||||
// only initialize source editor when we switch to this view
|
||||
let inputElement = details.querySelector(".stylesheet-editor-input");
|
||||
editor.load(inputElement);
|
||||
}
|
||||
editor.onShow();
|
||||
}
|
||||
|
||||
this.emit("editor-selected", editor);
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
@ -314,7 +345,6 @@ StyleEditorUI.prototype = {
|
||||
for each (let editor in this.editors) {
|
||||
if (editor.styleSheet.href == sheet.href) {
|
||||
this._selectEditor(editor, sheet.line, sheet.col);
|
||||
this._styleSheetToSelect = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -331,18 +361,14 @@ StyleEditorUI.prototype = {
|
||||
* Column number to jump to
|
||||
*/
|
||||
_selectEditor: function(editor, line, col) {
|
||||
line = line || 1;
|
||||
col = col || 1;
|
||||
|
||||
this.selectedStyleSheetIndex = editor.styleSheet.styleSheetIndex;
|
||||
line = line || 0;
|
||||
col = col || 0;
|
||||
|
||||
editor.getSourceEditor().then(() => {
|
||||
editor.sourceEditor.setCaretPosition(line - 1, col - 1);
|
||||
editor.sourceEditor.setCaretPosition(line, col);
|
||||
});
|
||||
|
||||
this._view.activeSummary = editor.summary;
|
||||
|
||||
this.emit("editor-selected", editor);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -354,9 +380,9 @@ StyleEditorUI.prototype = {
|
||||
* a stylesheet is not passed and the editor is initialized we ignore
|
||||
* the call.
|
||||
* @param {Number} [line]
|
||||
* Line to which the caret should be moved (one-indexed).
|
||||
* Line to which the caret should be moved (zero-indexed).
|
||||
* @param {Number} [col]
|
||||
* Column to which the caret should be moved (one-indexed).
|
||||
* Column to which the caret should be moved (zero-indexed).
|
||||
*/
|
||||
selectStyleSheet: function(href, line, col)
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ _BROWSER_TEST_FILES = \
|
||||
browser_styleeditor_bug_740541_iframes.js \
|
||||
browser_styleeditor_bug_851132_middle_click.js \
|
||||
browser_styleeditor_nostyle.js \
|
||||
browser_styleeditor_reload.js \
|
||||
head.js \
|
||||
four.html \
|
||||
head.js \
|
||||
|
@ -0,0 +1,99 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
const NEW_URI = TEST_BASE_HTTPS + "media.html";
|
||||
|
||||
const LINE_NO = 5;
|
||||
const COL_NO = 3;
|
||||
|
||||
let gContentWin;
|
||||
let gUI;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
gContentWin = gBrowser.selectedTab.linkedBrowser.contentWindow.wrappedJSObject;
|
||||
gUI = panel.UI;
|
||||
|
||||
let count = 0;
|
||||
gUI.on("editor-added", function editorAdded(event, editor) {
|
||||
if (++count == 2) {
|
||||
gUI.off("editor-added", editorAdded);
|
||||
gUI.editors[0].getSourceEditor().then(runTests);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function runTests()
|
||||
{
|
||||
let count = 0;
|
||||
gUI.once("editor-selected", (event, editor) => {
|
||||
editor.getSourceEditor().then(() => {
|
||||
info("selected second editor, about to reload page");
|
||||
reloadPage();
|
||||
|
||||
gUI.on("editor-added", function editorAdded(event, editor) {
|
||||
if (++count == 2) {
|
||||
gUI.off("editor-added", editorAdded);
|
||||
gUI.editors[1].getSourceEditor().then(testRemembered);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
gUI.selectStyleSheet(gUI.editors[1].styleSheet.href, LINE_NO, COL_NO);
|
||||
}
|
||||
|
||||
function testRemembered()
|
||||
{
|
||||
is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
|
||||
|
||||
let {line, col} = gUI.selectedEditor.sourceEditor.getCaretPosition();
|
||||
is(line, LINE_NO, "correct line selected");
|
||||
is(col, COL_NO, "correct column selected");
|
||||
|
||||
testNewPage();
|
||||
}
|
||||
|
||||
function testNewPage()
|
||||
{
|
||||
let count = 0;
|
||||
gUI.on("editor-added", function editorAdded(event, editor) {
|
||||
info("editor added here")
|
||||
if (++count == 2) {
|
||||
gUI.off("editor-added", editorAdded);
|
||||
gUI.editors[0].getSourceEditor().then(testNotRemembered);
|
||||
}
|
||||
})
|
||||
|
||||
info("navigating to a different page");
|
||||
navigatePage();
|
||||
}
|
||||
|
||||
function testNotRemembered()
|
||||
{
|
||||
is(gUI.selectedEditor, gUI.editors[0], "first editor is selected");
|
||||
|
||||
let {line, col} = gUI.selectedEditor.sourceEditor.getCaretPosition();
|
||||
is(line, 0, "first line is selected");
|
||||
is(col, 0, "first column is selected");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
function reloadPage()
|
||||
{
|
||||
gContentWin.location.reload();
|
||||
}
|
||||
|
||||
function navigatePage()
|
||||
{
|
||||
gContentWin.location = NEW_URI;
|
||||
}
|
@ -49,6 +49,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||
browser_webconsole_bug_580454_timestamp_l10n.js \
|
||||
browser_webconsole_netlogging.js \
|
||||
browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js \
|
||||
browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js \
|
||||
browser_webconsole_bug_594477_clickable_output.js \
|
||||
browser_webconsole_bug_589162_css_filter.js \
|
||||
browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js \
|
||||
|
@ -0,0 +1,39 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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 TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/browser/test-console.html";
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testInputChange);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function testInputChange(hud) {
|
||||
var jsterm = hud.jsterm;
|
||||
var input = jsterm.inputNode;
|
||||
|
||||
is(input.getAttribute("focused"), "true", "input has focus");
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(input.getAttribute("focused"), "", "focus moved away");
|
||||
|
||||
// Test user changed something
|
||||
input.focus();
|
||||
EventUtils.synthesizeKey("A", {});
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(input.getAttribute("focused"), "true", "input is still focused");
|
||||
|
||||
// Test non empty input but not changed since last focus
|
||||
input.blur();
|
||||
input.focus();
|
||||
EventUtils.synthesizeKey("VK_RIGHT", {});
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(input.getAttribute("focused"), "", "input moved away");
|
||||
|
||||
jsterm = input = null;
|
||||
finishTest();
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
// Tests that the Web Console CSP messages are displayed
|
||||
|
||||
const TEST_VIOLATION = "https://example.com/browser/browser/devtools/webconsole/test/test_bug_770099_violation.html";
|
||||
const CSP_VIOLATION_MSG = "CSP WARN: Directive default-src https://example.com:443 violated by http://some.example.com/test.png"
|
||||
const CSP_VIOLATION_MSG = "Content Security Policy: Directive default-src https://example.com:443 violated by http://some.example.com/test.png"
|
||||
|
||||
let hud = undefined;
|
||||
|
||||
|
@ -37,6 +37,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
|
||||
"resource:///modules/devtools/VariablesView.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
|
||||
"resource:///modules/devtools/VariablesViewController.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
|
||||
"resource:///modules/devtools/shared/event-emitter.js");
|
||||
|
||||
@ -2104,13 +2107,9 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
else if (aNode.classList.contains("webconsole-msg-inspector")) {
|
||||
let view = aNode._variablesView;
|
||||
let actors = view ?
|
||||
this.jsterm._objectActorsInVariablesViews.get(view) :
|
||||
new Set();
|
||||
for (let actor of actors) {
|
||||
this._releaseObject(actor);
|
||||
if (view) {
|
||||
view.controller.releaseActors();
|
||||
}
|
||||
actors.clear();
|
||||
aNode._variablesView = null;
|
||||
}
|
||||
|
||||
@ -2743,6 +2742,35 @@ WebConsoleFrame.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see VariablesView.simpleValueEvalMacro
|
||||
*/
|
||||
function simpleValueEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
return VariablesView.simpleValueEvalMacro(aItem, aCurrentString, "_self");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see VariablesView.overrideValueEvalMacro
|
||||
*/
|
||||
function overrideValueEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
return VariablesView.overrideValueEvalMacro(aItem, aCurrentString, "_self");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see VariablesView.getterOrSetterEvalMacro
|
||||
*/
|
||||
function getterOrSetterEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
return VariablesView.getterOrSetterEvalMacro(aItem, aCurrentString, "_self");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a JSTerminal (a JavaScript command line). This is attached to an
|
||||
* existing HeadsUpDisplay (a Web Console instance). This code is responsible
|
||||
@ -2769,10 +2797,9 @@ function JSTerm(aWebConsoleFrame)
|
||||
this.historyPlaceHolder = 0;
|
||||
this._objectActorsInVariablesViews = new Map();
|
||||
|
||||
this._keyPress = this.keyPress.bind(this);
|
||||
this._inputEventHandler = this.inputEventHandler.bind(this);
|
||||
this._fetchVarProperties = this._fetchVarProperties.bind(this);
|
||||
this._fetchVarLongString = this._fetchVarLongString.bind(this);
|
||||
this._keyPress = this._keyPress.bind(this);
|
||||
this._inputEventHandler = this._inputEventHandler.bind(this);
|
||||
this._focusEventHandler = this._focusEventHandler.bind(this);
|
||||
this._onKeypressInVariablesView = this._onKeypressInVariablesView.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
@ -2826,12 +2853,19 @@ JSTerm.prototype = {
|
||||
*/
|
||||
lastInputValue: "",
|
||||
|
||||
/**
|
||||
* Indicate input node changed since last focus.
|
||||
*
|
||||
* @private
|
||||
* @type boolean
|
||||
*/
|
||||
_inputChanged: false,
|
||||
|
||||
/**
|
||||
* History of code that was executed.
|
||||
* @type array
|
||||
*/
|
||||
history: null,
|
||||
|
||||
autocompletePopup: null,
|
||||
inputNode: null,
|
||||
completeNode: null,
|
||||
@ -2877,6 +2911,7 @@ JSTerm.prototype = {
|
||||
this.inputNode.addEventListener("keypress", this._keyPress, false);
|
||||
this.inputNode.addEventListener("input", this._inputEventHandler, false);
|
||||
this.inputNode.addEventListener("keyup", this._inputEventHandler, false);
|
||||
this.inputNode.addEventListener("focus", this._focusEventHandler, false);
|
||||
|
||||
this.lastInputValue && this.setInputValue(this.lastInputValue);
|
||||
},
|
||||
@ -3282,7 +3317,27 @@ JSTerm.prototype = {
|
||||
view.searchEnabled = !aOptions.hideFilterInput;
|
||||
view.lazyEmpty = this._lazyVariablesView;
|
||||
view.lazyAppend = this._lazyVariablesView;
|
||||
this._objectActorsInVariablesViews.set(view, new Set());
|
||||
|
||||
VariablesViewController.attach(view, {
|
||||
getGripClient: aGrip => {
|
||||
return new GripClient(this.hud.proxy.client, aGrip);
|
||||
},
|
||||
getLongStringClient: aGrip => {
|
||||
return this.webConsoleClient.longString(aGrip);
|
||||
},
|
||||
releaseActor: aActor => {
|
||||
this.hud._releaseObject(aActor);
|
||||
},
|
||||
simpleValueEvalMacro: simpleValueEvalMacro,
|
||||
overrideValueEvalMacro: overrideValueEvalMacro,
|
||||
getterOrSetterEvalMacro: getterOrSetterEvalMacro,
|
||||
});
|
||||
|
||||
// Relay events from the VariablesView.
|
||||
view.on("fetched", (aEvent, aType, aVar) => {
|
||||
this.emit("variablesview-fetched", aVar);
|
||||
});
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
@ -3304,16 +3359,11 @@ JSTerm.prototype = {
|
||||
view.createHierarchy();
|
||||
view.empty();
|
||||
|
||||
let actors = this._objectActorsInVariablesViews.get(view);
|
||||
for (let actor of actors) {
|
||||
// We need to avoid pruning the object inspection starting point.
|
||||
// That one is pruned when the console message is removed.
|
||||
if (view._consoleLastObjectActor != actor) {
|
||||
this.hud._releaseObject(actor);
|
||||
}
|
||||
}
|
||||
|
||||
actors.clear();
|
||||
// We need to avoid pruning the object inspection starting point.
|
||||
// That one is pruned when the console message is removed.
|
||||
view.controller.releaseActors(aActor => {
|
||||
return view._consoleLastObjectActor != aActor;
|
||||
});
|
||||
|
||||
if (aOptions.objectActor) {
|
||||
// Make sure eval works in the correct context.
|
||||
@ -3331,11 +3381,11 @@ JSTerm.prototype = {
|
||||
scope.expanded = true;
|
||||
scope.locked = true;
|
||||
|
||||
let container = scope.addVar();
|
||||
container.evaluationMacro = this._variablesViewSimpleValueEvalMacro;
|
||||
let container = scope.addItem();
|
||||
container.evaluationMacro = simpleValueEvalMacro;
|
||||
|
||||
if (aOptions.objectActor) {
|
||||
this._fetchVarProperties(container, aOptions.objectActor);
|
||||
view.controller.expand(container, aOptions.objectActor);
|
||||
view._consoleLastObjectActor = aOptions.objectActor.actor;
|
||||
}
|
||||
else if (aOptions.rawObject) {
|
||||
@ -3374,80 +3424,6 @@ JSTerm.prototype = {
|
||||
this.requestEvaluation(aString, evalOptions).then(onEval, onEval);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates the string evaluated when performing simple value changes in the
|
||||
* variables view.
|
||||
*
|
||||
* @private
|
||||
* @param Variable | Property aItem
|
||||
* The current variable or property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
_variablesViewSimpleValueEvalMacro:
|
||||
function JST__variablesViewSimpleValueEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
return "_self" + aItem.symbolicName + "=" + aCurrentString;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generates the string evaluated when overriding getters and setters with
|
||||
* plain values in the variables view.
|
||||
*
|
||||
* @private
|
||||
* @param Property aItem
|
||||
* The current getter or setter property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
_variablesViewOverrideValueEvalMacro:
|
||||
function JST__variablesViewOverrideValueEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
let parent = aItem.ownerView;
|
||||
let symbolicName = parent.symbolicName;
|
||||
if (symbolicName.indexOf("_self") != 0) {
|
||||
parent._symbolicName = "_self" + symbolicName;
|
||||
}
|
||||
|
||||
let result = VariablesView.overrideValueEvalMacro.apply(this, arguments);
|
||||
|
||||
parent._symbolicName = symbolicName;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates the string evaluated when performing getters and setters changes
|
||||
* in the variables view.
|
||||
*
|
||||
* @private
|
||||
* @param Property aItem
|
||||
* The current getter or setter property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
_variablesViewGetterOrSetterEvalMacro:
|
||||
function JST__variablesViewGetterOrSetterEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
let propertyObject = aItem.ownerView;
|
||||
let parentObject = propertyObject.ownerView;
|
||||
let parent = parentObject.symbolicName;
|
||||
parentObject._symbolicName = "_self" + parent;
|
||||
|
||||
let result = VariablesView.getterOrSetterEvalMacro.apply(this, arguments);
|
||||
|
||||
parentObject._symbolicName = parent;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* The property deletion function used by the variables view when a property
|
||||
* is deleted.
|
||||
@ -3556,144 +3532,7 @@ JSTerm.prototype = {
|
||||
aCallback && aCallback(aResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties to a variable in the view. Triggered when a variable is
|
||||
* expanded. It does not expand the variable.
|
||||
*
|
||||
* @param object aVar
|
||||
* The VariablseView Variable instance where the properties get added.
|
||||
* @param object [aGrip]
|
||||
* Optional, the object actor grip of the variable. If the grip is not
|
||||
* provided, then the aVar.value is used as the object actor grip.
|
||||
*/
|
||||
_fetchVarProperties: function JST__fetchVarProperties(aVar, aGrip)
|
||||
{
|
||||
// Retrieve the properties only once.
|
||||
if (aVar._fetched) {
|
||||
return;
|
||||
}
|
||||
aVar._fetched = true;
|
||||
|
||||
let grip = aGrip || aVar.value;
|
||||
if (!grip) {
|
||||
throw new Error("No object actor grip was given for the variable.");
|
||||
}
|
||||
|
||||
let view = aVar._variablesView;
|
||||
let actors = this._objectActorsInVariablesViews.get(view);
|
||||
|
||||
function addActorForDescriptor(aGrip) {
|
||||
if (WebConsoleUtils.isActorGrip(aGrip)) {
|
||||
actors.add(aGrip.actor);
|
||||
}
|
||||
}
|
||||
|
||||
let onNewProperty = (aProperty) => {
|
||||
if (aProperty.getter || aProperty.setter) {
|
||||
aProperty.evaluationMacro = this._variablesViewOverrideValueEvalMacro;
|
||||
let getter = aProperty.get("get");
|
||||
let setter = aProperty.get("set");
|
||||
if (getter) {
|
||||
getter.evaluationMacro = this._variablesViewGetterOrSetterEvalMacro;
|
||||
}
|
||||
if (setter) {
|
||||
setter.evaluationMacro = this._variablesViewGetterOrSetterEvalMacro;
|
||||
}
|
||||
}
|
||||
else {
|
||||
aProperty.evaluationMacro = this._variablesViewSimpleValueEvalMacro;
|
||||
}
|
||||
|
||||
let grips = [aProperty.value, aProperty.getter, aProperty.setter];
|
||||
grips.forEach(addActorForDescriptor);
|
||||
|
||||
let inspectable = !VariablesView.isPrimitive({ value: aProperty.value });
|
||||
let longString = WebConsoleUtils.isActorGrip(aProperty.value) &&
|
||||
aProperty.value.type == "longString";
|
||||
if (inspectable) {
|
||||
aProperty.onexpand = this._fetchVarProperties;
|
||||
}
|
||||
else if (longString) {
|
||||
aProperty.onexpand = this._fetchVarLongString;
|
||||
aProperty.showArrow();
|
||||
}
|
||||
};
|
||||
|
||||
let client = new GripClient(this.hud.proxy.client, grip);
|
||||
client.getPrototypeAndProperties((aResponse) => {
|
||||
let { ownProperties, prototype, safeGetterValues } = aResponse;
|
||||
let sortable = VariablesView.NON_SORTABLE_CLASSES.indexOf(grip.class) == -1;
|
||||
|
||||
// Merge the safe getter values into one object such that we can use it
|
||||
// in VariablesView.
|
||||
for (let name of Object.keys(safeGetterValues)) {
|
||||
if (name in ownProperties) {
|
||||
ownProperties[name].getterValue = safeGetterValues[name].getterValue;
|
||||
ownProperties[name].getterPrototypeLevel = safeGetterValues[name]
|
||||
.getterPrototypeLevel;
|
||||
}
|
||||
else {
|
||||
ownProperties[name] = safeGetterValues[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the variable properties.
|
||||
if (ownProperties) {
|
||||
aVar.addProperties(ownProperties, {
|
||||
sorted: sortable,
|
||||
callback: onNewProperty,
|
||||
});
|
||||
}
|
||||
|
||||
// Add the variable's __proto__.
|
||||
if (prototype && prototype.type != "null") {
|
||||
let proto = aVar.addProperty("__proto__", { value: prototype });
|
||||
onNewProperty(proto);
|
||||
}
|
||||
|
||||
aVar._retrieved = true;
|
||||
view.commitHierarchy();
|
||||
this.emit("variablesview-fetched", aVar);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the full string for a given variable that displays a long string.
|
||||
*
|
||||
* @param object aVar
|
||||
* The VariablesView Variable instance where the properties get added.
|
||||
*/
|
||||
_fetchVarLongString: function JST__fetchVarLongString(aVar)
|
||||
{
|
||||
if (aVar._fetched) {
|
||||
return;
|
||||
}
|
||||
aVar._fetched = true;
|
||||
|
||||
let grip = aVar.value;
|
||||
if (!grip) {
|
||||
throw new Error("No long string actor grip was given for the variable.");
|
||||
}
|
||||
|
||||
let client = this.webConsoleClient.longString(grip);
|
||||
let toIndex = Math.min(grip.length, MAX_LONG_STRING_LENGTH);
|
||||
client.substring(grip.initial.length, toIndex, (aResponse) => {
|
||||
if (aResponse.error) {
|
||||
Cu.reportError("JST__fetchVarLongString substring failure: " +
|
||||
aResponse.error + ": " + aResponse.message);
|
||||
return;
|
||||
}
|
||||
|
||||
aVar.onexpand = null;
|
||||
aVar.setGrip(grip.initial + aResponse.substring);
|
||||
aVar.hideArrow();
|
||||
aVar._retrieved = true;
|
||||
|
||||
if (toIndex != grip.length) {
|
||||
this.hud.logWarningAboutStringTooLong();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes a JS object to the JSTerm outputNode.
|
||||
@ -3830,28 +3669,30 @@ JSTerm.prototype = {
|
||||
this.lastInputValue = aNewValue;
|
||||
this.completeNode.value = "";
|
||||
this.resizeInput();
|
||||
this._inputChanged = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* The inputNode "input" and "keyup" event handler.
|
||||
*
|
||||
* @param nsIDOMEvent aEvent
|
||||
* @private
|
||||
*/
|
||||
inputEventHandler: function JSTF_inputEventHandler(aEvent)
|
||||
_inputEventHandler: function JST__inputEventHandler()
|
||||
{
|
||||
if (this.lastInputValue != this.inputNode.value) {
|
||||
this.resizeInput();
|
||||
this.complete(this.COMPLETE_HINT_ONLY);
|
||||
this.lastInputValue = this.inputNode.value;
|
||||
this._inputChanged = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The inputNode "keypress" event handler.
|
||||
*
|
||||
* @private
|
||||
* @param nsIDOMEvent aEvent
|
||||
*/
|
||||
keyPress: function JSTF_keyPress(aEvent)
|
||||
_keyPress: function JST__keyPress(aEvent)
|
||||
{
|
||||
if (aEvent.ctrlKey) {
|
||||
let inputNode = this.inputNode;
|
||||
@ -3986,17 +3827,25 @@ JSTerm.prototype = {
|
||||
this.acceptProposedCompletion()) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
else {
|
||||
else if (this._inputChanged) {
|
||||
this.updateCompleteNode(l10n.getStr("Autocomplete.blank"));
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The inputNode "focus" event handler.
|
||||
* @private
|
||||
*/
|
||||
_focusEventHandler: function JST__focusEventHandler()
|
||||
{
|
||||
this._inputChanged = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Go up/down the history stack of input values.
|
||||
*
|
||||
@ -4358,11 +4207,7 @@ JSTerm.prototype = {
|
||||
_sidebarDestroy: function JST__sidebarDestroy()
|
||||
{
|
||||
if (this._variablesView) {
|
||||
let actors = this._objectActorsInVariablesViews.get(this._variablesView);
|
||||
for (let actor of actors) {
|
||||
this.hud._releaseObject(actor);
|
||||
}
|
||||
actors.clear();
|
||||
this._variablesView.controller.releaseActors();
|
||||
this._variablesView = null;
|
||||
}
|
||||
|
||||
@ -4397,6 +4242,7 @@ JSTerm.prototype = {
|
||||
this.inputNode.removeEventListener("keypress", this._keyPress, false);
|
||||
this.inputNode.removeEventListener("input", this._inputEventHandler, false);
|
||||
this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
|
||||
this.inputNode.removeEventListener("focus", this._focusEventHandler, false);
|
||||
|
||||
this.hud = null;
|
||||
},
|
||||
|
@ -10,6 +10,6 @@ VPATH = @srcdir@
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
DISABLED_EXTRA_COMPONENTS = fuelApplication.manifest
|
||||
EXTRA_PP_COMPONENTS = fuelApplication.js
|
||||
DISABLED_EXTRA_PP_COMPONENTS = fuelApplication.js
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
@ -9,3 +9,7 @@ MODULE = 'fuel'
|
||||
EXTRA_COMPONENTS += [
|
||||
'fuelApplication.manifest',
|
||||
]
|
||||
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'fuelApplication.js',
|
||||
]
|
||||
|
@ -341,7 +341,7 @@ Section "-Application" APP_IDX
|
||||
${AddDisabledDDEHandlerValues} "FirefoxHTML" "$2" "$8,1" \
|
||||
"${AppRegName} Document" ""
|
||||
${AddDisabledDDEHandlerValues} "FirefoxURL" "$2" "$8,1" "${AppRegName} URL" \
|
||||
"true"
|
||||
"delete"
|
||||
|
||||
; For pre win8, the following keys should only be set if we can write to HKLM.
|
||||
; For post win8, the keys below get set in both HKLM and HKCU.
|
||||
|
@ -412,7 +412,7 @@ FunctionEnd
|
||||
"${AppRegName} HTML Document" ""
|
||||
|
||||
${AddDisabledDDEHandlerValues} "FirefoxURL" "$2" "$8,1" "${AppRegName} URL" \
|
||||
"true"
|
||||
"delete"
|
||||
Call RegisterCEH
|
||||
|
||||
; An empty string is used for the 4th & 5th params because the following
|
||||
@ -650,7 +650,7 @@ FunctionEnd
|
||||
${IsHandlerForInstallDir} "FirefoxURL" $R9
|
||||
${If} "$R9" == "true"
|
||||
${AddDisabledDDEHandlerValues} "FirefoxURL" "$2" "$8,1" \
|
||||
"${AppRegName} URL" "true"
|
||||
"${AppRegName} URL" "delete"
|
||||
${EndIf}
|
||||
|
||||
; An empty string is used for the 4th & 5th params because the following
|
||||
|
@ -20,10 +20,10 @@ profiler.label=Profiler
|
||||
profiler2.commandkey=VK_F5
|
||||
profiler.accesskey=P
|
||||
|
||||
# LOCALIZATION NOTE (profiler.tooltip):
|
||||
# LOCALIZATION NOTE (profiler.tooltip2):
|
||||
# This string is displayed in the tooltip of the tab when the profiler is
|
||||
# displayed inside the developer tools window.
|
||||
profiler.tooltip=Profiler
|
||||
profiler.tooltip2=JavaScript Profiler
|
||||
|
||||
# LOCALIZATION NOTE (profiler.profileName):
|
||||
# This string is the default name for new profiles. Its parameter is a number.
|
||||
@ -82,3 +82,18 @@ profiler.loading=Loading profile…
|
||||
# This string is displayed in the profiler whenever there is already
|
||||
# another running profile. Users can run only one profile at a time.
|
||||
profiler.alreadyRunning=Profiler is already running. If you want to run this profile stop Profile %S first.
|
||||
|
||||
# LOCALIZATION NOTE (profiler.stateIdle)
|
||||
# This string is used to show that the profile in question is in IDLE
|
||||
# state meaning that it hasn't been started yet.
|
||||
profiler.stateIdle=Idle
|
||||
|
||||
# LOCALIZATION NOTE (profiler.stateRunning)
|
||||
# This string is used to show that the profile in question is in RUNNING
|
||||
# state meaning that it has been started and currently gathering profile data.
|
||||
profiler.stateRunning=Running
|
||||
|
||||
# LOCALIZATION NOTE (profiler.stateCompleted)
|
||||
# This string is used to show that the profile in question is in COMPLETED
|
||||
# state meaning that it has been started and stopped already.
|
||||
profiler.stateCompleted=Completed
|
||||
|
@ -22,6 +22,7 @@
|
||||
this.popup._input = this;
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<method name="openPopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
@ -37,6 +38,21 @@
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="formatValue">
|
||||
<body>
|
||||
<![CDATA[
|
||||
BrowserUI.formatURI();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="trimValue">
|
||||
<parameter name="aURL"/>
|
||||
<body><![CDATA[
|
||||
return BrowserUI.trimURL(aURL);
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
@ -70,12 +86,12 @@
|
||||
<content orient="horizontal">
|
||||
<xul:vbox id="results-vbox" class="meta-section viewable-height" flex="1">
|
||||
<xul:label class="meta-section-title" value="&autocompleteResultsHeader.label;"/>
|
||||
<richgrid id="results-richgrid" anonid="results" seltype="single" flex="1"/>
|
||||
<richgrid id="results-richgrid" deferlayout="true" anonid="results" seltype="single" flex="1"/>
|
||||
</xul:vbox>
|
||||
|
||||
<xul:vbox id="searches-vbox" class="meta-section viewable-height" flex="1">
|
||||
<xul:label class="meta-section-title" value="&autocompleteSearchesHeader.label;"/>
|
||||
<richgrid id="searches-richgrid" anonid="searches" seltype="single" flex="1"/>
|
||||
<richgrid id="searches-richgrid" deferlayout="true" anonid="searches" seltype="single" flex="1"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
@ -162,6 +178,10 @@
|
||||
|
||||
this.clearSelection();
|
||||
this.invalidate();
|
||||
|
||||
this._results.arrangeItemsNow();
|
||||
this._searches.arrangeItemsNow();
|
||||
|
||||
this._fire("autocompletestart");
|
||||
]]>
|
||||
</body>
|
||||
@ -395,7 +415,7 @@
|
||||
<parameter name="aGrid"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return aGrid.itemCount != undefined;
|
||||
return aGrid && aGrid.itemCount != undefined;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -126,7 +126,7 @@
|
||||
* rectBrowserToClient
|
||||
* Converts a rect (left, top, right, bottom).
|
||||
*
|
||||
* @param aMessage - message manager message
|
||||
* @param aRect - rect to convert
|
||||
* @param aIgnoreScroll ignore root frame scroll.
|
||||
* @param aIgnoreScale ignore current scale factor.
|
||||
* @return { left:, top:, right:, bottom: }
|
||||
|
@ -338,53 +338,13 @@
|
||||
<field name="_columnCount">0</field>
|
||||
<property name="columnCount" readonly="true" onget="return this._columnCount;"/>
|
||||
|
||||
<field name="_scheduledArrangeItemsTries">0</field>
|
||||
|
||||
<!-- define a height where we consider an item not yet rendered
|
||||
10 is the height of the empty item (padding/border etc. only) -->
|
||||
<field name="_itemHeightRenderThreshold">10</field>
|
||||
|
||||
<method name="arrangeItems">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.itemCount <= 0) {
|
||||
return;
|
||||
}
|
||||
let item = this.getItemAtIndex(0);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
let gridItemRect = item.getBoundingClientRect();
|
||||
|
||||
// cap the number of times we reschedule calling arrangeItems
|
||||
let maxRetries = 5;
|
||||
|
||||
// delay as necessary until the item has a proper height
|
||||
if (gridItemRect.height <= this._itemHeightRenderThreshold) {
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
// retry of arrangeItems already scheduled
|
||||
return;
|
||||
}
|
||||
|
||||
// track how many times we've attempted arrangeItems
|
||||
this._scheduledArrangeItemsTries++;
|
||||
|
||||
if (maxRetries > this._scheduledArrangeItemsTries) {
|
||||
// schedule re-try of arrangeItems at the next tick
|
||||
this._scheduledArrangeItemsTimerId = setTimeout(this.arrangeItems.bind(this), 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// items ready to arrange (or retries max exceeded)
|
||||
// reset the flags
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
clearTimeout(this._scheduledArrangeItemsTimerId);
|
||||
delete this._scheduledArrangeItemsTimerId;
|
||||
}
|
||||
if (this._scheduledArrangeItemsTries) {
|
||||
this._scheduledArrangeItemsTries = 0;
|
||||
}
|
||||
<property name="_containerRect">
|
||||
<getter><![CDATA[
|
||||
// return the rect that represents our bounding box
|
||||
|
||||
// Autocomplete is a binding within a binding, so we have to step
|
||||
// up an additional parentNode.
|
||||
@ -394,12 +354,89 @@
|
||||
container = this.parentNode.parentNode.getBoundingClientRect();
|
||||
else
|
||||
container = this.parentNode.getBoundingClientRect();
|
||||
return container;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
// If we don't have valid dimensions we can't arrange yet
|
||||
if (!container.height || !gridItemRect.height) {
|
||||
<property name="_itemRect">
|
||||
<getter><![CDATA[
|
||||
// return the rect that represents an item in the grid
|
||||
|
||||
// TODO: when we remove the need for DOM item measurement, 0 items will not be a problem
|
||||
let item = this.itemCount ? this.getItemAtIndex(0) : null;
|
||||
if (item) {
|
||||
let gridItemRect = item.getBoundingClientRect();
|
||||
if (gridItemRect.height > this._itemHeightRenderThreshold) {
|
||||
return gridItemRect;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
]]></getter>
|
||||
</property>
|
||||
|
||||
<!-- do conditions allow layout/arrange of the grid? -->
|
||||
<property name="_canLayout" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
let gridItemRect = this._itemRect;
|
||||
// If we don't have valid item dimensions we can't arrange yet
|
||||
if (!(gridItemRect && gridItemRect.height)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let container = this._containerRect;
|
||||
// If we don't have valid container dimensions we can't arrange yet
|
||||
if (!(container && container.height)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<field name="_scheduledArrangeItemsTimerId">null</field>
|
||||
<field name="_scheduledArrangeItemsTries">0</field>
|
||||
<field name="_maxArrangeItemsRetries">5</field>
|
||||
<method name="_scheduleArrangeItems">
|
||||
<parameter name="aTime"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// cap the number of times we reschedule calling arrangeItems
|
||||
if (
|
||||
!this._scheduledArrangeItemsTimerId &&
|
||||
this._maxArrangeItemsRetries > this._scheduledArrangeItemsTries
|
||||
) {
|
||||
this._scheduledArrangeItemsTimerId = setTimeout(this.arrangeItems.bind(this), aTime || 0);
|
||||
// track how many times we've attempted arrangeItems
|
||||
this._scheduledArrangeItemsTries++;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="arrangeItems">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.hasAttribute("deferlayout")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._canLayout) {
|
||||
// try again later
|
||||
this._scheduleArrangeItems();
|
||||
return;
|
||||
}
|
||||
|
||||
let gridItemRect = this._itemRect;
|
||||
let container = this._containerRect;
|
||||
|
||||
// reset the flags
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
clearTimeout(this._scheduledArrangeItemsTimerId);
|
||||
delete this._scheduledArrangeItemsTimerId;
|
||||
}
|
||||
this._scheduledArrangeItemsTries = 0;
|
||||
|
||||
// We favor overflowing horizontally, not vertically
|
||||
let maxRowCount = Math.floor(container.height / gridItemRect.height) - 1;
|
||||
|
||||
@ -418,6 +455,21 @@
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<method name="arrangeItemsNow">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.removeAttribute("deferlayout");
|
||||
// cancel any scheduled arrangeItems and reset flags
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
clearTimeout(this._scheduledArrangeItemsTimerId);
|
||||
delete this._scheduledArrangeItemsTimerId;
|
||||
}
|
||||
this._scheduledArrangeItemsTries = 0;
|
||||
// pass over any params
|
||||
return this.arrangeItems.apply(this, arguments);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Inteface to suppress selection events -->
|
||||
|
||||
|
@ -10,9 +10,9 @@
|
||||
<html:div flex="1" class="selection-overlay-inner window-width window-height" anonid="selection-overlay-inner">
|
||||
<xul:stack>
|
||||
<html:div anonid="selection-overlay-debug" class="window-width window-height"/>
|
||||
<xul:toolbarbutton id="selectionhandle-mark1" label="^" left="10" top="10" hidden="true"/>
|
||||
<xul:toolbarbutton id="selectionhandle-mark2" label="^" left="10" top="10" hidden="true"/>
|
||||
<xul:toolbarbutton id="selectionhandle-mark3" label="^" left="10" top="10" hidden="true"/>
|
||||
<xul:toolbarbutton anonid="selectionhandle-mark1" class="selectionhandle" label="^" left="10" top="10" hidden="true"/>
|
||||
<xul:toolbarbutton anonid="selectionhandle-mark2" class="selectionhandle" label="^" left="10" top="10" hidden="true"/>
|
||||
<xul:toolbarbutton anonid="selectionhandle-mark3" class="selectionhandle" label="^" left="10" top="10" hidden="true"/>
|
||||
</xul:stack>
|
||||
</html:div>
|
||||
</content>
|
||||
@ -62,18 +62,11 @@
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="init">
|
||||
<method name="getMarker">
|
||||
<parameter name="aMarkerId"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.enabled = true;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="shutdown">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.enabled = false;
|
||||
return document.getAnonymousElementByAttribute(this, "anonid", aMarkerId);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -109,6 +109,8 @@ let ScriptContexts = {};
|
||||
["OfflineApps", "chrome://browser/content/helperui/OfflineApps.js"],
|
||||
["SelectHelperUI", "chrome://browser/content/helperui/SelectHelperUI.js"],
|
||||
["SelectionHelperUI", "chrome://browser/content/helperui/SelectionHelperUI.js"],
|
||||
["SelectionPrototype", "chrome://browser/content/library/SelectionPrototype.js"],
|
||||
["ChromeSelectionHandler", "chrome://browser/content/helperui/ChromeSelectionHandler.js"],
|
||||
["AnimatedZoom", "chrome://browser/content/AnimatedZoom.js"],
|
||||
["CommandUpdater", "chrome://browser/content/commandUtil.js"],
|
||||
["ContextCommands", "chrome://browser/content/ContextCommands.js"],
|
||||
|
@ -97,7 +97,11 @@ var BrowserUI = {
|
||||
window.addEventListener("MozImprecisePointer", this, true);
|
||||
|
||||
Services.prefs.addObserver("browser.cache.disk_cache_ssl", this, false);
|
||||
Services.prefs.addObserver("browser.urlbar.formatting.enabled", this, false);
|
||||
Services.prefs.addObserver("browser.urlbar.trimURLs", this, false);
|
||||
Services.obs.addObserver(this, "metro_viewstate_changed", false);
|
||||
|
||||
this._edit.inputField.controllers.insertControllerAt(0, this._copyCutURIController);
|
||||
|
||||
// Init core UI modules
|
||||
ContextUI.init();
|
||||
@ -239,11 +243,20 @@ var BrowserUI = {
|
||||
|
||||
getDisplayURI: function(browser) {
|
||||
let uri = browser.currentURI;
|
||||
let spec = uri.spec;
|
||||
|
||||
try {
|
||||
uri = gURIFixup.createExposableURI(uri);
|
||||
spec = gURIFixup.createExposableURI(uri).spec;
|
||||
} catch (ex) {}
|
||||
|
||||
return uri.spec;
|
||||
try {
|
||||
let charset = browser.characterSet;
|
||||
let textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
|
||||
getService(Ci.nsITextToSubURI);
|
||||
spec = textToSubURI.unEscapeNonAsciiURI(charset, spec);
|
||||
} catch (ex) {}
|
||||
|
||||
return spec;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -560,6 +573,12 @@ var BrowserUI = {
|
||||
case "browser.cache.disk_cache_ssl":
|
||||
this._sslDiskCacheEnabled = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "browser.urlbar.formatting.enabled":
|
||||
this._formattingEnabled = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "browser.urlbar.trimURLs":
|
||||
this._mayTrimURLs = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "metro_viewstate_changed":
|
||||
@ -650,15 +669,138 @@ var BrowserUI = {
|
||||
Elements.urlbarState.setAttribute("mode", "view");
|
||||
},
|
||||
|
||||
_trimURL: function _trimURL(aURL) {
|
||||
// This function must not modify the given URL such that calling
|
||||
// nsIURIFixup::createFixupURI with the result will produce a different URI.
|
||||
return aURL /* remove single trailing slash for http/https/ftp URLs */
|
||||
.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
|
||||
/* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
|
||||
.replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
|
||||
},
|
||||
|
||||
trimURL: function trimURL(aURL) {
|
||||
return this.mayTrimURLs ? this._trimURL(aURL) : aURL;
|
||||
},
|
||||
|
||||
_setURI: function _setURI(aURL) {
|
||||
this._edit.value = aURL;
|
||||
this.lastKnownGoodURL = aURL;
|
||||
},
|
||||
|
||||
_urlbarClicked: function _urlbarClicked() {
|
||||
_getSelectedURIForClipboard: function _getSelectedURIForClipboard() {
|
||||
// Grab the actual input field's value, not our value, which could include moz-action:
|
||||
let inputVal = this._edit.inputField.value;
|
||||
let selectedVal = inputVal.substring(this._edit.selectionStart, this._edit.electionEnd);
|
||||
|
||||
// If the selection doesn't start at the beginning or doesn't span the full domain or
|
||||
// the URL bar is modified, nothing else to do here.
|
||||
if (this._edit.selectionStart > 0 || this._edit.valueIsTyped)
|
||||
return selectedVal;
|
||||
// The selection doesn't span the full domain if it doesn't contain a slash and is
|
||||
// followed by some character other than a slash.
|
||||
if (!selectedVal.contains("/")) {
|
||||
let remainder = inputVal.replace(selectedVal, "");
|
||||
if (remainder != "" && remainder[0] != "/")
|
||||
return selectedVal;
|
||||
}
|
||||
|
||||
let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
|
||||
|
||||
let uri;
|
||||
try {
|
||||
uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_USE_UTF8);
|
||||
} catch (e) {}
|
||||
if (!uri)
|
||||
return selectedVal;
|
||||
|
||||
// Only copy exposable URIs
|
||||
try {
|
||||
uri = uriFixup.createExposableURI(uri);
|
||||
} catch (ex) {}
|
||||
|
||||
// If the entire URL is selected, just use the actual loaded URI.
|
||||
if (inputVal == selectedVal) {
|
||||
// ... but only if isn't a javascript: or data: URI, since those
|
||||
// are hard to read when encoded
|
||||
if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
|
||||
// Parentheses are known to confuse third-party applications (bug 458565).
|
||||
selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
|
||||
}
|
||||
|
||||
return selectedVal;
|
||||
}
|
||||
|
||||
// Just the beginning of the URL is selected, check for a trimmed value
|
||||
let spec = uri.spec;
|
||||
let trimmedSpec = this.trimURL(spec);
|
||||
if (spec != trimmedSpec) {
|
||||
// Prepend the portion that trimURL removed from the beginning.
|
||||
// This assumes trimURL will only truncate the URL at
|
||||
// the beginning or end (or both).
|
||||
let trimmedSegments = spec.split(trimmedSpec);
|
||||
selectedVal = trimmedSegments[0] + selectedVal;
|
||||
}
|
||||
|
||||
return selectedVal;
|
||||
},
|
||||
|
||||
_copyCutURIController: {
|
||||
doCommand: function(aCommand) {
|
||||
let urlbar = BrowserUI._edit;
|
||||
let val = BrowserUI._getSelectedURIForClipboard();
|
||||
if (!val)
|
||||
return;
|
||||
|
||||
if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
|
||||
let start = urlbar.selectionStart;
|
||||
let end = urlbar.selectionEnd;
|
||||
urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
|
||||
urlbar.inputField.value.substring(end);
|
||||
urlbar.selectionStart = urlbar.selectionEnd = start;
|
||||
}
|
||||
|
||||
Cc["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(Ci.nsIClipboardHelper)
|
||||
.copyString(val, document);
|
||||
},
|
||||
|
||||
supportsCommand: function(aCommand) {
|
||||
switch (aCommand) {
|
||||
case "cmd_copy":
|
||||
case "cmd_cut":
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
isCommandEnabled: function(aCommand) {
|
||||
let urlbar = BrowserUI._edit;
|
||||
return this.supportsCommand(aCommand) &&
|
||||
(aCommand != "cmd_cut" || !urlbar.readOnly) &&
|
||||
urlbar.selectionStart < urlbar.selectionEnd;
|
||||
},
|
||||
|
||||
onEvent: function(aEventName) {}
|
||||
},
|
||||
|
||||
_urlbarClicked: function _urlbarClicked(aEvent) {
|
||||
let touchEvent = aEvent.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH;
|
||||
|
||||
// If the urlbar is not already focused, focus it and select the contents.
|
||||
if (Elements.urlbarState.getAttribute("mode") != "edit")
|
||||
if (Elements.urlbarState.getAttribute("mode") != "edit") {
|
||||
this._editURI(true);
|
||||
if (touchEvent) {
|
||||
SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
|
||||
aEvent.clientX, aEvent.clientY);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// tap caret handling
|
||||
if (touchEvent) {
|
||||
SelectionHelperUI.attachToCaret(ChromeSelectionHandler,
|
||||
aEvent.clientX, aEvent.clientY);
|
||||
}
|
||||
},
|
||||
|
||||
_editURI: function _editURI(aShouldDismiss) {
|
||||
@ -667,8 +809,74 @@ var BrowserUI = {
|
||||
|
||||
Elements.urlbarState.setAttribute("mode", "edit");
|
||||
StartUI.show();
|
||||
if (aShouldDismiss)
|
||||
if (aShouldDismiss) {
|
||||
ContextUI.dismissTabs();
|
||||
}
|
||||
},
|
||||
|
||||
formatURI: function formatURI() {
|
||||
if (!this.formattingEnabled ||
|
||||
Elements.urlbarState.getAttribute("mode") == "edit")
|
||||
return;
|
||||
|
||||
let controller = this._edit.editor.selectionController;
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
|
||||
let textNode = this._edit.editor.rootElement.firstChild;
|
||||
let value = textNode.textContent;
|
||||
|
||||
let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
|
||||
if (protocol &&
|
||||
["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
|
||||
return;
|
||||
let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
|
||||
if (!matchedURL)
|
||||
return;
|
||||
|
||||
let [, preDomain, domain] = matchedURL;
|
||||
let baseDomain = domain;
|
||||
let subDomain = "";
|
||||
// getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
|
||||
if (domain[0] != "[") {
|
||||
try {
|
||||
baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
|
||||
if (!domain.endsWith(baseDomain)) {
|
||||
// getBaseDomainFromHost converts its resultant to ACE.
|
||||
let IDNService = Cc["@mozilla.org/network/idn-service;1"]
|
||||
.getService(Ci.nsIIDNService);
|
||||
baseDomain = IDNService.convertACEtoUTF8(baseDomain);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (baseDomain != domain) {
|
||||
subDomain = domain.slice(0, -baseDomain.length);
|
||||
}
|
||||
|
||||
let rangeLength = preDomain.length + subDomain.length;
|
||||
if (rangeLength) {
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, 0);
|
||||
range.setEnd(textNode, rangeLength);
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
let startRest = preDomain.length + domain.length;
|
||||
if (startRest < value.length) {
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, startRest);
|
||||
range.setEnd(textNode, value.length);
|
||||
selection.addRange(range);
|
||||
}
|
||||
},
|
||||
|
||||
_clearURIFormatting: function _clearURIFormatting() {
|
||||
if (!this.formattingEnabled)
|
||||
return;
|
||||
|
||||
let controller = this._edit.editor.selectionController;
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
},
|
||||
|
||||
_urlbarBlurred: function _urlbarBlurred() {
|
||||
@ -676,6 +884,7 @@ var BrowserUI = {
|
||||
if (state.getAttribute("mode") == "edit")
|
||||
state.removeAttribute("mode");
|
||||
this._updateToolbar();
|
||||
this.formatURI();
|
||||
},
|
||||
|
||||
_closeOrQuit: function _closeOrQuit() {
|
||||
@ -959,6 +1168,24 @@ var BrowserUI = {
|
||||
return this._sslDiskCacheEnabled;
|
||||
},
|
||||
|
||||
_formattingEnabled: null,
|
||||
|
||||
get formattingEnabled() {
|
||||
if (this._formattingEnabled === null) {
|
||||
this._formattingEnabled = Services.prefs.getBoolPref("browser.urlbar.formatting.enabled");
|
||||
}
|
||||
return this._formattingEnabled;
|
||||
},
|
||||
|
||||
_mayTrimURLs: null,
|
||||
|
||||
get mayTrimURLs() {
|
||||
if (this._mayTrimURLs === null) {
|
||||
this._mayTrimURLs = Services.prefs.getBoolPref("browser.urlbar.trimURLs");
|
||||
}
|
||||
return this._mayTrimURLs;
|
||||
},
|
||||
|
||||
supportsCommand : function(cmd) {
|
||||
var isSupported = false;
|
||||
switch (cmd) {
|
||||
|
@ -89,7 +89,8 @@ setting[type="menulist"] {
|
||||
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-multi");
|
||||
}
|
||||
|
||||
#selection-overlay {
|
||||
#chrome-selection-overlay,
|
||||
#content-selection-overlay {
|
||||
-moz-binding: url("chrome://browser/content/bindings/selectionoverlay.xml#selection-binding");
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user