Merge m-c to s-c

This commit is contained in:
Nick Alexander 2013-06-19 16:12:15 -07:00
commit 492a680813
1470 changed files with 33479 additions and 15489 deletions

View File

@ -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

View File

@ -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 = \

View File

@ -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 \

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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;
}
};

View File

@ -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;

View File

@ -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;
}
};

View File

@ -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;
}

View File

@ -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

View 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);
}

View 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>

View File

@ -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>

View File

@ -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);
}
);
});

View File

@ -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);
}

View File

@ -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:

View File

@ -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 \

View 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>

View File

@ -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 = {};

View File

@ -51,7 +51,6 @@ STL_FLAGS=
LIBS += $(JEMALLOC_LIBS)
LIBS += \
$(EXTRA_DSO_LIBS) \
$(XPCOM_STANDALONE_GLUE_LDOPTS) \
$(NULL)

View File

@ -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);

View File

@ -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 ====================

View File

@ -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

View File

@ -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

View File

@ -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',
]

View File

@ -1,4 +1,4 @@
{
"revision": "a7f713158285156858dcf774ce51782ac01c938c",
"revision": "393b5209f9bc300017f2e037b1d0beeda4da7519",
"repo_path": "/integration/gaia-central"
}

View 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"
}
}
}

View 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"
}
]

View File

@ -54,7 +54,6 @@ DEFINES += -DXPCOM_GLUE
STL_FLAGS=
LIBS += \
$(EXTRA_DSO_LIBS) \
$(XPCOM_STANDALONE_GLUE_LDOPTS) \
$(NULL)

View File

@ -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>

View File

@ -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";
}

View File

@ -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();
}

View File

@ -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();
});
}

View File

@ -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();
});
});
}));
});
});
}

View File

@ -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;

View File

@ -13,7 +13,7 @@ DISABLED_EXTRA_COMPONENTS = \
BrowserComponents.manifest \
$(NULL)
EXTRA_PP_COMPONENTS = \
DISABLED_EXTRA_PP_COMPONENTS = \
nsBrowserContentHandler.js \
nsBrowserGlue.js \
$(NULL)

View File

@ -25,7 +25,7 @@ DISABLED_EXTRA_COMPONENTS = \
WebContentConverter.js \
$(NULL)
EXTRA_PP_COMPONENTS = \
DISABLED_EXTRA_PP_COMPONENTS = \
FeedWriter.js \
$(NULL)

View File

@ -15,3 +15,7 @@ EXTRA_COMPONENTS += [
'FeedConverter.js',
'WebContentConverter.js',
]
EXTRA_PP_COMPONENTS += [
'FeedWriter.js',
]

View File

@ -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)

View File

@ -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',
]

View File

@ -39,3 +39,7 @@ MODULE = 'browsercomps'
EXTRA_COMPONENTS += [
'BrowserComponents.manifest',
]
EXTRA_PP_COMPONENTS += [
'nsBrowserContentHandler.js',
'nsBrowserGlue.js',
]

View File

@ -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() {

View File

@ -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"

View File

@ -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"

View File

@ -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;

View File

@ -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");

View File

@ -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");

View File

@ -66,7 +66,7 @@ function test() {
}
EventUtils.synthesizeKey("VK_RETURN", {});
executeSoon(afterSearch);
executeSoon(() => executeSoon(afterSearch));
});
});
}

View File

@ -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) {

View File

@ -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 &&

View File

@ -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();
},

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
*/

View File

@ -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

View File

@ -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,

View File

@ -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",

View File

@ -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.");

View File

@ -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.");

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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.

View File

@ -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.
*

View File

@ -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);
}
});

View File

@ -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>

View File

@ -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;

View File

@ -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);
});

View File

@ -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);
}

View File

@ -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);
});

View File

@ -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);
});
});

View File

@ -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");

View File

@ -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 }, "*");

View File

@ -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.
*

View 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);
};

View File

@ -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);
},
/**

View File

@ -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)
{

View File

@ -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 \

View File

@ -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;
}

View File

@ -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 \

View File

@ -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();
}

View File

@ -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;

View File

@ -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;
},

View File

@ -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

View File

@ -9,3 +9,7 @@ MODULE = 'fuel'
EXTRA_COMPONENTS += [
'fuelApplication.manifest',
]
EXTRA_PP_COMPONENTS += [
'fuelApplication.js',
]

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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: }

View File

@ -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 -->

View File

@ -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>

View File

@ -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"],

View File

@ -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) {

View File

@ -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