import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1256545: avoid compiler warning for pointer truncation when checking low bits r=bwc (d16c97ba81)
- Bug 1255655 - Const-ify and shrink octet_weight. r=hurley. (d5b4c89e87)
- Bug 1216837: add explicit error checks for packet length in srtp r=mcmanus rs=jesup (ce11d6694e)
- Bug 1248565 - Let child processes have its own MOZ_LOG_FILE. r=erahm (a242c8fa08)
- Bug 1256558 - Change MUST_CONVERT to avoid C4311 in VS2015; r=khuey (81b1f997f6)
- Bug 580313 - Use deque instead of manual queue im nsPrefetchService. r=smaug (023e0115e8)
- Bug 580313 - New resource hints for link. r=smaug (3c7949ab9a)
- Bug 1253593 - Fix applicationCache.onchecking notification on e10s. r=jduell (debb998b9d)
- Bug 1234177 - check to see if mFunctions.append returned error. r=bholley (0dc9d44233)
- Bug 1183754, part 1 - Get rid of aligned spacing for XPCWrappedNative fields. r=bholley (dfb09ad1a4)
- Bug 1183754, part 2 - Remove clearing of the next chunk. r=bholley (78e8c4d7cb)
- Bug 1183754, part 3 - Use a UniquePtr for XPCWrappedNativeChunk::mNextChunk. r=bholley (26d816ff0c)
- Bug 1183754, part 4 - Eliminate XPCWrappedNativeTearOffChunk. r=bholley (0f50e6c9ff)
- Bug 1252154: Delay allocation metadata collection for inline typed objects. r=sfink (9f3cea8d76)
- Bug 1250195: In TypedObject.from, decide that the input is typed only if it's an array type; r=pnkfelix (2ceb08c793)
- Bug 1250842 - Fix initialization of script source object when modules are compiled off main thread r=shu (ca3f9b900e)
- Bug 1257053 - Only make use of error stashing (via PossibleError) when it is necessary; r=jorendorff (f3744899c4)
- Bug 1253847 - Do not count the skipped EOL inside template literal. r=jorendorff (f63926bb2d)
- Bug 1221378: Don't collect allocation metadata when lazily creating standard prototypes. r=fitzgen (667e752baf)
- Bug 1249469 - Allow any script to execute when resolving constructors. (r=jimb) (474fae3645)
- Bug 1221378: SpiderMonkey: Assert against re-entrant constructor resolution. r=fitzgen (15619325de)
- Bug 1249469 - Followup: missing #include on a CLOSED TREE. (3c62bbbb31)
- Bug 1248412 - inlineIsTypedArrayHelper: Check for TypedArray and Proxy classes when we allow wrapped TypedArray. r=Waldo (1bcaba804b)
- Bug 1250307 - Add the by: "bucket" breakdown option; r=jimb (8ca32dc781)
- Bug 1221378: Add JSCLASS_DELAY_METADATA_CALLBACK flag to UnboxedPlainObject. r=jandem (7e0eb0be01)
- Bug 1064543 - Don't emit FilterTypeSet if it wouldn't remove any types. r=h4writer (d1a1a4e127)
- Bug 1242462 - Add a newline when calling TypeSet::print from a debugger. r=jandem (2e4c07aaf3)
- Bug 1247140 - Use mozilla::BinarySearch{,If} for more manual binary searches in SpiderMonkey. r=jandem (b948fe7f60)
- Bug 1249193 - Fix Debugger.Frame.this to work correctly if we're still in the script's prologue. r=shu (0f17a78c1a)
- Bug 1249193 part 2 - Fix DebugScopeProxy to return correct this-value if we're still in the prologue. r=shu (5e17bdb2c9)
- Bug 1187450 - avoid leaking cstr in SPSProfiler::allocProfileString. r=jorendorff (96964744b2)
- Bug 1251790 - Add help for "interface objects", r=terrence (9a5d8cc3fa)
- Bug 944164 - Implement proper redirection with ref counted underlying files, r=terrence (f4182fff75)
- Bug 1248352 - Allow shell option parsing code to handle help text containing blank lines r=jandem (d7f2814665)
- Bug 1249954 - Handle OOM in SingleStepCallback. r=terrence (460f323729)
- Bug 1257223 - Fix os.file.redirect(null), r=jonco (2ca40fd839)
- Bug 1245310 - Restore null check in GetFocusedNode(). r=khuey (fa816d87b2)
- Bug 1256424. Get rid of ThreadsafeAutoSafeJSContext. r=bholley (445aa7dd4b)
- Bug 1256424 followup to actually address the review comments (d085cf1900)
- Bug 1003432: Expose CustomEvent in Worker. r=smaug (3b143dc6e4)
- Bug 1256414 - Hide MozSettingsEvent from the Web; r=khuey (52fe18e823)
- Bug 1257038: Remove the worker descriptor for WorkerLocation. r=bz (2b9721e4fe)
- Bug 1257039: Remove the worker descriptor for FileReaderSync. r=bz (cc33ce76e6)
- Bug 1257355: Remove the worker descriptor for WorkerNavigator. r=bz (067f1fc9ea)
- Bug 1257480 - null check for GetOrCreateGlobalScope() in WorkerDebuggerGlobalScope, r=khuey (0d3d640fc0)
- Make split-profile run jprof in the directory with the jprof-log. No bug. (58f5060e39)
- Bug 1250333 - do not create accessibles for trailing BRs, r=davidb, roc (d8e1193adc)
- Bug 1251712 - propagate a context flag for alerts, r=davdib (d5ef6167ea)
- Bug 1251752 - logging: add tree specific methods, r=yzen (f974c207ca)
- Bug 1251218 - add special TreeWalker constructor for children creation, r=marcoz (74f939b93e)
- Bug 1251337 - TreeWalker doesn't have to check ARIA owned children for each DOM state, r=yzen (19d83af7e1)
- Bug 1249730 - make TreeWalker bi-directional, r=yzen (a229a591e1)
- Bug 1249730 - make TreeWalker bi-directional, r=yzen (5acc1155b1)
- Bug 1251743 - ARIA owns reallocation may insert a child at wrong index, r=yzen (d95065109b)
- Bug 1249253 - content removal processing can wrongly remove ARIA owned children, r=yzen (45df52f4c9)
- Bug 1254989 - extend TreeWalker::Next to accept a stopper node, r=yzen (c5dfbbdc96)
- Bug 1249730 - make TreeWalker bi-directional, follow up fix, r=yzen (747504b5ec)
- Bug 1196652 - OriginSuffix is shown in about:serviceworker on b2g. r=ferjm (f21d534755)
- Bug 1224570 - [Service Workers Panel] Service Workers panel fails to show registered service workers after restart. r=fabrice (7d7e95db4c)
- Bug 1169674 - Use originNoSuffix for permission event. r=fabrice, f=bholley (8f389cb0c7)
- Bug 1207499 - Part 1: Remove use of expression closure from b2g/. r=sicking (42b96cbfdf)
- Bug 1207499 - Part 2: Remove use of expression closure from chrome/. r=bsmedberg (7abb390349)
- Bug 1207499 - Part 3: Remove use of expression closure from docshell/. r=bz (aee60733a9)
- Bug 1207499 - Part 5: Remove use of expression closure from modules/. r=mwu (6c29c775fe)
- Bug 1147562 - Update remaining callsites of newChannel before landing the shim in netwerk/ (r=jduell) (2d37fab088)
- Bug 1207499 - Part 6: Remove use of expression closure from netwerk/. r=jduell (2881b3450f)
- Bug 1207499 - Part 7: Remove use of expression closure from parser/.  r=jst (2519fb1fff)
- Bug 1229913 - Prevent race conditions when there are many animation mutations; r=miker (442a1af761)
- Bug 1208204 - Pressing space in the animation inspector should toggle play/pause. r=pbrosset (f15cb19dcb)
- Bug 1249719 - Use promise.all instead of Promise.all to release player fronts; r=ochameau (560b1de176)
- Bug 1207499 - Part 8: Remove use of expression closure from security/. r=keeler (de33d27e8f)
- Bug 1207499 - Part 12: Remove use of expression closure from widget/. r=roc (0e7fd889d9)
- Bug 1207499 - Part 13: Remove use of expression closure from xpcom/. r=froydnj (f95a4eefde)
- Bug 1213680 - Stop using picker by default when opening inspector (r=pbrosset) (64c2741a0b)
- Bug 1233497 - Avoid unsafe CPOWs in devtools tests. r=jryans (d8bdcac868)
- Bug 1059312 - Fix highlighter offset after switching iframe context; =pbro (da36ee302d)
- Bug 1188695 - Increase the timeout of browser_markupview_keybindings_04.js (d94bafb1ef)
- Bug 1199180 - Wait for the inspector-updated event after selecting nodes with UP key. r=pbrosset (3ffcead1cd)
- Bug 1245654 - Make the inspector shortcut start the pick mode again; r=bgrins (f30b156b44)
- Bug 1244120 - Enable browser_rules_content_02.js with e10s; r=bgrins (94f9e97f20)
- Bug 1208864 - Duplicate node context menu item in markup view. r=pbro (1044d79738)
- Bug 1233363 - Fix timelime typo in animationinspector.css; r=me (0e4338c17e)
- Bug 1229000 - Adds separator borders in the timeline toolbar; r=miker (f0c07b9555)
- Bug 1233367 - Right-border of current time label has wrong color with the dark-theme .r=pbrosset (9017ea3e21)
- Bug 1239298 - Make sure percentage width of iterations and delays in the timeline are relative to the same parent (c900c4f4eb)
- Bug 1221494 - Fix the playback rate selector size on Linux; r=pbro (ddcd686764)
- Bug 1245276 - Make 'All animations' left border darker in dark theme. r=bgrins (d6b9edbda9)
- Bug 1228978 - Add a drop-mark to the playback-rate selector in the animation-inspector's toolbar; r=pbro (1948d0aeb4)
- Bug 1231945 - Display animation.id when it exists; r=tromey (291203c9a4)
- Bug 1231688 - Use waapi computed timing in devtools animation actors; r=tromey (4a6fd44649)
- Bug 1245849 - Remove mochitest browser_animation_name.js and add a xpcshell test instead; r=ochameau (bb1a570057)
- Bug 1232681 - Display script-generated animations correctly. r=pbro (15501a8ccb)
- Bug 1199712 - Prevent the animation panel from scrolling when space is pressed; r=pbro (c291c8541f)
- Bug 1247243 - Animations are shown only every 2 reloads. r=pbrosset (6162103af6)
- Bug 1249219 - Part 1: Define NonOwningAnimationTarget. r=birtles (c13d77b5d9)
- Bug 1249219 - Part 2: Remove struct PseudoElementHashKey. r=birtles (f8ff47d484)
- Bug 1249219 - Part 3: Replace Pair<Element*, CSSPseudoElementType> with NonOwningAnimationTarget. r=birtles (68a1a5e149)
- Bug 1218620 - Allow opacity animation running on compositor even if the frame has any restricted transforms. r=birtles (0f26c81fb0)
- Bug 1254419 - Move GetPropertyState alongside GetFrames; r=hiro (046dbce30e)
- Bug 1246320 part 0 - Whitespace fixes; r=whitespace-only (eda3e8b8ae)
- Bug 1247531 - Annotate intentional switch fallthrough to suppress -Wimplicit-fallthrough warning in dom/animations/. r=dholbert (fbdff98c25)
- Bug 1246320 part 1 - Add AnimationUtils::GetCurrentRealmDocument; r=bz (53c52acbe7)
- Bug 1246320 part 2 - Pass document to ParseEasing; r=hiro (269325c142)
- Bug 1246320 part 3 - Rework KeyframeEffect(ReadOnly) constructor helpers; r=hiro (e0c58fbe49)
- Bug 1246320 part 4 - Pass a document to TimingParams; r=hiro (f9ef7bc956)
- Bug 1246320 part 5 - Simplify KeyframeEffect(ReadOnly) Constructor overloads further; r=hiro (ec932de9a7)
- Bug 1254419 - Rename getPropertyState() to getProperties(); r=heycam, r=bz (4ab86c889d)
- Bug 1254419 - Fix zero-length segment handling; r=heycam (a638e5bbd7)
- Bug 1254419 - Fill in values sequence in getProperties(); r=heycam (c7c233f5ce)
- Bug 1254419 - Return animation property information from getProperties() even if the property is overridden; r=hiro (55537b4445)
- Bug 1254419 - Add values member to AnimationPropertyDetails; r=heycam, r=bz (3ea6c1fb7f)
- Bug 1254419 - Add tests for getProperties(); r=heycam (ff9999286d)
- Bug 1254419 - Make always-set members of AnimationProperty(Value)Details required; r=bz (07ef47d79a)
- Bug 1254419 - Throw if we fail to allocate memory for a values array in getProperties(); r=bz (51ae5d9f99)
- Bug 1003204: Removed CommonUtils.exceptionStr() in toolkit/ r=gfritzsche (d1c2efa08f)
- Bug 1243936 - Convert remaining callsites within devtools/ and toolkit/ to use channel.open2() (54f398eb47)
- Bug 1187270 - Add Telemetry session ID to crash annotations, r=gfritzsche (6c08170c5a)
- Bug 1249219 - Part 4: Use NonOwningAnimationTarget as the returned value of some animation target getters. r=birtles (df788abe39)
- Bug 1249219 - Part 5: Add a wrapper of AnimationAdded/Changed/Removed. r=birtles (58cf3a3ce2)
- Bug 1249219 - Part 6: Support pseudo elements in Animation Mutation Observer. r=heycam (d10c901821)
- Bug 1249219 - Part 7: Test. r=birtles (e2b78422be)
- Bug 1249219 - Part 8: Avoid adding animations on pseudo elements for Inspector temporary. r=pbro (2bff38bfe1)
- add back some utils of Bug 751291  used in tests (8fd2cc847f)
- Bug 1253470 - Part 1: Produce console warnings for invalid duration. r=birtles (a0491eeab4)
- Bug 1253470 - Part 2: Produce console warnings for invalid iterationStart. r=birtles (6a910650c9)
- Bug 1253470 - Part 3: Produce console warnings for invalid iterations. r=birtles (e3210e754e)
- Bug 1253470 - Part 4: Produce console warnings for invalid easing. r=birtles (fc1868b3c0)
- Bug 1245748 - Move ComputedTiming to a separate file; r=heycam (22a76e4f03)
- Bug 1245748 - Rename Keyframe-related IDL types to match changes to Web Animations spec; r=heycam, r=bz (e79338bafd)
- Bug 1245748 - Update handling of 'composite' dictionary members to match changes to the spec; r=heycam, r=bz (d9cc71cde8)
- Bug 1245748 - Define the Keyframe type for storing specified keyframes; r=heycam (a429e2bf46)
- Bug 1245748 - Add missing includes to TimingParams.{cpp,h}; r=heycam (3e1e121c6f)
- Bug 1245748 - Move keyframe handling code to a separate KeyframeUtils class; r=heycam (e359f26244)
- Bug 1245748 - Add KeyframeUtils::GetKeyframesFromObject; r=heycam (eda69445d7)
- Bug 1245748 - Add nsStyleContext parameter to StyleAnimationValue::ComputeValue(s); r=heycam (2c22b9926c)
- Bug 1245748 - Add a variant of StyleAnimationValue::ComputeValues that takes an nsCSSValue; r=heycam (12386559dd)
- Bug 1245748 - Split PropertyPriorityComparator into a separate (reusable) class; r=heycam (132394bf45)
- Bug 1245748 - Add PropertyPriorityIterator; r=heycam (bfef46fd12)
- Bug 1245748 - Add GetAnimationPropertiesFromKeyframes; r=heycam (4681ac8407)
- Bug 1245748 - Add ApplyDistributeSpacing for Keyframe objects; r=heycam (9c0bc885c9)
- Bug 1245748 - Use Keyframe-based utility functions when constructing KeyframeEffect(ReadOnly); r=heycam (e0b7460548)
- Bug 1229859 - Introduce new import-globals-from eslint rule to import globals from other modules; r=Mossop (10075a136c)
- Bug 1227477 - Polish the way the timeline time graduations are calculated; r=pbro (eb383aa4a0)
- Bug 1219611 - When animations end in the timeline, make sure the time-label shows the right time; r=pbro (518b574e74)
- Bug 1242898 - Clean devtools/client/animationinspector of eslint errors; r=me (df8451951b)
- Bug 1203398 - Increase the timeout of browser_animation_empty_on_invalid_nodes.js (c339d66030)
- Bug 1222937 - Show a dedicated error message for animated pseudo elements; r=pbro (b0858c955c)
- Bug 1231362 - Part 1: Upgrade existing addons to CodeMirror 5.9.0 r=bgrins (626a3129ee)
- Bug 1231362 - Part 2: Upgrade existing CodeMirror keymap to 5.9.0 r=bgrins (43f135e34b)
- Bug 1231362 - Part 3: Upgrade to CodeMirror 5.9.0 r=bgrins (e14212f0b1)
- Bug 1231362 - Part 4: Upgrade CodeMirror mode to 5.9.0 r=bgrins (049181c2b7)
- Bug 1231362 - Part 5: Upgrade existing CodeMirror tests to 5.9.0 r=bgrins (15ce59f2cc)
- Bug 1231362 - Part 6: Apply CodeMirror customization for search.js r=bgrins (a326658816)
- Bug 1231362 - Part 7: Update CodeMirror README for version 5.9.0 r=bgrins (13eca0cd85)
- Bug 1234374 - Part 1: Upgrade existing addons to CodeMirror 5.10.0 r=bgrins (0cf4d63559)
- Bug 1234374 - Part 2: Upgrade existing CodeMirror keymap to 5.10.0 r=grins (47bdb7b9b1)
- Bug 1234374 - Part 3: Upgrade to CodeMirror 5.10.0 r=bgrins (0c20797954)
- Bug 1234374 - Part 4: Upgrade CodeMirror mode to 5.10.0 r=bgrins (9678c17894)
- Bug 1234374 - Part 5: Upgrade existing CodeMirror tests to 5.10.0 r=bgrins (477daf4c8b)
- Bug 1234374 - Part 6: Update CodeMirror README for version 5.10.0 r=bgrins (dacf42a87b)
- Bug 1242309 - Upgrade to CodeMirror 5.11.0 r=bgrins (e99b62fdc7)
- Bug 762979 - Implement shorlander's line gutter mockup for the source editor. r=vporof, bgrins (a23de53dbe)
- Bug 762979 - Implement conditional breakpoint gutter style. r=jlongster (dbbfed7ef9)
- Bug 1245030 - simplify breakpoint colors in debugger to reduce visual clutter r=hholmes (643e596727)
- reshuffle modules a little (69d9409a23)
- Bug 1238537 - make nsBrowserContentHandler.js use Services.jsm, r=florian.dapted since we still have Preference Windows (136514e705)
- fix exception (ccbcb581e1)
- Bug 436564 - Add unit tests for distribution.js; r=mixedpuppy (aa3474f7ed)
- Bug 1249630 - Add language support to distribution.js; r=mixedpuppy (cfea768ef2)
- Bug 1249742 - Don't set prefs at all if overridden by lang or locale; r=mixedpuppy (3a94e1aff6)
- Bug 1251729 - Don't use eval for distribution.js; r=mixedpuppy (b2e199f68b)
- Bug 1252466 - Switch distribution.js to use Preferences.jsm; r=mixedpuppy (9ce63143ce)
- Bug 782924 - Allow locale specific preferences in distribution.ini; r=mixedpuppy (016c42ab53)
- Bug 1252627 - distribution defaultLocale shoudld take precedence over user agent locale; r=mixedpuppy (d154458567)
- Bug 1248829 - Fix number of characters reserved in memory table's cells; r=jsantell (052b7d7ca3)
- Bug 1242296 - mem. profiler: clear snapshots icon; r=ntim (2f7b316334)
- Bug 1224660 - New icon for snapshot diff view button. r=ntim (900c618352)
- Bug 1219584 - Test that we show the allocations recording hint at the correct times. r=jsantell (5af9ca4486)
- Bug 1149385 - Render retaining paths in the dominators view; r=jsantell (bd05c3a138)
- Bug 1171903 - Add endless scrolling to storage inspector to view more items. r=miker (7c9aed5f3a)
- Bug 1003860 - Various tweaks to the storage inspector. r=mratcliffe (9badb236c9)
- Bug 1171903 - Optimise storage inspector CSS and disable animations for tests. r=miker (4297543cf2)
- Bug 1248563 - eslint cleanup of storage inspector code r=pbrosset (0d9f0219c8)
- Bug 1224115 - Allow filtering the storage inspector table. r=mratcliffe (21be5a691e)
- Bug 1224115 - Properly refresh zebra stripes in the table. r=mratcliffe (6a52bd7de1)
- Bug 1253072 - Small bug fixes to the Storage Inspector functionality. r=miker (74e5bd52c6)
- Bug 1140839 - Scratchpad should remember View options. r=past (6da9c4d13f)
- Bug 1253125 - Stop duplicating telemetry from inside tools. r=mratcliffe (736ab1872f)
- Bug 1250833 - Stop using browser.xul globals specific to devtools. r=jryans (956939f560)
- Bug 1167478 - Menu items "Larger Font" and "Smaller Font" should be disabled if the current font size is equals to MAXIMUM_FONT_SIZE or MINIMUM_FONT_SIZE constants. r=past (ececdb4ac1)
- cleanup (447f001158)
- Bug 1246273 - Add localized strings for types and attempts in the JIT opts view. r=vp (a9875992a5)
This commit is contained in:
2024-03-29 08:50:36 +08:00
parent c5f8214660
commit 021f317ebe
395 changed files with 11825 additions and 4266 deletions
-1
View File
@@ -85,7 +85,6 @@ browser/extensions/loop/**
# devtools/ exclusions
devtools/*.js
devtools/client/animationinspector/**
devtools/client/canvasdebugger/**
devtools/client/commandline/**
devtools/client/debugger/**
+1 -1
View File
@@ -44,7 +44,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] =
eNoValue,
eNoAction,
eNoLiveAttr,
kGenericAccType,
eAlert,
kNoReqStates
},
{ // alertdialog
+15 -14
View File
@@ -68,20 +68,21 @@ enum AccType {
* type, the same accessible class can have several types.
*/
enum AccGenericType {
eAutoComplete = 1 << 0,
eAutoCompletePopup = 1 << 1,
eButton = 1 << 2,
eCombobox = 1 << 3,
eDocument = 1 << 4,
eHyperText = 1 << 5,
eLandmark = 1 << 6,
eList = 1 << 7,
eListControl = 1 << 8,
eMenuButton = 1 << 9,
eSelect = 1 << 10,
eTable = 1 << 11,
eTableCell = 1 << 12,
eTableRow = 1 << 13,
eAlert = 1 << 0,
eAutoComplete = 1 << 1,
eAutoCompletePopup = 1 << 2,
eButton = 1 << 3,
eCombobox = 1 << 4,
eDocument = 1 << 5,
eHyperText = 1 << 6,
eLandmark = 1 << 7,
eList = 1 << 8,
eListControl = 1 << 9,
eMenuButton = 1 << 10,
eSelect = 1 << 11,
eTable = 1 << 12,
eTableCell = 1 << 13,
eTableRow = 1 << 14,
eLastAccGenericType = eTableRow
};
+121 -2
View File
@@ -45,14 +45,16 @@ static ModuleRep sModuleMap[] = {
{ "events", logging::eEvents },
{ "platforms", logging::ePlatforms },
{ "stack", logging::eStack },
{ "text", logging::eText },
{ "tree", logging::eTree },
{ "DOMEvents", logging::eDOMEvents },
{ "focus", logging::eFocus },
{ "selection", logging::eSelection },
{ "notifications", logging::eNotifications }
{ "notifications", logging::eNotifications },
{ "stack", logging::eStack },
{ "verbose", logging::eVerbose }
};
static void
@@ -610,6 +612,61 @@ logging::SelChange(nsISelection* aSelection, DocAccessible* aDocument,
Stack();
}
void
logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...)
{
if (IsEnabledAll(logging::eTree | aExtraFlags)) {
MsgBegin("TREE", aMsg);
va_list vl;
va_start(vl, aExtraFlags);
const char* descr = nullptr;
while ((descr = va_arg(vl, const char*))) {
AccessibleInfo(descr, va_arg(vl, Accessible*));
}
va_end(vl);
MsgEnd();
if (aExtraFlags & eStack) {
Stack();
}
}
}
void
logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags,
const char* aMsg1, Accessible* aAcc,
const char* aMsg2, nsINode* aNode)
{
if (IsEnabledAll(logging::eTree | logging::eVerbose)) {
MsgBegin("TREE", aMsg);
AccessibleInfo(aMsg1, aAcc);
Accessible* acc = aAcc->Document()->GetAccessible(aNode);
if (acc) {
AccessibleInfo(aMsg2, acc);
}
else {
Node(aMsg2, aNode);
}
MsgEnd();
}
}
void
logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, Accessible* aParent)
{
if (IsEnabledAll(logging::eTree | aExtraFlags)) {
MsgBegin("TREE", aMsg);
AccessibleInfo("container", aParent);
for (uint32_t idx = 0; idx < aParent->ChildCount(); idx++) {
AccessibleInfo("child", aParent->GetChildAt(idx));
}
MsgEnd();
}
}
void
logging::MsgBegin(const char* aTitle, const char* aMsgText, ...)
{
@@ -740,6 +797,62 @@ logging::Document(DocAccessible* aDocument)
printf("\n");
}
void
logging::AccessibleInfo(const char* aDescr, Accessible* aAccessible)
{
printf(" %s: %p; ", aDescr, static_cast<void*>(aAccessible));
if (!aAccessible) {
printf("\n");
return;
}
if (aAccessible->IsDefunct()) {
printf("defunct\n");
return;
}
if (!aAccessible->Document() || aAccessible->Document()->IsDefunct()) {
printf("document is shutting down, no info\n");
return;
}
nsAutoString role;
GetAccService()->GetStringRole(aAccessible->Role(), role);
printf("role: %s", NS_ConvertUTF16toUTF8(role).get());
nsAutoString name;
aAccessible->Name(name);
if (!name.IsEmpty()) {
printf(", name: '%s'", NS_ConvertUTF16toUTF8(name).get());
}
printf(", idx: %d", aAccessible->IndexInParent());
nsINode* node = aAccessible->GetNode();
if (!node) {
printf(", node: null\n");
}
else if (node->IsNodeOfType(nsINode::eDOCUMENT)) {
printf(", document node: %p\n", static_cast<void*>(node));
}
else if (node->IsNodeOfType(nsINode::eTEXT)) {
printf(", text node: %p\n", static_cast<void*>(node));
}
else if (node->IsElement()) {
dom::Element* el = node->AsElement();
nsAutoCString tag;
el->NodeInfo()->NameAtom()->ToUTF8String(tag);
nsIAtom* idAtom = el->GetID();
nsAutoCString id;
if (idAtom) {
idAtom->ToUTF8String(id);
}
printf(", element node: %p, %s@id='%s'\n",
static_cast<void*>(el), tag.get(), id.get());
}
}
void
logging::AccessibleNNode(const char* aDescr, Accessible* aAccessible)
{
@@ -818,6 +931,12 @@ logging::IsEnabled(uint32_t aModules)
return sModules & aModules;
}
bool
logging::IsEnabledAll(uint32_t aModules)
{
return (sModules & aModules) == aModules;
}
bool
logging::IsEnabled(const nsAString& aModuleStr)
{
+27 -8
View File
@@ -35,14 +35,17 @@ enum EModules {
eEvents = 1 << 3,
ePlatforms = 1 << 4,
eStack = 1 << 5,
eText = 1 << 6,
eTree = 1 << 7,
eText = 1 << 5,
eTree = 1 << 6,
eDOMEvents = 1 << 8,
eFocus = 1 << 9,
eSelection = 1 << 10,
eNotifications = eDOMEvents | eSelection | eFocus
eDOMEvents = 1 << 7,
eFocus = 1 << 8,
eSelection = 1 << 9,
eNotifications = eDOMEvents | eSelection | eFocus,
// extras
eStack = 1 << 10,
eVerbose = 1 << 11
};
/**
@@ -50,6 +53,11 @@ enum EModules {
*/
bool IsEnabled(uint32_t aModules);
/**
* Return true if all of the given modules are logged.
*/
bool IsEnabledAll(uint32_t aModules);
/**
* Return true if the given module is logged.
*/
@@ -121,6 +129,15 @@ void FocusDispatched(Accessible* aTarget);
void SelChange(nsISelection* aSelection, DocAccessible* aDocument,
int16_t aReason);
/**
* Log the given accessible elements info.
*/
void TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...);
void TreeInfo(const char* aMsg, uint32_t aExtraFlags,
const char* aMsg1, Accessible* aAcc,
const char* aMsg2, nsINode* aNode);
void TreeInfo(const char* aMsg, uint32_t aExtraFlags, Accessible* aParent);
/**
* Log the message ('title: text' format) on new line. Print the start and end
* boundaries of the message body designated by '{' and '}' (2 spaces indent for
@@ -164,6 +181,7 @@ void Document(DocAccessible* aDocument);
/**
* Log the accessible and its DOM node as a message entry.
*/
void AccessibleInfo(const char* aDescr, Accessible* aAccessible);
void AccessibleNNode(const char* aDescr, Accessible* aAccessible);
void AccessibleNNode(const char* aDescr, nsINode* aNode);
@@ -189,7 +207,8 @@ void Enable(const nsAFlatCString& aModules);
*/
void CheckEnv();
} // namespace logs
} // namespace logging
} // namespace a11y
} // namespace mozilla
+223 -69
View File
@@ -21,18 +21,33 @@ using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
TreeWalker::
TreeWalker(Accessible* aContext, nsIContent* aContent, uint32_t aFlags) :
mDoc(aContext->Document()), mContext(aContext), mAnchorNode(aContent),
mFlags(aFlags)
TreeWalker(Accessible* aContext) :
mDoc(aContext->Document()), mContext(aContext), mAnchorNode(nullptr),
mARIAOwnsIdx(0),
mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(0),
mPhase(eAtStart)
{
NS_ASSERTION(aContent, "No node for the accessible tree walker!");
mChildFilter = mContext->NoXBLKids() ?
mChildFilter |= mContext->NoXBLKids() ?
nsIContent::eAllButXBL : nsIContent::eAllChildren;
mChildFilter |= nsIContent::eSkipPlaceholderContent;
if (aContent)
PushState(aContent);
mAnchorNode = mContext->IsDoc() ?
mDoc->DocumentNode()->GetRootElement() : mContext->GetContent();
MOZ_COUNT_CTOR(TreeWalker);
}
TreeWalker::
TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags) :
mDoc(aContext->Document()), mContext(aContext), mAnchorNode(aAnchorNode),
mARIAOwnsIdx(0),
mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(aFlags),
mPhase(eAtStart)
{
MOZ_ASSERT(mFlags & eWalkCache, "This constructor cannot be used for tree creation");
MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
mChildFilter |= mContext->NoXBLKids() ?
nsIContent::eAllButXBL : nsIContent::eAllChildren;
MOZ_COUNT_CTOR(TreeWalker);
}
@@ -42,35 +57,122 @@ TreeWalker::~TreeWalker()
MOZ_COUNT_DTOR(TreeWalker);
}
////////////////////////////////////////////////////////////////////////////////
// TreeWalker: private
Accessible*
TreeWalker::Next()
bool
TreeWalker::Seek(nsIContent* aChildNode)
{
if (mStateStack.IsEmpty())
return nullptr;
MOZ_ASSERT(aChildNode, "Child cannot be null");
ChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
while (top) {
Accessible* child = nullptr;
bool skipSubtree = false;
while (nsIContent* childNode = Next(top, &child, &skipSubtree)) {
if (child)
return child;
mPhase = eAtStart;
mStateStack.Clear();
mARIAOwnsIdx = 0;
// Walk down into subtree to find accessibles.
if (!skipSubtree && childNode->IsElement())
top = PushState(childNode);
nsIContent* childNode = nullptr;
nsINode* parentNode = aChildNode;
do {
childNode = parentNode->AsContent();
parentNode = childNode->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) &&
(mChildFilter & nsIContent::eAllButXBL) ?
childNode->GetParentNode() : childNode->GetFlattenedTreeParent();
if (!parentNode || !parentNode->IsElement()) {
return false;
}
// If ARIA owned child.
Accessible* child = mDoc->GetAccessible(childNode);
if (child && child->IsRelocated()) {
if (child->Parent() != mContext) {
return false;
}
Accessible* ownedChild = nullptr;
while ((ownedChild = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx++)) &&
ownedChild != child);
MOZ_ASSERT(ownedChild, "A child has to be in ARIA owned elements");
mPhase = eAtARIAOwns;
return true;
}
// Look in DOM.
dom::AllChildrenIterator* iter = PrependState(parentNode->AsElement(), true);
if (!iter->Seek(childNode)) {
return false;
}
if (parentNode == mAnchorNode) {
mPhase = eAtDOM;
return true;
}
} while (true);
return false;
}
Accessible*
TreeWalker::Next(nsIContent* aStopNode)
{
if (mStateStack.IsEmpty()) {
if (mPhase == eAtEnd) {
return nullptr;
}
if (mPhase == eAtDOM || mPhase == eAtARIAOwns) {
mPhase = eAtARIAOwns;
Accessible* child = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx);
if (child) {
mARIAOwnsIdx++;
return child;
}
mPhase = eAtEnd;
return nullptr;
}
if (!mAnchorNode) {
mPhase = eAtEnd;
return nullptr;
}
mPhase = eAtDOM;
PushState(mAnchorNode, true);
}
dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
while (top) {
if (aStopNode && top->Get() == aStopNode) {
return nullptr;
}
while (nsIContent* childNode = top->GetNextChild()) {
bool skipSubtree = false;
Accessible* child = AccessibleFor(childNode, mFlags, &skipSubtree);
if (child) {
return child;
}
// Walk down the subtree if allowed, otherwise check if we have reached
// a stop node.
if (!skipSubtree && childNode->IsElement()) {
top = PushState(childNode, true);
}
else if (childNode == aStopNode) {
return nullptr;
}
}
top = PopState();
}
// If we traversed the whole subtree of the anchor node. Move to next node
// relative anchor node within the context subtree if possible.
if (mFlags != eWalkContextTree)
return nullptr;
// relative anchor node within the context subtree if asked.
if (mFlags != eWalkContextTree) {
// eWalkCache flag presence indicates that the search is scoped to the
// anchor (no ARIA owns stuff).
if (mFlags & eWalkCache) {
mPhase = eAtEnd;
return nullptr;
}
return Next(aStopNode);
}
nsINode* contextNode = mContext->GetNode();
while (mAnchorNode != contextNode) {
@@ -79,10 +181,10 @@ TreeWalker::Next()
return nullptr;
nsIContent* parent = parentNode->AsElement();
top = PushState(parent);
if (top->mDOMIter.Seek(mAnchorNode)) {
top = PushState(parent, true);
if (top->Seek(mAnchorNode)) {
mAnchorNode = parent;
return Next();
return Next(aStopNode);
}
// XXX We really should never get here, it means we're trying to find an
@@ -92,58 +194,110 @@ TreeWalker::Next()
mAnchorNode = parent;
}
return nullptr;
return Next(aStopNode);
}
nsIContent*
TreeWalker::Next(ChildrenIterator* aIter, Accessible** aAccesible,
bool* aSkipSubtree)
Accessible*
TreeWalker::Prev()
{
nsIContent* childEl = aIter->mDOMIter.GetNextChild();
if (!aAccesible)
return childEl;
*aAccesible = nullptr;
*aSkipSubtree = false;
if (childEl) {
Accessible* accessible = nullptr;
if (mFlags & eWalkCache) {
accessible = mDoc->GetAccessible(childEl);
}
else if (mContext->IsAcceptableChild(childEl)) {
accessible = GetAccService()->
GetOrCreateAccessible(childEl, mContext, aSkipSubtree);
if (mStateStack.IsEmpty()) {
if (mPhase == eAtStart || mPhase == eAtDOM) {
mPhase = eAtStart;
return nullptr;
}
// Ignore the accessible and its subtree if it was repositioned by means of
// aria-owns.
if (accessible) {
if (accessible->IsRelocated()) {
*aSkipSubtree = true;
} else {
*aAccesible = accessible;
if (mPhase == eAtEnd) {
mARIAOwnsIdx = mDoc->ARIAOwnedCount(mContext);
mPhase = eAtARIAOwns;
}
if (mPhase == eAtARIAOwns) {
if (mARIAOwnsIdx > 0) {
return mDoc->ARIAOwnedAt(mContext, --mARIAOwnsIdx);
}
if (!mAnchorNode) {
mPhase = eAtStart;
return nullptr;
}
mPhase = eAtDOM;
PushState(mAnchorNode, false);
}
}
dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
while (top) {
while (nsIContent* childNode = top->GetPreviousChild()) {
// No accessible creation on the way back.
bool skipSubtree = false;
Accessible* child = AccessibleFor(childNode, eWalkCache, &skipSubtree);
if (child) {
return child;
}
// Walk down into subtree to find accessibles.
if (!skipSubtree && childNode->IsElement()) {
top = PushState(childNode, false);
}
}
return childEl;
top = PopState();
}
// At last iterate over ARIA owned children.
Accessible* parent = mDoc->GetAccessible(aIter->mDOMIter.Parent());
if (parent) {
Accessible* child = mDoc->ARIAOwnedAt(parent, aIter->mARIAOwnsIdx++);
if (child) {
*aAccesible = child;
return child->GetContent();
}
// Move to a previous node relative the anchor node within the context
// subtree if asked.
if (mFlags != eWalkContextTree) {
mPhase = eAtStart;
return nullptr;
}
nsINode* contextNode = mContext->GetNode();
while (mAnchorNode != contextNode) {
nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
if (!parentNode || !parentNode->IsElement()) {
return nullptr;
}
nsIContent* parent = parentNode->AsElement();
top = PushState(parent, true);
if (top->Seek(mAnchorNode)) {
mAnchorNode = parent;
return Prev();
}
mAnchorNode = parent;
}
mPhase = eAtStart;
return nullptr;
}
TreeWalker::ChildrenIterator*
Accessible*
TreeWalker::AccessibleFor(nsIContent* aNode, uint32_t aFlags, bool* aSkipSubtree)
{
Accessible* child = nullptr;
if (aFlags & eWalkCache) {
child = mDoc->GetAccessible(aNode);
}
else if (mContext->IsAcceptableChild(aNode)) {
child = GetAccService()->
GetOrCreateAccessible(aNode, mContext, aSkipSubtree);
}
// Ignore the accessible and its subtree if it was repositioned by means
// of aria-owns.
if (child && child->IsRelocated()) {
*aSkipSubtree = true;
return nullptr;
}
return child;
}
dom::AllChildrenIterator*
TreeWalker::PopState()
{
size_t length = mStateStack.Length();
mStateStack.RemoveElementAt(length - 1);
return mStateStack.IsEmpty() ? nullptr : &mStateStack[mStateStack.Length() - 1];
return mStateStack.IsEmpty() ? nullptr : &mStateStack.LastElement();
}
+51 -20
View File
@@ -33,63 +33,94 @@ public:
};
/**
* Constructor
* Used to navigate and create if needed the accessible children.
*/
explicit TreeWalker(Accessible* aContext);
/**
* Used to navigate the accessible children relative to the anchor.
*
* @param aContext [in] container accessible for the given node, used to
* define accessible context
* @param aNode [in] the node the search will be prepared relative to
* @param aAnchorNode [in] the node the search will be prepared relative to
* @param aFlags [in] flags (see enum above)
*/
TreeWalker(Accessible* aContext, nsIContent* aNode, uint32_t aFlags = 0);
TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags = eWalkCache);
~TreeWalker();
/**
* Return the next accessible.
* Clears the tree walker state and resets it to the given child within
* the anchor.
*/
bool Seek(nsIContent* aChildNode);
/**
* Return the next/prev accessible.
*
* @note Returned accessible is bound to the document, if the accessible is
* rejected during tree creation then the caller should be unbind it
* from the document.
*/
Accessible* Next();
Accessible* Next(nsIContent* aStopNode = nullptr);
Accessible* Prev();
Accessible* Context() const { return mContext; }
DocAccessible* Document() const { return mDoc; }
private:
TreeWalker();
TreeWalker(const TreeWalker&);
TreeWalker& operator =(const TreeWalker&);
struct ChildrenIterator {
ChildrenIterator(nsIContent* aNode, uint32_t aFilter) :
mDOMIter(aNode, aFilter), mARIAOwnsIdx(0) { }
dom::AllChildrenIterator mDOMIter;
uint32_t mARIAOwnsIdx;
};
nsIContent* Next(ChildrenIterator* aIter, Accessible** aAccessible = nullptr,
bool* aSkipSubtree = nullptr);
/**
* Return an accessible for the given node if any.
*/
Accessible* AccessibleFor(nsIContent* aNode, uint32_t aFlags,
bool* aSkipSubtree);
/**
* Create new state for the given node and push it on top of stack.
* Create new state for the given node and push it on top of stack / at bottom
* of stack.
*
* @note State stack is used to navigate up/down the DOM subtree during
* accessible children search.
*/
ChildrenIterator* PushState(nsIContent* aContent)
dom::AllChildrenIterator* PushState(nsIContent* aContent,
bool aStartAtBeginning)
{
return mStateStack.AppendElement(ChildrenIterator(aContent, mChildFilter));
return mStateStack.AppendElement(
dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
}
dom::AllChildrenIterator* PrependState(nsIContent* aContent,
bool aStartAtBeginning)
{
return mStateStack.InsertElementAt(0,
dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
}
/**
* Pop state from stack.
*/
ChildrenIterator* PopState();
dom::AllChildrenIterator* PopState();
DocAccessible* mDoc;
Accessible* mContext;
nsIContent* mAnchorNode;
AutoTArray<ChildrenIterator, 20> mStateStack;
AutoTArray<dom::AllChildrenIterator, 20> mStateStack;
uint32_t mARIAOwnsIdx;
int32_t mChildFilter;
uint32_t mFlags;
enum Phase {
eAtStart,
eAtDOM,
eAtARIAOwns,
eAtEnd
};
Phase mPhase;
};
} // namespace a11y
+7 -4
View File
@@ -1983,6 +1983,10 @@ Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent)
if (mParent->IsARIAHidden() || aria::HasDefinedARIAHidden(mContent))
SetARIAHidden(true);
mContextFlags |=
static_cast<uint32_t>((mParent->IsAlert() ||
mParent->IsInsideAlert())) & eInsideAlert;
}
// Accessible protected
@@ -2000,7 +2004,7 @@ Accessible::UnbindFromParent()
delete mBits.groupInfo;
mBits.groupInfo = nullptr;
mContextFlags &= ~eHasNameDependentParent;
mContextFlags &= ~eHasNameDependentParent & ~eInsideAlert;
}
////////////////////////////////////////////////////////////////////////////////
@@ -2542,10 +2546,9 @@ Accessible::LastRelease()
void
Accessible::CacheChildren()
{
DocAccessible* doc = Document();
NS_ENSURE_TRUE_VOID(doc);
NS_ENSURE_TRUE_VOID(Document());
TreeWalker walker(this, mContent);
TreeWalker walker(this);
Accessible* child = nullptr;
while ((child = walker.Next()) && AppendChild(child));
+11 -3
View File
@@ -568,6 +568,8 @@ public:
return mContent->IsAnyOfHTMLElements(nsGkAtoms::abbr, nsGkAtoms::acronym);
}
bool IsAlert() const { return HasGenericType(eAlert); }
bool IsApplication() const { return mType == eApplicationType; }
ApplicationAccessible* AsApplication();
@@ -946,6 +948,11 @@ public:
bool IsARIAHidden() const { return mContextFlags & eARIAHidden; }
void SetARIAHidden(bool aIsDefined);
/**
* Return true if the element is inside an alert.
*/
bool IsInsideAlert() const { return mContextFlags & eInsideAlert; }
protected:
virtual ~Accessible();
@@ -1034,8 +1041,9 @@ protected:
enum ContextFlags {
eHasNameDependentParent = 1 << 0, // Parent's name depends on this accessible.
eARIAHidden = 1 << 1,
eInsideAlert = 1 << 2,
eLastContextFlag = eARIAHidden
eLastContextFlag = eInsideAlert
};
protected:
@@ -1141,9 +1149,9 @@ protected:
static const uint8_t kChildrenFlagsBits = 2;
static const uint8_t kStateFlagsBits = 11;
static const uint8_t kContextFlagsBits = 2;
static const uint8_t kContextFlagsBits = 3;
static const uint8_t kTypeBits = 6;
static const uint8_t kGenericTypesBits = 14;
static const uint8_t kGenericTypesBits = 15;
/**
* Keep in sync with ChildrenFlags, StateFlags, ContextFlags, and AccTypes.
+30 -69
View File
@@ -1426,36 +1426,6 @@ if (!aNode->IsContent() || !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area))
return GetAccessible(aNode);
}
////////////////////////////////////////////////////////////////////////////////
// Accessible protected
void
DocAccessible::CacheChildren()
{
// Search for accessible children starting from the document element since
// some web pages tend to insert elements under it rather than document body.
dom::Element* rootElm = mDocumentNode->GetRootElement();
if (!rootElm)
return;
// Ignore last HTML:br, copied from HyperTextAccessible.
TreeWalker walker(this, rootElm);
Accessible* lastChild = nullptr;
while (Accessible* child = walker.Next()) {
if (lastChild)
AppendChild(lastChild);
lastChild = child;
}
if (lastChild) {
if (lastChild->IsHTMLBr())
Document()->UnbindFromDocument(lastChild);
else
AppendChild(lastChild);
}
}
////////////////////////////////////////////////////////////////////////////////
// Protected members
@@ -1791,22 +1761,16 @@ DocAccessible::UpdateTreeOnInsertion(Accessible* aContainer)
// Check to see if change occurred inside an alert, and fire an EVENT_ALERT
// if it did.
if (!(updateFlags & eAlertAccessible)) {
// XXX: tree traversal is perf issue, accessible should know if they are
// children of alert accessible to avoid this.
if (!(updateFlags & eAlertAccessible) &&
(aContainer->IsAlert() || aContainer->IsInsideAlert())) {
Accessible* ancestor = aContainer;
while (ancestor) {
if (ancestor->ARIARole() == roles::ALERT) {
do {
if (ancestor->IsAlert()) {
FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
break;
}
// Don't climb above this document.
if (ancestor == this)
break;
ancestor = ancestor->Parent();
}
while ((ancestor = ancestor->Parent()));
}
MaybeNotifyOfValueChange(aContainer);
@@ -1839,34 +1803,9 @@ DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNod
if (child) {
updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
} else {
// aChildNode may not coorespond to a particular accessible, to handle
// this we go through all the children of aContainer. Then if a child
// has aChildNode as an ancestor, or does not have the node for
// aContainer as an ancestor remove that child of aContainer. Note that
// when we are called aChildNode may already have been removed from the DOM
// so we can't expect it to have a parent or what was it's parent to have
// it as a child.
nsINode* containerNode = aContainer->GetNode();
for (uint32_t idx = 0; idx < aContainer->ContentChildCount();) {
Accessible* child = aContainer->ContentChildAt(idx);
// If accessible doesn't have its own content then we assume parent
// will handle its update. If child is DocAccessible then we don't
// handle updating it here either.
if (!child->HasOwnContent() || child->IsDoc()) {
idx++;
continue;
}
nsINode* childNode = child->GetContent();
while (childNode != aChildNode && childNode != containerNode &&
(childNode = childNode->GetParentNode()));
if (childNode != containerNode) {
updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
} else {
idx++;
}
TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
while (Accessible* child = walker.Next()) {
updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
}
}
@@ -2062,6 +2001,7 @@ DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
MoveChild(child, insertIdx);
children->InsertElementAt(arrayIdx, child);
arrayIdx++;
insertIdx = child->IndexInParent() + 1;
} else if (SeizeChild(aOwner, child, insertIdx)) {
children->InsertElementAt(arrayIdx, child);
@@ -2088,6 +2028,12 @@ DocAccessible::SeizeChild(Accessible* aNewParent, Accessible* aChild,
int32_t oldIdxInParent = aChild->IndexInParent();
#ifdef A11Y_LOG
logging::TreeInfo("aria owns seize child", 0,
"old parent", oldParent, "new parent", aNewParent,
"child", aChild, nullptr);
#endif
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(oldParent);
RefPtr<AccMutationEvent> hideEvent = new AccHideEvent(aChild, false);
reorderEvent->AddSubMutationEvent(hideEvent);
@@ -2103,6 +2049,11 @@ DocAccessible::SeizeChild(Accessible* aNewParent, Accessible* aChild,
isReinserted = aNewParent->InsertChildAt(aIdxInParent, aChild);
}
#ifdef A11Y_LOG
logging::TreeInfo("aria owns seize child: new parent tree after",
logging::eVerbose, aNewParent);
#endif
if (!isReinserted) {
AutoTreeMutation mut(oldParent);
oldParent->InsertChildAt(oldIdxInParent, aChild);
@@ -2141,10 +2092,20 @@ DocAccessible::MoveChild(Accessible* aChild, int32_t aIdxInParent)
RefPtr<AccMutationEvent> hideEvent = new AccHideEvent(aChild, false);
reorderEvent->AddSubMutationEvent(hideEvent);
#ifdef A11Y_LOG
logging::TreeInfo("aria owns move child", 0,
"parent", parent, "child", aChild, nullptr);
#endif
AutoTreeMutation mut(parent);
parent->MoveChild(aIdxInParent, aChild);
aChild->SetRelocated(true);
#ifdef A11Y_LOG
logging::TreeInfo("aria owns move child: parent tree after",
logging::eVerbose, parent);
#endif
FireDelayedEvent(hideEvent);
RefPtr<AccMutationEvent> showEvent = new AccShowEvent(aChild);
+5 -3
View File
@@ -291,6 +291,11 @@ public:
}
return nullptr;
}
uint32_t ARIAOwnedCount(Accessible* aParent) const
{
nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
return children ? children->Length() : 0;
}
/**
* Return true if the given ID is referred by relation attribute.
@@ -359,9 +364,6 @@ protected:
void LastRelease();
// Accessible
virtual void CacheChildren() override;
// DocAccessible
virtual nsresult AddEventListeners();
virtual nsresult RemoveEventListeners();
@@ -1936,31 +1936,6 @@ HyperTextAccessible::RelationByType(RelationType aType)
return rel;
}
void
HyperTextAccessible::CacheChildren()
{
// Trailing HTML br element don't play any difference. We don't need to expose
// it to AT (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=899433#c16
// for details).
TreeWalker walker(this, mContent);
Accessible* child = nullptr;
Accessible* lastChild = nullptr;
while ((child = walker.Next())) {
if (lastChild)
AppendChild(lastChild);
lastChild = child;
}
if (lastChild) {
if (lastChild->IsHTMLBr())
Document()->UnbindFromDocument(lastChild);
else
AppendChild(lastChild);
}
}
////////////////////////////////////////////////////////////////////////////////
// HyperTextAccessible public static
-1
View File
@@ -434,7 +434,6 @@ protected:
// Accessible
virtual ENameValueFlag NativeName(nsString& aName) override;
virtual void CacheChildren() override;
// HyperTextAccessible
+14
View File
@@ -126,9 +126,23 @@ function dumpTree(aId, aMsg)
}
}
function dumpDOMTreeIntl(node, indent)
{
dump(indent + prettyName(node) + "\n");
var children = node.childNodes;
for (var i = 0; i < children.length; i++) {
var child = children.item(i);
dumpDOMTreeIntl(child, indent + " ");
}
}
dump(aMsg + "\n");
var root = getAccessible(aId);
dumpTreeIntl(root, " ");
dump("DOM tree:\n");
dumpDOMTreeIntl(getNode(aId), " ");
}
/**
@@ -62,6 +62,7 @@ function editableTextTest(aID)
function setTextContentsInvoke()
{
dump(`\nsetTextContents '${aValue}'\n`);
var acc = getAccessible(aID, nsIAccessibleEditableText);
acc.setTextContents(aValue);
}
@@ -84,6 +85,7 @@ function editableTextTest(aID)
function insertTextInvoke()
{
dump(`\ninsertText '${aStr}' at ${aPos} pos\n`);
var acc = getAccessible(aID, nsIAccessibleEditableText);
acc.insertText(aStr, aPos);
}
@@ -153,6 +153,7 @@
var gQueue = null;
//gA11yEventDumpToConsole = true; // debuging
//enableLogging("tree,verbose");
function doTests()
{
@@ -441,12 +441,81 @@
}
}
function rearrangeARIAOwns(aContainer, aAttr, aIdList, aRoleList)
{
this.eventSeq = [];
for (var id of aIdList) {
this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id)));
this.eventSeq.push(new invokerChecker(EVENT_SHOW, getNode(id)));
}
this.eventSeq.push(new invokerChecker(EVENT_REORDER, getNode(aContainer)));
this.invoke = function rearrangeARIAOwns_invoke()
{
getNode(aContainer).setAttribute("aria-owns", aAttr);
}
this.finalCheck = function rearrangeARIAOwns_finalCheck()
{
var tree = { SECTION: [ ] };
for (var role of aRoleList) {
var ch = {};
ch[role] = [];
tree["SECTION"].push(ch);
}
testAccessibleTree(aContainer, tree);
}
this.getID = function rearrangeARIAOwns_getID()
{
return `Rearrange @aria-owns attribute to '${aAttr}'`;
}
}
function removeNotARIAOwnedEl(aContainer, aChild)
{
this.eventSeq = [
new invokerChecker(EVENT_REORDER, aContainer)
];
this.invoke = function removeNotARIAOwnedEl_invoke()
{
dumpTree(aContainer, "before");
var tree = {
SECTION: [
{ TEXT_LEAF: [ ] },
{ GROUPING: [ ] }
]
};
testAccessibleTree(aContainer, tree);
getNode(aContainer).removeChild(getNode(aChild));
}
this.finalCheck = function removeNotARIAOwnedEl_finalCheck()
{
dumpTree(aContainer, "after");
var tree = {
SECTION: [
{ GROUPING: [ ] }
]
};
testAccessibleTree(aContainer, tree);
}
this.getID = function removeNotARIAOwnedEl_getID()
{
return `remove not ARIA owned child`;
}
}
////////////////////////////////////////////////////////////////////////////
// Test
////////////////////////////////////////////////////////////////////////////
//gA11yEventDumpToConsole = true;
//enableLogging("tree"); // debug stuff
//enableLogging("tree,verbose"); // debug stuff
var gQueue = null;
@@ -474,6 +543,18 @@
// test4
gQueue.push(new showHiddenElement());
// test5
gQueue.push(new rearrangeARIAOwns(
"t5_container", "t5_checkbox t5_radio t5_button",
[ "t5_checkbox", "t5_radio", "t5_button" ],
[ "CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON" ]));
gQueue.push(new rearrangeARIAOwns(
"t5_container", "t5_radio t5_button t5_checkbox",
[ "t5_radio", "t5_button" ],
[ "RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON" ]));
gQueue.push(new removeNotARIAOwnedEl("t6_container", "t6_span"));
gQueue.invoke(); // SimpleTest.finish() will be called in the end
}
@@ -515,6 +596,17 @@
<div id="t4_child1" style="display:none" role="checkbox"></div>
<div id="t4_child2" role="radio"></div>
</div>
<div id="t5_container">
<div role="button" id="t5_button"></div>
<div role="checkbox" id="t5_checkbox"></div>
<div role="radio" id="t5_radio"></div>
</div>
<div id="t6_container" aria-owns="t6_fake">
<span id="t6_span">hey</span>
</div>
<div id="t6_fake" role="group"></div>
</body>
</html>
+1
View File
@@ -19,6 +19,7 @@ XULAlertAccessible::
XULAlertAccessible(nsIContent* aContent, DocAccessible* aDoc) :
AccessibleWrap(aContent, aDoc)
{
mGenericTypes |= eAlert;
}
XULAlertAccessible::~XULAlertAccessible()
+6 -11
View File
@@ -29,17 +29,12 @@ function serializeServiceWorkerInfo(aServiceWorkerInfo) {
let result = {};
Object.keys(aServiceWorkerInfo).forEach(property => {
if (typeof aServiceWorkerInfo[property] == "function") {
return;
}
if (property === "principal") {
result.principal = {
origin: aServiceWorkerInfo.principal.origin,
originAttributes: aServiceWorkerInfo.principal.originAttributes
};
return;
}
result.principal = {
origin: aServiceWorkerInfo.principal.originNoSuffix,
originAttributes: aServiceWorkerInfo.principal.originAttributes
};
["scope", "scriptSpec"].forEach(property => {
result[property] = aServiceWorkerInfo[property];
});
+5 -2
View File
@@ -32,7 +32,7 @@ Cu.import("resource://gre/modules/PermissionsTable.jsm");
var permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
let permissionSpecificChecker = {};
var permissionSpecificChecker = {};
XPCOMUtils.defineLazyServiceGetter(this,
"TelephonyService",
@@ -413,7 +413,10 @@ ContentPermissionPrompt.prototype = {
type: type,
permissions: permissions,
id: requestId,
origin: principal.origin,
// This system app uses the origin from permission events to
// compare against the mozApp.origin of app windows, so we
// are not concerned with origin suffixes here (appId, etc).
origin: principal.originNoSuffix,
isApp: isApp,
remember: remember,
isGranted: isGranted,
+1 -1
View File
@@ -23,7 +23,7 @@ MailtoProtocolHandler.prototype = {
Ci.nsIProtocolHandler.URI_NOAUTH |
Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE |
Ci.nsIProtocolHandler.URI_DOES_NOT_RETURN_DATA,
allowPort: function() false,
allowPort: () => false,
newURI: function Proto_newURI(aSpec, aOriginCharset) {
let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+3 -3
View File
@@ -62,13 +62,13 @@ function hookScreen(window) {
};
Object.defineProperty(screen, 'width', {
get: function () GlobalSimulatorScreen.width
get: () => GlobalSimulatorScreen.width
});
Object.defineProperty(screen, 'height', {
get: function () GlobalSimulatorScreen.height
get: () => GlobalSimulatorScreen.height
});
Object.defineProperty(screen, 'mozOrientation', {
get: function () GlobalSimulatorScreen.mozOrientation
get: () => GlobalSimulatorScreen.mozOrientation
});
}
+1 -1
View File
@@ -32,7 +32,7 @@ SmsProtocolHandler.prototype = {
Ci.nsIProtocolHandler.URI_NOAUTH |
Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE |
Ci.nsIProtocolHandler.URI_DOES_NOT_RETURN_DATA,
allowPort: function() false,
allowPort: () => false,
newURI: function Proto_newURI(aSpec, aOriginCharset) {
let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+1 -1
View File
@@ -31,7 +31,7 @@ TelProtocolHandler.prototype = {
Ci.nsIProtocolHandler.URI_NOAUTH |
Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE |
Ci.nsIProtocolHandler.URI_DOES_NOT_RETURN_DATA,
allowPort: function() false,
allowPort: () => false,
newURI: function Proto_newURI(aSpec, aOriginCharset) {
let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+5 -3
View File
@@ -21,10 +21,10 @@ const { EventEmitter } = Cu.import("resource://gre/modules/devtools/shared/event
// have trailing newlines. And note that registerLogHandler actually registers
// an error handler, despite its name.
Subprocess.registerLogHandler(
function(s) console.error("subprocess: " + s.trim())
s => console.error("subprocess: " + s.trim())
);
Subprocess.registerDebugHandler(
function(s) console.debug("subprocess: " + s.trim())
s => console.debug("subprocess: " + s.trim())
);
function SimulatorProcess(options) {
@@ -38,7 +38,9 @@ function SimulatorProcess(options) {
SimulatorProcess.prototype = {
// check if b2g is running
get isRunning() !!this.process,
get isRunning() {
return !!this.process;
},
/**
* Start the process and connect the debugger client.
+8
View File
@@ -5158,6 +5158,14 @@ function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
menuItem.addEventListener("command", onViewToolbarCommand, false);
}
let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
let removeFromToolbar = popup.querySelector(".customize-context-removeFromToolbar");
// View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
if (!moveToPanel || !removeFromToolbar) {
return;
}
// The explicitOriginalTarget can be a nested child element of a toolbaritem.
let toolbarItem = aEvent.explicitOriginalTarget;
+16 -4
View File
@@ -552,12 +552,18 @@ nsContextMenu.prototype = {
LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
},
inspectNode: function CM_inspectNode() {
inspectNode: function() {
let {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
let tt = devtools.TargetFactory.forTab(gBrowser.selectedTab);
return gDevTools.showToolbox(tt, "inspector").then(function(toolbox) {
let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
return gDevTools.showToolbox(target, "inspector").then(toolbox => {
let inspector = toolbox.getCurrentPanel();
// new-node-front tells us when the node has been selected, whether the
// browser is remote or not.
let onNewNode = inspector.selection.once("new-node-front");
if (this.isRemote) {
this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target});
inspector.walker.findInspectingNode().then(nodeFront => {
@@ -566,7 +572,13 @@ nsContextMenu.prototype = {
} else {
inspector.selection.setNode(this.target, "browser-context-menu");
}
}.bind(this));
return onNewNode.then(() => {
// Now that the node has been selected, wait until the inspector is
// fully updated.
return inspector.once("inspector-updated");
});
});
},
// Set various context menu attributes based on the state of the world.
@@ -106,6 +106,24 @@ AppendDistroSearchDirs(nsIProperties* aDirSvc, nsCOMArray<nsIFile> &array)
localePlugins->AppendNative(NS_LITERAL_CSTRING("locale"));
nsCString defLocale;
rv = prefs->GetCharPref("distribution.searchplugins.defaultLocale",
getter_Copies(defLocale));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFile> defLocalePlugins;
rv = localePlugins->Clone(getter_AddRefs(defLocalePlugins));
if (NS_SUCCEEDED(rv)) {
defLocalePlugins->AppendNative(defLocale);
rv = defLocalePlugins->Exists(&exists);
if (NS_SUCCEEDED(rv) && exists)
array.AppendObject(defLocalePlugins);
return; // all done
}
}
// we didn't have a defaultLocale, use the user agent locale
nsCString locale;
nsCOMPtr<nsIPrefLocalizedString> prefString;
rv = prefs->GetComplexValue("general.useragent.locale",
@@ -133,23 +151,6 @@ AppendDistroSearchDirs(nsIProperties* aDirSvc, nsCOMArray<nsIFile> &array)
}
}
}
// we didn't append the locale dir - try the default one
nsCString defLocale;
rv = prefs->GetCharPref("distribution.searchplugins.defaultLocale",
getter_Copies(defLocale));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFile> defLocalePlugins;
rv = localePlugins->Clone(getter_AddRefs(defLocalePlugins));
if (NS_SUCCEEDED(rv)) {
defLocalePlugins->AppendNative(defLocale);
rv = defLocalePlugins->Exists(&exists);
if (NS_SUCCEEDED(rv) && exists)
array.AppendObject(defLocalePlugins);
}
}
}
}
+111 -41
View File
@@ -15,6 +15,7 @@ const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
@@ -70,6 +71,12 @@ DistributionCustomizer.prototype = {
return this._locale;
},
get _language() {
let language = this._locale.split("-")[0];
this.__defineGetter__("_language", () => language);
return this._language;
},
get _prefSvc() {
let svc = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
@@ -112,6 +119,8 @@ DistributionCustomizer.prototype = {
if (keys.indexOf(key + "." + this._locale) >= 0) {
key += "." + this._locale;
} else if (keys.indexOf(key + "." + this._language) >= 0) {
key += "." + this._language;
}
if (!items[itemIndex])
@@ -309,76 +318,124 @@ DistributionCustomizer.prototype = {
if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"]))
return this._checkCustomizationComplete();
let defaults = this._prefSvc.getDefaultBranch(null);
let defaults = new Preferences({defaultBranch: true});
// Global really contains info we set as prefs. They're only
// separate because they are "special" (read: required)
defaults.setCharPref("distribution.id", this._ini.getString("Global", "id"));
defaults.setCharPref("distribution.version",
this._ini.getString("Global", "version"));
defaults.set("distribution.id", this._ini.getString("Global", "id"));
defaults.set("distribution.version", this._ini.getString("Global", "version"));
let partnerAbout = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
let partnerAbout;
try {
if (globalPrefs["about." + this._locale]) {
partnerAbout.data = this._ini.getString("Global", "about." + this._locale);
partnerAbout = this._ini.getString("Global", "about." + this._locale);
} else if (globalPrefs["about." + this._language]) {
partnerAbout = this._ini.getString("Global", "about." + this._language);
} else {
partnerAbout.data = this._ini.getString("Global", "about");
partnerAbout = this._ini.getString("Global", "about");
}
defaults.setComplexValue("distribution.about",
Ci.nsISupportsString, partnerAbout);
defaults.set("distribution.about", partnerAbout);
} catch (e) {
/* ignore bad prefs due to bug 895473 and move on */
Cu.reportError(e);
}
var usedPreferences = [];
if (sections["Preferences-" + this._locale]) {
for (let key of enumerate(this._ini.getKeys("Preferences-" + this._locale))) {
try {
let value = this._ini.getString("Preferences-" + this._locale, key);
if (value) {
Preferences.set(key, parseValue(value));
}
usedPreferences.push(key);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
if (sections["Preferences-" + this._language]) {
for (let key of enumerate(this._ini.getKeys("Preferences-" + this._language))) {
if (usedPreferences.indexOf(key) > -1) {
continue;
}
try {
let value = this._ini.getString("Preferences-" + this._language, key);
if (value) {
Preferences.set(key, parseValue(value));
}
usedPreferences.push(key);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
if (sections["Preferences"]) {
for (let key of enumerate(this._ini.getKeys("Preferences"))) {
if (usedPreferences.indexOf(key) > -1) {
continue;
}
try {
let value = eval(this._ini.getString("Preferences", key));
switch (typeof value) {
case "boolean":
defaults.setBoolPref(key, value);
break;
case "number":
defaults.setIntPref(key, value);
break;
case "string":
defaults.setCharPref(key, value);
break;
case "undefined":
defaults.setCharPref(key, value);
break;
let value = this._ini.getString("Preferences", key);
if (value) {
value = value.replace(/%LOCALE%/g, this._locale);
value = value.replace(/%LANGUAGE%/g, this._language);
Preferences.set(key, parseValue(value));
}
} catch (e) { /* ignore bad prefs and move on */ }
}
}
// We eval() the localizable prefs as well (even though they'll
// always get set as a string) to keep the INI format consistent:
// string prefs always need to be in quotes
let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"].
createInstance(Ci.nsIPrefLocalizedString);
if (sections["LocalizablePreferences"]) {
for (let key of enumerate(this._ini.getKeys("LocalizablePreferences"))) {
try {
let value = eval(this._ini.getString("LocalizablePreferences", key));
value = value.replace(/%LOCALE%/g, this._locale);
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
var usedLocalizablePreferences = [];
if (sections["LocalizablePreferences-" + this._locale]) {
for (let key of enumerate(this._ini.getKeys("LocalizablePreferences-" + this._locale))) {
try {
let value = eval(this._ini.getString("LocalizablePreferences-" + this._locale, key));
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
let value = this._ini.getString("LocalizablePreferences-" + this._locale, key);
if (value) {
value = parseValue(value);
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
}
usedLocalizablePreferences.push(key);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
if (sections["LocalizablePreferences-" + this._language]) {
for (let key of enumerate(this._ini.getKeys("LocalizablePreferences-" + this._language))) {
if (usedLocalizablePreferences.indexOf(key) > -1) {
continue;
}
try {
let value = this._ini.getString("LocalizablePreferences-" + this._language, key);
if (value) {
value = parseValue(value);
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
}
usedLocalizablePreferences.push(key);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
if (sections["LocalizablePreferences"]) {
for (let key of enumerate(this._ini.getKeys("LocalizablePreferences"))) {
if (usedLocalizablePreferences.indexOf(key) > -1) {
continue;
}
try {
let value = this._ini.getString("LocalizablePreferences", key);
if (value) {
value = parseValue(value);
value = value.replace(/%LOCALE%/g, this._locale);
value = value.replace(/%LANGUAGE%/g, this._language);
localizedStr.data = "data:text/plain," + key + "=" + value;
}
defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
} catch (e) { /* ignore bad prefs and move on */ }
}
}
@@ -397,6 +454,19 @@ DistributionCustomizer.prototype = {
}
};
function parseValue(value) {
try {
value = JSON.parse(value);
} catch (e) {
// JSON.parse catches numbers and booleans.
// Anything else, we assume is a string.
// Remove the quotes that aren't needed anymore.
value = value.replace(/^"/, "");
value = value.replace(/"$/, "");
}
return value;
}
function* enumerate(UTF8Enumerator) {
while (UTF8Enumerator.hasMore())
yield UTF8Enumerator.getNext();
+9
View File
@@ -48,5 +48,14 @@ EXTRA_JS_MODULES += [
'distribution.js',
]
BROWSER_CHROME_MANIFESTS += [
'tests/browser/browser.ini'
]
XPCSHELL_TESTS_MANIFESTS += [
'tests/unit/xpcshell.ini'
]
with Files('controlcenter/**'):
BUG_COMPONENT = ('Firefox', 'General')
+25 -39
View File
@@ -32,13 +32,9 @@ const nsIDOMWindow = Components.interfaces.nsIDOMWindow;
const nsIFileURL = Components.interfaces.nsIFileURL;
const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor;
const nsINetUtil = Components.interfaces.nsINetUtil;
const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
const nsISupportsString = Components.interfaces.nsISupportsString;
const nsIURIFixup = Components.interfaces.nsIURIFixup;
const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
const nsIWindowMediator = Components.interfaces.nsIWindowMediator;
const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
const nsIWebNavigationInfo = Components.interfaces.nsIWebNavigationInfo;
const nsICommandLineValidator = Components.interfaces.nsICommandLineValidator;
@@ -60,12 +56,11 @@ function shouldLoadURI(aURI) {
function resolveURIInternal(aCmdLine, aArgument) {
var uri = aCmdLine.resolveURI(aArgument);
var urifixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
.getService(nsIURIFixup);
var uriFixup = Services.uriFixup;
if (!(uri instanceof nsIFileURL)) {
return urifixup.createFixupURI(aArgument,
urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS);
return uriFixup.createFixupURI(aArgument,
uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS);
}
try {
@@ -80,7 +75,7 @@ function resolveURIInternal(aCmdLine, aArgument) {
// doesn't exist. Try URI fixup heuristics: see bug 290782.
try {
uri = urifixup.createFixupURI(aArgument, 0);
uri = uriFixup.createFixupURI(aArgument, 0);
}
catch (e) {
Components.utils.reportError(e);
@@ -182,9 +177,6 @@ function getPostUpdateOverridePage(defaultOverridePage) {
const NO_EXTERNAL_URIS = 1;
function openWindow(parent, url, target, features, args, noExternalArgs) {
var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(nsIWindowWatcher);
if (noExternalArgs == NO_EXTERNAL_URIS) {
// Just pass in the defaultArgs directly
var argstring;
@@ -194,7 +186,7 @@ function openWindow(parent, url, target, features, args, noExternalArgs) {
argstring.data = args;
}
return wwatch.openWindow(parent, url, target, features, argstring);
return Services.ww.openWindow(parent, url, target, features, argstring);
}
// Pass an array to avoid the browser "|"-splitting behavior.
@@ -231,7 +223,7 @@ function openWindow(parent, url, target, features, args, noExternalArgs) {
argArray.AppendElement(null); // postData
argArray.AppendElement(null); // allowThirdPartyFixup
return wwatch.openWindow(parent, url, target, features, argArray);
return Services.ww.openWindow(parent, url, target, features, argArray);
}
function openPreferences() {
@@ -242,8 +234,12 @@ function openPreferences() {
if (win) {
win.focus();
} else {
openWindow(null, url, "_blank", features);
}
return Services.ww.openWindow(null, gBrowserContentHandler.chromeURL,
"_blank",
"chrome,dialog=no,all" +
gBrowserContentHandler.getFeatures(cmdLine),
sa);
}
}
function getMostRecentWindow(aType) {
@@ -280,14 +276,11 @@ function doSearch(searchTerm, cmdLine) {
// XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
// preferences, but need nsIBrowserDOMWindow extensions
var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(nsIWindowWatcher);
return wwatch.openWindow(null, gBrowserContentHandler.chromeURL,
"_blank",
"chrome,dialog=no,all" +
gBrowserContentHandler.getFeatures(cmdLine),
sa);
return Services.ww.openWindow(null, gBrowserContentHandler.chromeURL,
"_blank",
"chrome,dialog=no,all" +
gBrowserContentHandler.getFeatures(cmdLine),
sa);
}
function nsBrowserContentHandler() {
@@ -312,9 +305,7 @@ nsBrowserContentHandler.prototype = {
return this.mChromeURL;
}
var prefb = Components.classes["@mozilla.org/preferences-service;1"]
.getService(nsIPrefBranch);
this.mChromeURL = prefb.getCharPref("browser.chromeURL");
this.mChromeURL = Services.prefs.getCharPref("browser.chromeURL");
return this.mChromeURL;
},
@@ -338,7 +329,7 @@ nsBrowserContentHandler.prototype = {
var uriparam;
try {
while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
var uri = resolveURIInternal(cmdLine, uriparam);
let uri = resolveURIInternal(cmdLine, uriparam);
if (!shouldLoadURI(uri))
continue;
openWindow(null, this.chromeURL, "_blank",
@@ -353,7 +344,7 @@ nsBrowserContentHandler.prototype = {
try {
while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
var uri = resolveURIInternal(cmdLine, uriparam);
let uri = resolveURIInternal(cmdLine, uriparam);
handURIToExistingBrowser(uri, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine);
cmdLine.preventDefault = true;
}
@@ -426,12 +417,10 @@ nsBrowserContentHandler.prototype = {
var fileParam = cmdLine.handleFlagWithParam("file", false);
if (fileParam) {
var file = cmdLine.resolveFile(fileParam);
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var uri = ios.newFileURI(file);
openWindow(null, this.chromeURL, "_blank",
var fileURI = Services.io.newFileURI(file);
openWindow(null, this.chromeURL, "_blank",
"chrome,dialog=no,all" + this.getFeatures(cmdLine),
uri.spec);
fileURI.spec);
cmdLine.preventDefault = true;
}
@@ -468,8 +457,7 @@ nsBrowserContentHandler.prototype = {
/* nsIBrowserHandler */
get defaultArgs() {
var prefb = Components.classes["@mozilla.org/preferences-service;1"]
.getService(nsIPrefBranch);
var prefb = Services.prefs;
if (!gFirstWindow) {
gFirstWindow = true;
@@ -685,9 +673,7 @@ nsDefaultCommandLineHandler.prototype = {
if (!this._haveProfile) {
try {
// This will throw when a profile has not been selected.
var fl = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
var dir = fl.get("ProfD", Components.interfaces.nsILocalFile);
var dir = Services.dirsvc.get("ProfD", Components.interfaces.nsILocalFile);
this._haveProfile = true;
}
catch (e) {
+15 -14
View File
@@ -91,20 +91,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
"resource://gre/modules/LoginManagerParent.jsm");
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
return Services.strings.createBundle('chrome://branding/locale/brand.properties');
});
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings.createBundle('chrome://browser/locale/browser.properties');
});
XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
"resource:///modules/FormValidationHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
"resource://gre/modules/AddonWatcher.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
@@ -132,9 +118,24 @@ if (AppConstants.MOZ_CRASHREPORTER) {
"resource:///modules/ContentCrashHandlers.jsm");
}
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
return Services.strings.createBundle('chrome://branding/locale/brand.properties');
});
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings.createBundle('chrome://browser/locale/browser.properties');
});
XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
"resource:///modules/FormValidationHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
"resource:///modules/ReaderParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
"resource://gre/modules/AddonWatcher.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
@@ -0,0 +1,5 @@
{
"extends": [
"../../../../testing/mochitest/browser.eslintrc"
]
}
@@ -0,0 +1,3 @@
[DEFAULT]
[browser_bug538331.js]
@@ -0,0 +1,426 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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 PREF_POSTUPDATE = "app.update.postupdate";
const PREF_MSTONE = "browser.startup.homepage_override.mstone";
const PREF_OVERRIDE_URL = "startup.homepage_override_url";
const DEFAULT_PREF_URL = "http://pref.example.com/";
const DEFAULT_UPDATE_URL = "http://example.com/";
const XML_EMPTY = "<?xml version=\"1.0\"?><updates xmlns=" +
"\"http://www.mozilla.org/2005/app-update\"></updates>";
const XML_PREFIX = "<updates xmlns=\"http://www.mozilla.org/2005/app-update\"" +
"><update appVersion=\"1.0\" buildID=\"20080811053724\" " +
"channel=\"nightly\" displayVersion=\"Version 1.0\" " +
"extensionVersion=\"1.0\" installDate=\"1238441400314\" " +
"isCompleteUpdate=\"true\" name=\"Update Test 1.0\" " +
"serviceURL=\"https://example.com/\" showNeverForVersion=" +
"\"false\" showPrompt=\"false\" type=" +
"\"minor\" version=\"version 1.0\" detailsURL=" +
"\"http://example.com/\" previousAppVersion=\"1.0\" " +
"statusText=\"The Update was successfully installed\" " +
"foregroundDownload=\"true\"";
const XML_SUFFIX = "><patch type=\"complete\" URL=\"http://example.com/\" " +
"hashFunction=\"MD5\" hashValue=" +
"\"6232cd43a1c77e30191c53a329a3f99d\" size=\"775\" " +
"selected=\"true\" state=\"succeeded\"/></update></updates>";
// nsBrowserContentHandler.js defaultArgs tests
const BCH_TESTS = [
{
description: "no mstone change and no update",
noPostUpdatePref: true,
noMstoneChange: true
}, {
description: "mstone changed and no update",
noPostUpdatePref: true,
prefURL: DEFAULT_PREF_URL
}, {
description: "no mstone change and update with 'showURL' for actions",
actions: "showURL",
noMstoneChange: true
}, {
description: "update without actions",
prefURL: DEFAULT_PREF_URL
}, {
description: "update with 'showURL' for actions",
actions: "showURL",
prefURL: DEFAULT_PREF_URL
}, {
description: "update with 'showURL' for actions and openURL",
actions: "showURL",
openURL: DEFAULT_UPDATE_URL
}, {
description: "update with 'showURL showAlert' for actions",
actions: "showAlert showURL",
prefURL: DEFAULT_PREF_URL
}, {
description: "update with 'showAlert showURL' for actions and openURL",
actions: "showAlert showURL",
openURL: DEFAULT_UPDATE_URL
}, {
description: "update with 'showURL showNotification' for actions",
actions: "showURL showNotification",
prefURL: DEFAULT_PREF_URL
}, {
description: "update with 'showNotification showURL' for actions and " +
"openURL",
actions: "showNotification showURL",
openURL: DEFAULT_UPDATE_URL
}, {
description: "update with 'showAlert showURL showNotification' for actions",
actions: "showAlert showURL showNotification",
prefURL: DEFAULT_PREF_URL
}, {
description: "update with 'showNotification showURL showAlert' for " +
"actions and openURL",
actions: "showNotification showURL showAlert",
openURL: DEFAULT_UPDATE_URL
}, {
description: "update with 'showAlert' for actions",
actions: "showAlert"
}, {
description: "update with 'showAlert showNotification' for actions",
actions: "showAlert showNotification"
}, {
description: "update with 'showNotification' for actions",
actions: "showNotification"
}, {
description: "update with 'showNotification showAlert' for actions",
actions: "showNotification showAlert"
}, {
description: "update with 'silent' for actions",
actions: "silent"
}, {
description: "update with 'silent showURL showAlert showNotification' " +
"for actions and openURL",
actions: "silent showURL showAlert showNotification"
}
];
var gOriginalMStone;
var gOriginalOverrideURL;
this.__defineGetter__("gBG", function() {
delete this.gBG;
return this.gBG = Cc["@mozilla.org/browser/browserglue;1"].
getService(Ci.nsIObserver);
});
function test()
{
waitForExplicitFinish();
// Reset the startup page pref since it may have been set by other tests
// and we will assume it is default.
Services.prefs.clearUserPref('browser.startup.page');
if (gPrefService.prefHasUserValue(PREF_MSTONE)) {
gOriginalMStone = gPrefService.getCharPref(PREF_MSTONE);
}
if (gPrefService.prefHasUserValue(PREF_OVERRIDE_URL)) {
gOriginalOverrideURL = gPrefService.getCharPref(PREF_OVERRIDE_URL);
}
testDefaultArgs();
}
var gWindowCatcher = {
windowsOpen: 0,
finishCalled: false,
start: function() {
Services.ww.registerNotification(this);
},
finish: function(aFunc) {
Services.ww.unregisterNotification(this);
this.finishFunc = aFunc;
if (this.windowsOpen > 0)
return;
this.finishFunc();
},
closeWindow: function (win) {
info("window catcher closing window: " + win.document.documentURI);
win.close();
this.windowsOpen--;
if (this.finishFunc) {
this.finish(this.finishFunc);
}
},
windowLoad: function (win) {
executeSoon(this.closeWindow.bind(this, win));
},
observe: function(subject, topic, data) {
if (topic != "domwindowopened")
return;
this.windowsOpen++;
let win = subject.QueryInterface(Ci.nsIDOMWindow);
info("window catcher caught window opening: " + win.document.documentURI);
win.addEventListener("load", function () {
win.removeEventListener("load", arguments.callee, false);
gWindowCatcher.windowLoad(win);
}, false);
}
};
function finish_test()
{
// Reset browser.startup.homepage_override.mstone to the original value or
// clear it if it didn't exist.
if (gOriginalMStone) {
gPrefService.setCharPref(PREF_MSTONE, gOriginalMStone);
} else if (gPrefService.prefHasUserValue(PREF_MSTONE)) {
gPrefService.clearUserPref(PREF_MSTONE);
}
// Reset startup.homepage_override_url to the original value or clear it if
// it didn't exist.
if (gOriginalOverrideURL) {
gPrefService.setCharPref(PREF_OVERRIDE_URL, gOriginalOverrideURL);
} else if (gPrefService.prefHasUserValue(PREF_OVERRIDE_URL)) {
gPrefService.clearUserPref(PREF_OVERRIDE_URL);
}
writeUpdatesToXMLFile(XML_EMPTY);
reloadUpdateManagerData();
finish();
}
// Test the defaultArgs returned by nsBrowserContentHandler after an update
function testDefaultArgs()
{
// Clear any pre-existing override in defaultArgs that are hanging around.
// This will also set the browser.startup.homepage_override.mstone preference
// if it isn't already set.
Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs;
let originalMstone = gPrefService.getCharPref(PREF_MSTONE);
gPrefService.setCharPref(PREF_OVERRIDE_URL, DEFAULT_PREF_URL);
writeUpdatesToXMLFile(XML_EMPTY);
reloadUpdateManagerData();
for (let i = 0; i < BCH_TESTS.length; i++) {
let test = BCH_TESTS[i];
ok(true, "Test nsBrowserContentHandler " + (i + 1) + ": " + test.description);
if (test.actions) {
let actionsXML = " actions=\"" + test.actions + "\"";
if (test.openURL) {
actionsXML += " openURL=\"" + test.openURL + "\"";
}
writeUpdatesToXMLFile(XML_PREFIX + actionsXML + XML_SUFFIX);
} else {
writeUpdatesToXMLFile(XML_EMPTY);
}
reloadUpdateManagerData();
let noOverrideArgs = Cc["@mozilla.org/browser/clh;1"].
getService(Ci.nsIBrowserHandler).defaultArgs;
let overrideArgs = "";
if (test.prefURL) {
overrideArgs = test.prefURL;
} else if (test.openURL) {
overrideArgs = test.openURL;
}
if (overrideArgs == "" && noOverrideArgs) {
overrideArgs = noOverrideArgs;
} else if (noOverrideArgs) {
overrideArgs += "|" + noOverrideArgs;
}
if (test.noMstoneChange === undefined) {
gPrefService.setCharPref(PREF_MSTONE, "PreviousMilestone");
}
if (test.noPostUpdatePref == undefined) {
gPrefService.setBoolPref(PREF_POSTUPDATE, true);
}
let defaultArgs = Cc["@mozilla.org/browser/clh;1"].
getService(Ci.nsIBrowserHandler).defaultArgs;
is(defaultArgs, overrideArgs, "correct value returned by defaultArgs");
if (test.noMstoneChange === undefined || test.noMstoneChange != true) {
let newMstone = gPrefService.getCharPref(PREF_MSTONE);
is(originalMstone, newMstone, "preference " + PREF_MSTONE +
" should have been updated");
}
if (gPrefService.prefHasUserValue(PREF_POSTUPDATE)) {
gPrefService.clearUserPref(PREF_POSTUPDATE);
}
}
testShowNotification();
}
// nsBrowserGlue.js _showUpdateNotification notification tests
const BG_NOTIFY_TESTS = [
{
description: "'silent showNotification' actions should not display a notification",
actions: "silent showNotification"
}, {
description: "'showNotification' for actions should display a notification",
actions: "showNotification"
}, {
description: "no actions and empty updates.xml",
}, {
description: "'showAlert' for actions should not display a notification",
actions: "showAlert"
}, {
// This test MUST be the last test in the array to test opening the url
// provided by the updates.xml.
description: "'showNotification' for actions with custom notification " +
"attributes should display a notification",
actions: "showNotification",
notificationText: "notification text",
notificationURL: DEFAULT_UPDATE_URL,
notificationButtonLabel: "button label",
notificationButtonAccessKey: "b"
}
];
// Test showing a notification after an update
// _showUpdateNotification in nsBrowserGlue.js
function testShowNotification()
{
let notifyBox = document.getElementById("high-priority-global-notificationbox");
// Catches any windows opened by these tests (e.g. alert windows) and closes
// them
gWindowCatcher.start();
for (let i = 0; i < BG_NOTIFY_TESTS.length; i++) {
let test = BG_NOTIFY_TESTS[i];
ok(true, "Test showNotification " + (i + 1) + ": " + test.description);
if (test.actions) {
let actionsXML = " actions=\"" + test.actions + "\"";
if (test.notificationText) {
actionsXML += " notificationText=\"" + test.notificationText + "\"";
}
if (test.notificationURL) {
actionsXML += " notificationURL=\"" + test.notificationURL + "\"";
}
if (test.notificationButtonLabel) {
actionsXML += " notificationButtonLabel=\"" + test.notificationButtonLabel + "\"";
}
if (test.notificationButtonAccessKey) {
actionsXML += " notificationButtonAccessKey=\"" + test.notificationButtonAccessKey + "\"";
}
writeUpdatesToXMLFile(XML_PREFIX + actionsXML + XML_SUFFIX);
} else {
writeUpdatesToXMLFile(XML_EMPTY);
}
reloadUpdateManagerData();
gPrefService.setBoolPref(PREF_POSTUPDATE, true);
gBG.observe(null, "browser-glue-test", "post-update-notification");
let updateBox = notifyBox.getNotificationWithValue("post-update-notification");
if (test.actions && test.actions.indexOf("showNotification") != -1 &&
test.actions.indexOf("silent") == -1) {
ok(updateBox, "Update notification box should have been displayed");
if (updateBox) {
if (test.notificationText) {
is(updateBox.label, test.notificationText, "Update notification box " +
"should have the label provided by the update");
}
if (test.notificationButtonLabel) {
var button = updateBox.getElementsByTagName("button").item(0);
is(button.label, test.notificationButtonLabel, "Update notification " +
"box button should have the label provided by the update");
if (test.notificationButtonAccessKey) {
let accessKey = button.getAttribute("accesskey");
is(accessKey, test.notificationButtonAccessKey, "Update " +
"notification box button should have the accesskey " +
"provided by the update");
}
}
// The last test opens an url and verifies the url from the updates.xml
// is correct.
if (i == (BG_NOTIFY_TESTS.length - 1)) {
// Wait for any windows caught by the windowcatcher to close
gWindowCatcher.finish(function () {
BrowserTestUtils.waitForNewTab(gBrowser).then(testNotificationURL);
button.click();
});
} else {
notifyBox.removeAllNotifications(true);
}
} else if (i == (BG_NOTIFY_TESTS.length - 1)) {
// If updateBox is null the test has already reported errors so bail
finish_test();
}
} else {
ok(!updateBox, "Update notification box should not have been displayed");
}
let prefHasUserValue = gPrefService.prefHasUserValue(PREF_POSTUPDATE);
is(prefHasUserValue, false, "preference " + PREF_POSTUPDATE +
" shouldn't have a user value");
}
}
// Test opening the url provided by the updates.xml in the last test
function testNotificationURL()
{
ok(true, "Test testNotificationURL: clicking the notification button " +
"opened the url specified by the update");
let href = gBrowser.currentURI.spec;
let expectedURL = BG_NOTIFY_TESTS[BG_NOTIFY_TESTS.length - 1].notificationURL;
is(href, expectedURL, "The url opened from the notification should be the " +
"url provided by the update");
gBrowser.removeCurrentTab();
window.focus();
finish_test();
}
/* Reloads the update metadata from disk */
function reloadUpdateManagerData()
{
Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager).
QueryInterface(Ci.nsIObserver).observe(null, "um-reload-update-data", "");
}
function writeUpdatesToXMLFile(aText)
{
const PERMS_FILE = 0o644;
const MODE_WRONLY = 0x02;
const MODE_CREATE = 0x08;
const MODE_TRUNCATE = 0x20;
let file = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties).
get("UpdRootD", Ci.nsIFile);
file.append("updates.xml");
let fos = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
if (!file.exists()) {
file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
}
fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
fos.write(aText, aText.length);
fos.close();
}
@@ -0,0 +1,8 @@
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>Google</ShortName>
<Description>override-de-DE</Description>
<InputEncoding>UTF-8</InputEncoding>
<Url type="text/html" method="GET" template="http://searchtest.local">
<Param name="search" value="{searchTerms}"/>
</Url>
</SearchPlugin>
@@ -0,0 +1,58 @@
# Distribution Configuration File
# Test of distribution preferences
[Global]
id=disttest
version=1.0
about=Test distribution file
about.en-US=Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè
[Preferences]
distribution.test.string="Test String"
distribution.test.string.noquotes=Test String
distribution.test.int=777
distribution.test.bool.true=true
distribution.test.bool.false=false
distribution.test.empty=
distribution.test.pref.locale="%LOCALE%"
distribution.test.pref.language.reset="Preference Set"
distribution.test.pref.locale.reset="Preference Set"
distribution.test.pref.locale.set="Preference Set"
distribution.test.pref.language.set="Preference Set"
[Preferences-en]
distribution.test.pref.language.en="en"
distribution.test.pref.language.reset=
distribution.test.pref.language.set="Language Set"
distribution.test.pref.locale.set="Language Set"
[Preferences-en-US]
distribution.test.pref.locale.en-US="en-US"
distribution.test.pref.locale.reset=
distribution.test.pref.locale.set="Locale Set"
[Preferences-de]
distribution.test.pref.language.de="de"
[LocalizablePreferences]
distribution.test.locale="%LOCALE%"
distribution.test.language.reset="Preference Set"
distribution.test.locale.reset="Preference Set"
distribution.test.locale.set="Preference Set"
distribution.test.language.set="Preference Set"
[LocalizablePreferences-en]
distribution.test.language.en="en"
distribution.test.language.reset=
distribution.test.language.set="Language Set"
distribution.test.locale.set="Language Set"
[LocalizablePreferences-en-US]
distribution.test.locale.en-US="en-US"
distribution.test.locale.reset=
distribution.test.locale.set="Locale Set"
[LocalizablePreferences-de]
distribution.test.language.de="de"
@@ -0,0 +1,157 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that preferences are properly set by distribution.ini
*/
var Ci = Components.interfaces;
var Cc = Components.classes;
var Cr = Components.results;
var Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/LoadContextInfo.jsm");
// Import common head.
var commonFile = do_get_file("../../../../toolkit/components/places/tests/head_common.js", false);
if (commonFile) {
let uri = Services.io.newFileURI(commonFile);
Services.scriptloader.loadSubScript(uri.spec, this);
}
const TOPICDATA_DISTRIBUTION_CUSTOMIZATION = "force-distribution-customization";
const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
/**
* Copy the engine-distribution.xml engine to a fake distribution
* created in the profile, and registered with the directory service.
* Create an empty en-US directory to make sure it isn't used.
*/
function installDistributionEngine() {
const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
const gProfD = do_get_profile().QueryInterface(Ci.nsILocalFile);
let dir = gProfD.clone();
dir.append("distribution");
let distDir = dir.clone();
dir.append("searchplugins");
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
dir.append("locale");
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let localeDir = dir.clone();
dir.append("en-US");
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
localeDir.append("de-DE");
localeDir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
do_get_file("data/engine-de-DE.xml").copyTo(localeDir, "engine-de-DE.xml");
Services.dirsvc.registerProvider({
getFile: function(aProp, aPersistent) {
aPersistent.value = true;
if (aProp == XRE_APP_DISTRIBUTION_DIR)
return distDir.clone();
return null;
}
});
}
function run_test() {
// Set special pref to load distribution.ini from the profile folder.
Services.prefs.setBoolPref("distribution.testing.loadFromProfile", true);
// Copy distribution.ini file to the profile dir.
let distroDir = gProfD.clone();
distroDir.leafName = "distribution";
let iniFile = distroDir.clone();
iniFile.append("distribution.ini");
if (iniFile.exists()) {
iniFile.remove(false);
print("distribution.ini already exists, did some test forget to cleanup?");
}
let testDistributionFile = gTestDir.clone();
testDistributionFile.append("distribution.ini");
testDistributionFile.copyTo(distroDir, "distribution.ini");
Assert.ok(testDistributionFile.exists());
installDistributionEngine();
run_next_test();
}
do_register_cleanup(function () {
// Remove the distribution dir, even if the test failed, otherwise all
// next tests will use it.
let distDir = gProfD.clone();
distDir.append("distribution");
distDir.remove(true);
Assert.ok(!distDir.exists());
});
add_task(function* () {
// Force distribution.
let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
Assert.equal(Services.prefs.getCharPref("distribution.id"), "disttest");
Assert.equal(Services.prefs.getCharPref("distribution.version"), "1.0");
Assert.equal(Services.prefs.getComplexValue("distribution.about", Ci.nsISupportsString).data, "Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè");
Assert.equal(Services.prefs.getCharPref("distribution.test.string"), "Test String");
Assert.equal(Services.prefs.getCharPref("distribution.test.string.noquotes"), "Test String");
Assert.equal(Services.prefs.getIntPref("distribution.test.int"), 777);
Assert.equal(Services.prefs.getBoolPref("distribution.test.bool.true"), true);
Assert.equal(Services.prefs.getBoolPref("distribution.test.bool.false"), false);
Assert.throws(() => Services.prefs.getCharPref("distribution.test.empty"));
Assert.throws(() => Services.prefs.getIntPref("distribution.test.empty"));
Assert.throws(() => Services.prefs.getBoolPref("distribution.test.empty"));
Assert.equal(Services.prefs.getCharPref("distribution.test.pref.locale"), "en-US");
Assert.equal(Services.prefs.getCharPref("distribution.test.pref.language.en"), "en");
Assert.equal(Services.prefs.getCharPref("distribution.test.pref.locale.en-US"), "en-US");
Assert.throws(() => Services.prefs.getCharPref("distribution.test.pref.language.de"));
// This value was never set because of the empty language specific pref
Assert.throws(() => Services.prefs.getCharPref("distribution.test.pref.language.reset"));
// This value was never set because of the empty locale specific pref
Assert.throws(() => Services.prefs.getCharPref("distribution.test.pref.locale.reset"));
// This value was overridden by a locale specific setting
Assert.equal(Services.prefs.getCharPref("distribution.test.pref.locale.set"), "Locale Set");
// This value was overridden by a language specific setting
Assert.equal(Services.prefs.getCharPref("distribution.test.pref.language.set"), "Language Set");
// Language should not override locale
Assert.notEqual(Services.prefs.getCharPref("distribution.test.pref.locale.set"), "Language Set");
Assert.equal(Services.prefs.getComplexValue("distribution.test.locale", Ci.nsIPrefLocalizedString).data, "en-US");
Assert.equal(Services.prefs.getComplexValue("distribution.test.language.en", Ci.nsIPrefLocalizedString).data, "en");
Assert.equal(Services.prefs.getComplexValue("distribution.test.locale.en-US", Ci.nsIPrefLocalizedString).data, "en-US");
Assert.throws(() => Services.prefs.getComplexValue("distribution.test.language.de", Ci.nsIPrefLocalizedString));
// This value was never set because of the empty language specific pref
Assert.throws(() => Services.prefs.getComplexValue("distribution.test.language.reset", Ci.nsIPrefLocalizedString));
// This value was never set because of the empty locale specific pref
Assert.throws(() => Services.prefs.getComplexValue("distribution.test.locale.reset", Ci.nsIPrefLocalizedString));
// This value was overridden by a locale specific setting
Assert.equal(Services.prefs.getComplexValue("distribution.test.locale.set", Ci.nsIPrefLocalizedString).data, "Locale Set");
// This value was overridden by a language specific setting
Assert.equal(Services.prefs.getComplexValue("distribution.test.language.set", Ci.nsIPrefLocalizedString).data, "Language Set");
// Language should not override locale
Assert.notEqual(Services.prefs.getComplexValue("distribution.test.locale.set", Ci.nsIPrefLocalizedString).data, "Language Set");
do_test_pending();
Services.prefs.setCharPref("distribution.searchplugins.defaultLocale", "de-DE");
Services.search.init(function() {
Assert.equal(Services.search.isInitialized, true);
var engine = Services.search.getEngineByName("Google");
Assert.equal(engine.description, "override-de-DE");
do_test_finished();
});
});
@@ -0,0 +1,8 @@
[DEFAULT]
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
support-files =
distribution.ini
data/engine-de-DE.xml
[test_distribution.js]
@@ -41,7 +41,7 @@ function ProtocolHandler(aScheme, aFlags)
ProtocolHandler.prototype =
{
defaultPort: -1,
allowPort: function() false,
allowPort: () => false,
newURI: function(aSpec, aCharset, aBaseURI)
{
let uri = Cc["@mozilla.org/network/standard-url;1"].
+2 -1
View File
@@ -28,6 +28,7 @@
// Rules from the mozilla plugin
"mozilla/balanced-listeners": 2,
"mozilla/components-imports": 1,
"mozilla/import-globals-from": 1,
"mozilla/import-headjs-globals": 1,
"mozilla/mark-test-function-used": 1,
"mozilla/no-aArgs": 1,
@@ -96,7 +97,7 @@
// rule is a better rule to check this.
"max-depth": 0,
// Maximum length of a line.
"max-len": [1, 80],
"max-len": [2, 80, 2, {"ignoreUrls": true, "ignorePattern": "\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}],
// Maximum depth callbacks can be nested.
"max-nested-callbacks": [2, 3],
// Don't limit the number of parameters that can be used in a function.
@@ -3,8 +3,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals ViewHelpers, Task, AnimationsPanel, promise, EventEmitter,
AnimationsFront */
/* animation-panel.js is loaded in the same scope but we don't use
import-globals-from to avoid infinite loops since animation-panel.js already
imports globals from animation-controller.js */
/* globals AnimationsPanel */
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
"use strict";
@@ -16,10 +20,8 @@ Cu.import("resource://gre/modules/Console.jsm");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "AnimationsFront",
"devtools/server/actors/animation", true);
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "AnimationsFront", "devtools/server/actors/animation", true);
const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
@@ -127,6 +129,7 @@ var getServerTraits = Task.async(function*(target) {
*/
var AnimationsController = {
PLAYERS_UPDATED_EVENT: "players-updated",
ALL_ANIMATIONS_TOGGLED_EVENT: "all-animations-toggled",
initialize: Task.async(function*() {
if (this.initialized) {
@@ -174,7 +177,7 @@ var AnimationsController = {
this.destroyed = promise.defer();
this.stopListeners();
yield this.destroyAnimationPlayers();
this.destroyAnimationPlayers();
this.nodeFront = null;
if (this.animationsFront) {
@@ -221,17 +224,18 @@ var AnimationsController = {
return;
}
this.nodeFront = gInspector.selection.nodeFront;
let done = gInspector.updating("animationscontroller");
if (!gInspector.selection.isConnected() ||
!gInspector.selection.isElementNode()) {
yield this.destroyAnimationPlayers();
!gInspector.selection.isElementNode() ||
gInspector.selection.isPseudoElementNode()) {
this.destroyAnimationPlayers();
this.emit(this.PLAYERS_UPDATED_EVENT);
done();
return;
}
this.nodeFront = gInspector.selection.nodeFront;
yield this.refreshAnimationPlayers(this.nodeFront);
this.emit(this.PLAYERS_UPDATED_EVENT, this.animationPlayers);
@@ -246,7 +250,9 @@ var AnimationsController = {
return promise.resolve();
}
return this.animationsFront.toggleAll().catch(e => console.error(e));
return this.animationsFront.toggleAll()
.then(() => this.emit(this.ALL_ANIMATIONS_TOGGLED_EVENT, this))
.catch(e => console.error(e));
},
/**
@@ -319,7 +325,7 @@ var AnimationsController = {
animationPlayers: [],
refreshAnimationPlayers: Task.async(function*(nodeFront) {
yield this.destroyAnimationPlayers();
this.destroyAnimationPlayers();
this.animationPlayers = yield this.animationsFront
.getAnimationPlayersForNode(nodeFront);
@@ -332,7 +338,7 @@ var AnimationsController = {
}
}),
onAnimationMutations: Task.async(function*(changes) {
onAnimationMutations: function(changes) {
// Insert new players into this.animationPlayers when new animations are
// added.
for (let {type, player} of changes) {
@@ -341,7 +347,6 @@ var AnimationsController = {
}
if (type === "removed") {
yield player.release();
let index = this.animationPlayers.indexOf(player);
this.animationPlayers.splice(index, 1);
}
@@ -349,7 +354,7 @@ var AnimationsController = {
// Let the UI know the list has been updated.
this.emit(this.PLAYERS_UPDATED_EVENT, this.animationPlayers);
}),
},
/**
* Get the latest known current time of document.timeline.
@@ -370,19 +375,9 @@ var AnimationsController = {
return time;
},
destroyAnimationPlayers: Task.async(function*() {
// Let the server know that we're not interested in receiving updates about
// players for the current node. We're either being destroyed or a new node
// has been selected.
if (this.traits.hasMutationEvents) {
yield this.animationsFront.stopAnimationPlayerUpdates();
}
for (let front of this.animationPlayers) {
yield front.release();
}
destroyAnimationPlayers: function() {
this.animationPlayers = [];
})
}
};
EventEmitter.decorate(AnimationsController);
@@ -21,12 +21,12 @@
<div id="timeline-toolbar" class="theme-toolbar">
<button id="rewind-timeline" standalone="true" class="devtools-button"></button>
<button id="pause-resume-timeline" standalone="true" class="devtools-button pause-button paused"></button>
<span id="timeline-rate"></span>
<span id="timeline-current-time" class="label"></span>
<span id="timeline-rate" standalone="true" class="devtools-button"></span>
<span id="timeline-current-time" class="label devtools-toolbarbutton"></span>
</div>
<div id="players"></div>
<div id="error-message">
<p>&invalidElement;</p>
<p id="error-type"></p>
<p>&selectElement;</p>
<button id="element-picker" standalone="true" class="devtools-button"></button>
</div>
@@ -3,7 +3,9 @@
/* 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/. */
/* globals AnimationsController, document, promise, gToolbox, gInspector */
/* import-globals-from animation-controller.js */
/* globals document */
"use strict";
@@ -41,6 +43,9 @@ var AnimationsPanel = {
this.timelineCurrentTimeEl = $("#timeline-current-time");
this.rateSelectorEl = $("#timeline-rate");
this.rewindTimelineButtonEl.setAttribute("title",
L10N.getStr("timeline.rewindButtonTooltip"));
// If the server doesn't support toggling all animations at once, hide the
// whole global toolbar.
if (!AnimationsController.traits.hasToggleAll) {
@@ -48,10 +53,10 @@ var AnimationsPanel = {
}
// Binding functions that need to be called in scope.
for (let functionName of ["onPickerStarted", "onPickerStopped",
"refreshAnimationsUI", "toggleAll", "onTabNavigated",
"onTimelineDataChanged", "playPauseTimeline", "rewindTimeline",
"onRateChanged"]) {
for (let functionName of ["onKeyDown", "onPickerStarted",
"onPickerStopped", "refreshAnimationsUI", "onToggleAllClicked",
"onTabNavigated", "onTimelineDataChanged", "onTimelinePlayClicked",
"onTimelineRewindClicked", "onRateChanged"]) {
this[functionName] = this[functionName].bind(this);
}
let hUtils = gToolbox.highlighterUtils;
@@ -110,9 +115,13 @@ var AnimationsPanel = {
gToolbox.on("picker-started", this.onPickerStarted);
gToolbox.on("picker-stopped", this.onPickerStopped);
this.toggleAllButtonEl.addEventListener("click", this.toggleAll);
this.playTimelineButtonEl.addEventListener("click", this.playPauseTimeline);
this.rewindTimelineButtonEl.addEventListener("click", this.rewindTimeline);
this.toggleAllButtonEl.addEventListener("click", this.onToggleAllClicked);
this.playTimelineButtonEl.addEventListener(
"click", this.onTimelinePlayClicked);
this.rewindTimelineButtonEl.addEventListener(
"click", this.onTimelineRewindClicked);
document.addEventListener("keydown", this.onKeyDown, false);
gToolbox.target.on("navigate", this.onTabNavigated);
@@ -132,9 +141,14 @@ var AnimationsPanel = {
gToolbox.off("picker-started", this.onPickerStarted);
gToolbox.off("picker-stopped", this.onPickerStopped);
this.toggleAllButtonEl.removeEventListener("click", this.toggleAll);
this.playTimelineButtonEl.removeEventListener("click", this.playPauseTimeline);
this.rewindTimelineButtonEl.removeEventListener("click", this.rewindTimeline);
this.toggleAllButtonEl.removeEventListener("click",
this.onToggleAllClicked);
this.playTimelineButtonEl.removeEventListener("click",
this.onTimelinePlayClicked);
this.rewindTimelineButtonEl.removeEventListener("click",
this.onTimelineRewindClicked);
document.removeEventListener("keydown", this.onKeyDown, false);
gToolbox.target.off("navigate", this.onTabNavigated);
@@ -146,6 +160,22 @@ var AnimationsPanel = {
}
},
onKeyDown: function(event) {
let keyEvent = Ci.nsIDOMKeyEvent;
// If the space key is pressed, it should toggle the play state of
// the animations displayed in the panel, or of all the animations on
// the page if the selected node does not have any animation on it.
if (event.keyCode === keyEvent.DOM_VK_SPACE) {
if (AnimationsController.animationPlayers.length > 0) {
this.playPauseTimeline().catch(ex => console.error(ex));
} else {
this.toggleAll().catch(ex => console.error(ex));
}
event.preventDefault();
}
},
togglePlayers: function(isVisible) {
if (isVisible) {
document.body.removeAttribute("empty");
@@ -153,6 +183,9 @@ var AnimationsPanel = {
} else {
document.body.setAttribute("empty", "true");
document.body.removeAttribute("timeline");
$("#error-type").textContent = gInspector.selection.isPseudoElementNode()
? L10N.getStr("panel.pseudoElementSelected")
: L10N.getStr("panel.invalidElementSelected");
}
},
@@ -164,32 +197,53 @@ var AnimationsPanel = {
this.pickerButtonEl.removeAttribute("checked");
},
onToggleAllClicked: function() {
this.toggleAll().catch(ex => console.error(ex));
},
/**
* Toggle (pause/play) all animations in the current target
* and update the UI the toggleAll button.
*/
toggleAll: Task.async(function*() {
this.toggleAllButtonEl.classList.toggle("paused");
yield AnimationsController.toggleAll();
}),
onTimelinePlayClicked: function() {
this.playPauseTimeline().catch(ex => console.error(ex));
},
/**
* Depending on the state of the timeline either pause or play the animations
* displayed in it.
* If the animations are finished, this will play them from the start again.
* If the animations are playing, this will pause them.
* If the animations are paused, this will resume them.
*
* @return {Promise} Resolves when the playState is changed and the UI
* is refreshed
*/
playPauseTimeline: function() {
AnimationsController.toggleCurrentAnimations(this.timelineData.isMoving)
.then(() => this.refreshAnimationsStateAndUI())
.catch(e => console.error(e));
return AnimationsController
.toggleCurrentAnimations(this.timelineData.isMoving)
.then(() => this.refreshAnimationsStateAndUI());
},
onTimelineRewindClicked: function() {
this.rewindTimeline().catch(ex => console.error(ex));
},
/**
* Reset the startTime of all current animations shown in the timeline and
* pause them.
*
* @return {Promise} Resolves when currentTime is set and the UI is refreshed
*/
rewindTimeline: function() {
AnimationsController.setCurrentTimeAll(0, true)
.then(() => this.refreshAnimationsStateAndUI())
.catch(e => console.error(e));
return AnimationsController
.setCurrentTimeAll(0, true)
.then(() => this.refreshAnimationsStateAndUI());
},
/**
@@ -199,7 +253,7 @@ var AnimationsPanel = {
onRateChanged: function(e, rate) {
AnimationsController.setPlaybackRateAll(rate)
.then(() => this.refreshAnimationsStateAndUI())
.catch(e => console.error(e));
.catch(ex => console.error(ex));
},
onTabNavigated: function() {
@@ -212,6 +266,12 @@ var AnimationsPanel = {
this.playTimelineButtonEl.classList.toggle("paused", !isMoving);
let l10nPlayProperty = isMoving ? "timeline.resumedButtonTooltip" :
"timeline.pausedButtonTooltip";
this.playTimelineButtonEl.setAttribute("title",
L10N.getStr(l10nPlayProperty));
// If the timeline data changed as a result of the user dragging the
// scrubber, then pause all animations and set their currentTimes.
// (Note that we want server-side requests to be sequenced, so we only do
@@ -227,11 +287,8 @@ var AnimationsPanel = {
},
displayTimelineCurrentTime: function() {
let {isMoving, isPaused, time} = this.timelineData;
if (isMoving || isPaused) {
this.timelineCurrentTimeEl.textContent = formatStopwatchTime(time);
}
let {time} = this.timelineData;
this.timelineCurrentTimeEl.textContent = formatStopwatchTime(time);
},
/**
@@ -251,8 +308,6 @@ var AnimationsPanel = {
* the various components again.
*/
refreshAnimationsUI: Task.async(function*() {
let done = gInspector.updating("animationspanel");
// Empty the whole panel first.
this.togglePlayers(true);
@@ -271,12 +326,10 @@ var AnimationsPanel = {
if (!AnimationsController.animationPlayers.length) {
this.togglePlayers(false);
this.emit(this.UI_UPDATED_EVENT);
done();
return;
}
this.emit(this.UI_UPDATED_EVENT);
done();
})
};
@@ -1,12 +1,16 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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 {Cu} = require("chrome");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {
createNode,
TimeScale
} = require("devtools/client/animationinspector/utils");
const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
const {Keyframes} = require("devtools/client/animationinspector/components/keyframes");
/**
* UI component responsible for displaying detailed information for a given
* animation.
@@ -1,10 +1,15 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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 {Cu} = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {DomNodePreview} = require(
"devtools/client/inspector/shared/dom-node-preview");
const {DomNodePreview} = require("devtools/client/inspector/shared/dom-node-preview");
// Map dom node fronts by animation fronts so we don't have to get them from the
// walker every time the timeline is refreshed.
@@ -1,11 +1,14 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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 {Cu} = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const {
createNode,
TimeScale
} = require("devtools/client/animationinspector/utils");
const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
@@ -52,11 +55,10 @@ AnimationTimeBlock.prototype = {
let {x, iterationW, delayX, delayW, negativeDelayW} =
TimeScale.getAnimationDimensions(animation);
let iterations = createNode({
createNode({
parent: this.containerEl,
attributes: {
"class": state.type + " iterations" +
(state.iterationCount ? "" : " infinite"),
"class": "iterations" + (state.iterationCount ? "" : " infinite"),
// Individual iterations are represented by setting the size of the
// repeating linear-gradient.
"style": `left:${x}%;
@@ -66,16 +68,20 @@ AnimationTimeBlock.prototype = {
});
// The animation name is displayed over the iterations.
// Note that in case of negative delay, we push the name towards the right
// so the delay can be shown.
// Note that in case of negative delay, it is pushed towards the right so
// the delay element does not overlap.
createNode({
parent: iterations,
attributes: {
"class": "name",
"title": this.getTooltipText(state),
// Make space for the negative delay with a margin-left.
"style": `margin-left:${negativeDelayW}%`
},
parent: createNode({
parent: this.containerEl,
attributes: {
"class": "name",
"title": this.getTooltipText(state),
// Place the name at the same position as the iterations, but make
// space for the negative delay if any.
"style": `left:${x + negativeDelayW}%;
width:${iterationW - negativeDelayW}%;`
},
}),
textContent: state.name
});
@@ -83,10 +89,10 @@ AnimationTimeBlock.prototype = {
if (state.delay) {
// Negative delays need to start at 0.
createNode({
parent: iterations,
parent: this.containerEl,
attributes: {
"class": "delay" + (state.delay < 0 ? " negative" : ""),
"style": `left:-${delayX}%;
"style": `left:${delayX}%;
width:${delayW}%;`
}
});
@@ -145,13 +151,24 @@ AnimationTimeBlock.prototype = {
/**
* Get a formatted title for this animation. This will be either:
* "some-name", "some-name : CSS Transition", or "some-name : CSS Animation",
* depending if the server provides the type, and what type it is.
* "some-name", "some-name : CSS Transition", "some-name : CSS Animation",
* "some-name : Script Animation", or "Script Animation", depending
* if the server provides the type, what type it is and if the animation
* has a name
* @param {AnimationPlayerFront} animation
*/
function getFormattedAnimationTitle({state}) {
// Older servers don't send the type.
return state.type
? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
: state.name;
// Older servers don't send a type, and only know about
// CSSAnimations and CSSTransitions, so it's safe to use
// just the name.
if (!state.type) {
return state.name;
}
// Script-generated animations may not have a name.
if (state.type === "scriptanimation" && !state.name) {
return L10N.getStr("timeline.scriptanimation.unnamedLabel");
}
return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
}
@@ -1,3 +1,9 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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 {
@@ -81,7 +87,8 @@ AnimationsTimeline.prototype = {
"class": "scrubber-handle"
}
});
this.scrubberHandleEl.addEventListener("mousedown", this.onScrubberMouseDown);
this.scrubberHandleEl.addEventListener("mousedown",
this.onScrubberMouseDown);
this.timeHeaderEl = createNode({
parent: this.rootWrapperEl,
@@ -89,7 +96,8 @@ AnimationsTimeline.prototype = {
"class": "time-header track-container"
}
});
this.timeHeaderEl.addEventListener("mousedown", this.onScrubberMouseDown);
this.timeHeaderEl.addEventListener("mousedown",
this.onScrubberMouseDown);
this.animationsEl = createNode({
parent: this.rootWrapperEl,
@@ -99,14 +107,16 @@ AnimationsTimeline.prototype = {
}
});
this.win.addEventListener("resize", this.onWindowResize);
this.win.addEventListener("resize",
this.onWindowResize);
},
destroy: function() {
this.stopAnimatingScrubber();
this.unrender();
this.win.removeEventListener("resize", this.onWindowResize);
this.win.removeEventListener("resize",
this.onWindowResize);
this.timeHeaderEl.removeEventListener("mousedown",
this.onScrubberMouseDown);
this.scrubberHandleEl.removeEventListener("mousedown",
@@ -144,6 +154,7 @@ AnimationsTimeline.prototype = {
for (let animation of this.animations) {
animation.off("changed", this.onAnimationStateChanged);
}
this.stopAnimatingScrubber();
TimeScale.reset();
this.destroySubComponents("targetNodes");
this.destroySubComponents("timeBlocks");
@@ -277,9 +288,9 @@ AnimationsTimeline.prototype = {
parent: this.animationsEl,
nodeType: "li",
attributes: {
"class": "animation" + (animation.state.isRunningOnCompositor
? " fast-track"
: "")
"class": "animation " +
animation.state.type +
(animation.state.isRunningOnCompositor ? " fast-track" : "")
}
});
@@ -357,16 +368,19 @@ AnimationsTimeline.prototype = {
},
startAnimatingScrubber: function(time) {
let x = TimeScale.startTimeToDistance(time);
this.scrubberEl.style.left = x + "%";
// Only stop the scrubber if it's out of bounds or all animations have been
// paused, but not if at least an animation is infinite.
let isOutOfBounds = time < TimeScale.minStartTime ||
time > TimeScale.maxEndTime;
let isAllPaused = !this.isAtLeastOneAnimationPlaying();
let hasInfinite = this.hasInfiniteAnimations();
let x = TimeScale.startTimeToDistance(time);
if (x > 100 && !hasInfinite) {
x = 100;
}
this.scrubberEl.style.left = x + "%";
// Only stop the scrubber if it's out of bounds or all animations have been
// paused, but not if at least an animation is infinite.
if (isAllPaused || (isOutOfBounds && !hasInfinite)) {
this.stopAnimatingScrubber();
this.emit("timeline-data-changed", {
@@ -410,15 +424,21 @@ AnimationsTimeline.prototype = {
drawHeaderAndBackground: function() {
let width = this.timeHeaderEl.offsetWidth;
let scale = width / (TimeScale.maxEndTime - TimeScale.minStartTime);
let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
let minTimeInterval = TIME_GRADUATION_MIN_SPACING *
animationDuration / width;
let intervalLength = findOptimalTimeInterval(minTimeInterval);
let intervalWidth = intervalLength * width / animationDuration;
drawGraphElementBackground(this.win.document, "time-graduations",
width, scale);
width, intervalWidth);
// And the time graduation header.
this.timeHeaderEl.innerHTML = "";
let interval = findOptimalTimeInterval(scale, TIME_GRADUATION_MIN_SPACING);
for (let i = 0; i < width; i += interval) {
let pos = 100 * i / width;
for (let i = 0; i <= width / intervalWidth; i++) {
let pos = 100 * i * intervalWidth / width;
createNode({
parent: this.timeHeaderEl,
nodeType: "span",
@@ -1,3 +1,9 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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 {Cu} = require("chrome");
@@ -1,3 +1,9 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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 {Cu} = require("chrome");
@@ -28,7 +34,10 @@ RateSelector.prototype = {
this.selectEl = createNode({
parent: containerEl,
nodeType: "select",
attributes: {"class": "devtools-button"}
attributes: {
"class": "devtools-button",
"title": L10N.getStr("timeline.rateSelectorTooltip")
}
});
this.selectEl.addEventListener("change", this.onRateChanged);
@@ -8,6 +8,7 @@ support-files =
doc_modify_playbackRate.html
doc_negative_animation.html
doc_simple_animation.html
doc_multiple_animation_types.html
head.js
[browser_animation_animated_properties_displayed.js]
@@ -26,6 +27,8 @@ support-files =
[browser_animation_running_on_compositor.js]
[browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
[browser_animation_shows_player_on_valid_node.js]
[browser_animation_spacebar_toggles_animations.js]
[browser_animation_spacebar_toggles_node_animations.js]
[browser_animation_target_highlight_select.js]
[browser_animation_target_highlighter_lock.js]
[browser_animation_timeline_currentTime.js]
@@ -1,19 +1,20 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
requestLongerTimeout(2);
// Test that the panel shows no animation data for invalid or not animated nodes
const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, window} = yield openAnimationInspector();
let {document} = window;
let {inspector, panel} = yield openAnimationInspector();
yield testEmptyPanel(inspector, panel);
});
function* testEmptyPanel(inspector, panel) {
info("Select node .still and check that the panel is empty");
let stillNode = yield getNodeFront(".still", inspector);
let onUpdated = panel.once(panel.UI_UPDATED_EVENT);
@@ -24,6 +25,9 @@ function* testEmptyPanel(inspector, panel) {
"No animation players stored in the timeline component for a still node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a still node");
is(document.querySelector("#error-type").textContent,
L10N.getStr("panel.invalidElementSelected"),
"The correct error message is displayed");
info("Select the comment text node and check that the panel is empty");
let commentNode = yield inspector.walker.previousSibling(stillNode);
@@ -34,4 +38,25 @@ function* testEmptyPanel(inspector, panel) {
is(panel.animationsTimelineComponent.animations.length, 0,
"No animation players stored in the timeline component for a text node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a text node");}
"No animation displayed in the timeline component for a text node");
is(document.querySelector("#error-type").textContent,
L10N.getStr("panel.invalidElementSelected"),
"The correct error message is displayed");
info("Select the pseudo element node and check that the panel is empty " +
"and contains the special animated pseudo-element message");
let pseudoElParent = yield getNodeFront(".pseudo", inspector);
let {nodes} = yield inspector.walker.children(pseudoElParent);
let pseudoEl = nodes[0];
onUpdated = panel.once(panel.UI_UPDATED_EVENT);
yield selectNode(pseudoEl, inspector);
yield onUpdated;
is(panel.animationsTimelineComponent.animations.length, 0,
"No animation players stored in the timeline component for a pseudo-node");
is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
"No animation displayed in the timeline component for a pseudo-node");
is(document.querySelector("#error-type").textContent,
L10N.getStr("panel.pseudoElementSelected"),
"The correct error message is displayed");
});
@@ -10,10 +10,14 @@ add_task(function*() {
yield addTab("data:text/html;charset=utf-8,welcome to the animation panel");
let {panel, controller} = yield openAnimationInspector();
ok(controller, "The animation controller exists");
ok(controller.animationsFront, "The animation controller has been initialized");
ok(panel, "The animation panel exists");
ok(panel.playersEl, "The animation panel has been initialized");
ok(panel.animationsTimelineComponent, "The animation panel has been initialized");
ok(controller,
"The animation controller exists");
ok(controller.animationsFront,
"The animation controller has been initialized");
ok(panel,
"The animation panel exists");
ok(panel.playersEl,
"The animation panel has been initialized");
ok(panel.animationsTimelineComponent,
"The animation panel has been initialized");
});
@@ -12,7 +12,8 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector();
info("Listen for the players-updated, ui-updated and inspector-updated events");
info("Listen for the players-updated, ui-updated and " +
"inspector-updated events");
let receivedEvents = [];
controller.once(controller.PLAYERS_UPDATED_EVENT, () => {
receivedEvents.push(controller.PLAYERS_UPDATED_EVENT);
@@ -26,24 +26,9 @@ add_task(function*() {
is(controller.animationPlayers.length, 2,
"2 AnimationPlayerFronts have been created");
// Hold on to one of the AnimationPlayerFront objects and mock its release
// method to test that it is released correctly and that its auto-refresh is
// stopped.
let retainedFront = controller.animationPlayers[0];
let oldRelease = retainedFront.release;
let releaseCalled = false;
retainedFront.release = () => {
releaseCalled = true;
};
info("Selecting a node with no animations");
yield selectNode(".still", inspector);
is(controller.animationPlayers.length, 0,
"There are no more AnimationPlayerFront objects");
info("Checking the destroyed AnimationPlayerFront object");
ok(releaseCalled, "The AnimationPlayerFront has been released");
yield oldRelease.call(retainedFront);
});
@@ -7,11 +7,41 @@
// Test that player widgets are displayed right when the animation panel is
// initialized, if the selected node (<body> by default) is animated.
const { ANIMATION_TYPES } = require("devtools/server/actors/animation");
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_body_animation.html");
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["dom.animations-api.core.enabled", true]
]}, resolve);
});
yield addTab(TEST_URL_ROOT + "doc_multiple_animation_types.html");
let {panel} = yield openAnimationInspector();
is(panel.animationsTimelineComponent.animations.length, 1,
"One animation is handled by the timeline after init");
assertAnimationsDisplayed(panel, 1, "One animation is displayed after init");
is(panel.animationsTimelineComponent.animations.length, 3,
"Three animations are handled by the timeline after init");
assertAnimationsDisplayed(panel, 3,
"Three animations are displayed after init");
is(
panel.animationsTimelineComponent
.animationsEl
.querySelectorAll(`.animation.${ANIMATION_TYPES.SCRIPT_ANIMATION}`)
.length,
1,
"One script-generated animation is displayed");
is(
panel.animationsTimelineComponent
.animationsEl
.querySelectorAll(`.animation.${ANIMATION_TYPES.CSS_ANIMATION}`)
.length,
1,
"One CSS animation is displayed");
is(
panel.animationsTimelineComponent
.animationsEl
.querySelectorAll(`.animation.${ANIMATION_TYPES.CSS_TRANSITION}`)
.length,
1,
"One CSS transition is displayed");
});
@@ -0,0 +1,43 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the spacebar key press toggles the toggleAll button state
// when a node with no animation is selected.
// This test doesn't need to test if animations actually pause/resume
// because there's an other test that does this :
// browser_animation_toggle_button_toggles_animation.js
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel, inspector, window, controller} = yield openAnimationInspector();
let {toggleAllButtonEl} = panel;
// select a node without animations
yield selectNode(".still", inspector);
// ensure the focus is on the animation panel
window.focus();
info("Simulate spacebar stroke and check toggleAll button" +
" is in paused state");
// sending the key will lead to a ALL_ANIMATIONS_TOGGLED_EVENT
let onToggled = once(controller, controller.ALL_ANIMATIONS_TOGGLED_EVENT);
EventUtils.sendKey("SPACE", window);
yield onToggled;
ok(toggleAllButtonEl.classList.contains("paused"),
"The toggle all button is in its paused state");
info("Simulate spacebar stroke and check toggleAll button" +
" is in playing state");
// sending the key will lead to a ALL_ANIMATIONS_TOGGLED_EVENT
onToggled = once(controller, controller.ALL_ANIMATIONS_TOGGLED_EVENT);
EventUtils.sendKey("SPACE", window);
yield onToggled;
ok(!toggleAllButtonEl.classList.contains("paused"),
"The toggle all button is in its playing state again");
});
@@ -0,0 +1,40 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the spacebar key press toggles the play/resume button state.
// This test doesn't need to test if animations actually pause/resume
// because there's an other test that does this.
// There are animations in the test page and since, by default, the <body> node
// is selected, animations will be displayed in the timeline, so the timeline
// play/resume button will be displayed
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel, window} = yield openAnimationInspector();
let {playTimelineButtonEl} = panel;
// ensure the focus is on the animation panel
window.focus();
info("Simulate spacebar stroke and check playResume button" +
" is in paused state");
// sending the key will lead to a UI_UPDATE_EVENT
let onUpdated = panel.once(panel.UI_UPDATED_EVENT);
EventUtils.sendKey("SPACE", window);
yield onUpdated;
ok(playTimelineButtonEl.classList.contains("paused"),
"The play/resume button is in its paused state");
info("Simulate spacebar stroke and check playResume button" +
" is in playing state");
// sending the key will lead to a UI_UPDATE_EVENT
onUpdated = panel.once(panel.UI_UPDATED_EVENT);
EventUtils.sendKey("SPACE", window);
yield onUpdated;
ok(!playTimelineButtonEl.classList.contains("paused"),
"The play/resume button is in its play state again");
});
@@ -6,11 +6,10 @@
// Check that the timeline shows correct time graduations in the header.
const {
findOptimalTimeInterval,
TimeScale
} = require("devtools/client/animationinspector/utils");
// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in animation-timeline.js
const {findOptimalTimeInterval, TimeScale} = require("devtools/client/animationinspector/utils");
// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in
// animation-timeline.js
const TIME_GRADUATION_MIN_SPACING = 40;
add_task(function*() {
@@ -22,11 +21,14 @@ add_task(function*() {
info("Find out how many time graduations should there be");
let width = headerEl.offsetWidth;
let scale = width / (TimeScale.maxEndTime - TimeScale.minStartTime);
let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
let minTimeInterval = TIME_GRADUATION_MIN_SPACING * animationDuration / width;
// Note that findOptimalTimeInterval is tested separately in xpcshell test
// test_findOptimalTimeInterval.js, so we assume that it works here.
let interval = findOptimalTimeInterval(scale, TIME_GRADUATION_MIN_SPACING);
let nb = Math.ceil(width / interval);
let interval = findOptimalTimeInterval(minTimeInterval);
let nb = Math.ceil(animationDuration / interval);
is(headerEl.querySelectorAll(".time-tick").length, nb,
"The expected number of time ticks were found");
@@ -34,9 +36,9 @@ add_task(function*() {
info("Make sure graduations are evenly distributed and show the right times");
[...headerEl.querySelectorAll(".time-tick")].forEach((tick, i) => {
let left = parseFloat(tick.style.left);
let expectedPos = i * interval * 100 / width;
let expectedPos = i * interval * 100 / animationDuration;
is(Math.round(left), Math.round(expectedPos),
"Graduation " + i + " is positioned correctly");
`Graduation ${i} is positioned correctly`);
// Note that the distancetoRelativeTime and formatTime functions are tested
// separately in xpcshell test test_timeScale.js, so we assume that they
@@ -44,6 +46,6 @@ add_task(function*() {
let formattedTime = TimeScale.formatTime(
TimeScale.distanceToRelativeTime(expectedPos, width));
is(tick.textContent, formattedTime,
"Graduation " + i + " has the right text content");
`Graduation ${i} has the right text content`);
});
});
@@ -95,11 +95,12 @@ function waitForOutOfBoundScrubber({win, scrubberEl}) {
function waitForScrubberStopped(timeline) {
return new Promise(resolve => {
timeline.on("timeline-data-changed", function onTimelineData(e, {isMoving}) {
if (!isMoving) {
timeline.off("timeline-data-changed", onTimelineData);
resolve();
}
});
timeline.on("timeline-data-changed",
function onTimelineData(e, {isMoving}) {
if (!isMoving) {
timeline.off("timeline-data-changed", onTimelineData);
resolve();
}
});
});
}
@@ -14,6 +14,7 @@ add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {panel, controller} = yield openAnimationInspector();
let players = controller.animationPlayers;
let btn = panel.rewindTimelineButtonEl;
ok(btn, "The rewind button exists");
@@ -24,9 +25,9 @@ add_task(function*() {
info("Check that the scrubber has stopped moving");
yield assertScrubberMoving(panel, false);
ok(controller.animationPlayers.every(({state}) => state.currentTime === 0),
ok(players.every(({state}) => state.currentTime === 0),
"All animations' currentTimes have been set to 0");
ok(controller.animationPlayers.every(({state}) => state.playState === "paused"),
ok(players.every(({state}) => state.playState === "paused"),
"All animations have been paused");
info("Play the animations again");
@@ -41,8 +42,8 @@ add_task(function*() {
info("Check that the scrubber has stopped moving");
yield assertScrubberMoving(panel, false);
ok(controller.animationPlayers.every(({state}) => state.currentTime === 0),
ok(players.every(({state}) => state.currentTime === 0),
"All animations' currentTimes have been set to 0");
ok(controller.animationPlayers.every(({state}) => state.playState === "paused"),
ok(players.every(({state}) => state.playState === "paused"),
"All animations have been paused");
});
@@ -39,14 +39,15 @@ function checkDelayAndName(timelineEl, hasDelay) {
let targetNode = timelineEl.querySelector(".target");
// Check that the delay element does not cause the timeline to overflow.
let delayRect = delay.getBoundingClientRect();
let sidebarWidth = targetNode.getBoundingClientRect().width;
ok(delayRect.x >= sidebarWidth,
let delayLeft = Math.round(delay.getBoundingClientRect().x);
let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width);
ok(delayLeft >= sidebarWidth,
"The delay element isn't displayed over the sidebar");
// Check that the delay is not displayed on top of the name.
let nameLeft = name.getBoundingClientRect().left;
ok(delayRect.right <= nameLeft,
let delayRight = Math.round(delay.getBoundingClientRect().right);
let nameLeft = Math.round(name.getBoundingClientRect().left);
ok(delayRight <= nameLeft,
"The delay element does not span over the name element");
}
}
@@ -1,6 +1,7 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* globals addMessageListener, sendAsyncMessage */
"use strict";
@@ -11,7 +12,8 @@
* @param {Object} data
* - {String} selector The CSS selector to get the node (can be a "super"
* selector).
* - {Number} animationIndex The index of the node's animationPlayers to play or pause
* - {Number} animationIndex The index of the node's animationPlayers to play
* or pause
* - {Boolean} pause True to pause the animation, false to play.
*/
addMessageListener("Test:ToggleAnimationPlayer", function(msg) {
@@ -103,18 +105,18 @@ addMessageListener("Test:GetAnimationPlayerState", function(msg) {
* @param {String} superSelector.
* @return {DOMNode} The node, or null if not found.
*/
function superQuerySelector(superSelector, root=content.document) {
function superQuerySelector(superSelector, root = content.document) {
let frameIndex = superSelector.indexOf("||");
if (frameIndex === -1) {
return root.querySelector(superSelector);
} else {
let rootSelector = superSelector.substring(0, frameIndex).trim();
let childSelector = superSelector.substring(frameIndex+2).trim();
root = root.querySelector(rootSelector);
if (!root || !root.contentWindow) {
return null;
}
return superQuerySelector(childSelector, root.contentWindow.document);
}
let rootSelector = superSelector.substring(0, frameIndex).trim();
let childSelector = superSelector.substring(frameIndex + 2).trim();
root = root.querySelector(rootSelector);
if (!root || !root.contentWindow) {
return null;
}
return superQuerySelector(childSelector, root.contentWindow.document);
}
@@ -22,6 +22,8 @@
<div></div>
<div class="rate"></div>
<script>
"use strict";
var el = document.querySelector(".rate");
var ani = el.getAnimations()[0];
ani.playbackRate = 2;
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.ball {
width: 80px;
height: 80px;
border-radius: 50%;
}
.script-animation {
background: #f06;
}
.css-transition {
background: #006;
transition: background-color 20s;
}
.css-animation {
background: #a06;
animation: flash 10s forwards;
}
@keyframes flash {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
</head>
<body>
<div class="ball script-animation"></div>
<div class="ball css-animation"></div>
<div class="ball css-transition"></div>
<script>
"use strict";
setTimeout(function() {
document.querySelector(".css-transition").style.backgroundColor = "yellow";
}, 0);
document.querySelector(".script-animation").animate([
{opacity: 1, offset: 0},
{opacity: .1, offset: 1}
], {
duration: 10000,
fill: "forwards"
});
</script>
</body>
</html>
@@ -45,6 +45,8 @@
<div class="zero"></div>
<div class="positive"></div>
<script>
"use strict";
var negative = document.querySelector(".negative");
var zero = document.querySelector(".zero");
var positive = document.querySelector(".positive");
@@ -82,6 +82,21 @@
animation: no-compositor 10s cubic-bezier(.57,-0.02,1,.31) forwards;
}
.pseudo {
top: 800px;
left: 10px;
}
.pseudo::before {
content: "";
width: 50%;
height: 50%;
border-radius: 50%;
background: black;
position: absolute;
animation: simple-animation 1s infinite alternate;
}
@keyframes simple-animation {
100% {
transform: translateX(300px);
@@ -112,5 +127,6 @@
<div class="ball long"></div>
<div class="ball negative-delay"></div>
<div class="ball no-compositor"></div>
<div class="ball pseudo"></div>
</body>
</html>
@@ -1,6 +1,7 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
"use strict";
@@ -114,7 +115,7 @@ function getNodeFront(selector, {walker}) {
* to highlight the node upon selection
* @return {Promise} Resolves when the inspector is updated with the new node
*/
var selectNode = Task.async(function*(data, inspector, reason="test") {
var selectNode = Task.async(function*(data, inspector, reason = "test") {
info("Selecting the node for '" + data + "'");
let nodeFront = data;
if (!data._form) {
@@ -258,7 +259,7 @@ function hasSideBarTab(inspector, id) {
* @param {Boolean} useCapture Optional, for add/removeEventListener
* @return A promise that resolves when the event has been handled
*/
function once(target, eventName, useCapture=false) {
function once(target, eventName, useCapture = false) {
info("Waiting for event: '" + eventName + "' on " + target + ".");
let deferred = promise.defer();
@@ -312,7 +313,8 @@ function waitForContentMessage(name) {
* @return {Promise} Resolves to the response data if a response is expected,
* immediately resolves otherwise
*/
function executeInContent(name, data={}, objects={}, expectResponse=true) {
function executeInContent(name, data = {}, objects = {},
expectResponse = true) {
info("Sending message " + name + " to content");
let mm = gBrowser.selectedBrowser.messageManager;
@@ -327,7 +329,8 @@ function executeInContent(name, data={}, objects={}, expectResponse=true) {
/**
* Get the current playState of an animation player on a given node.
*/
var getAnimationPlayerState = Task.async(function*(selector, animationIndex=0) {
var getAnimationPlayerState = Task.async(function*(selector,
animationIndex = 0) {
let playState = yield executeInContent("Test:GetAnimationPlayerState",
{selector, animationIndex});
return playState;
@@ -366,7 +369,6 @@ var waitForAllAnimationTargets = Task.async(function*(panel) {
*/
function* assertScrubberMoving(panel, isMoving) {
let timeline = panel.animationsTimelineComponent;
let scrubberEl = timeline.scrubberEl;
if (isMoving) {
// If we expect the scrubber to move, just wait for a couple of
@@ -446,8 +448,8 @@ function* changeTimelinePlaybackRate(panel, rate) {
// Simulate a mousemove outside of the rate selector area to avoid subsequent
// tests from failing because of unwanted mouseover events.
EventUtils.synthesizeMouseAtCenter(win.document.querySelector("#timeline-toolbar"),
{type: "mousemove"}, win);
EventUtils.synthesizeMouseAtCenter(
win.document.querySelector("#timeline-toolbar"), {type: "mousemove"}, win);
}
/**
@@ -519,6 +521,8 @@ function getKeyframeComponent(panel, animationIndex, propertyName) {
* @return {DOMNode} The keyframe element.
*/
function getKeyframeEl(panel, animationIndex, propertyName, keyframeIndex) {
let keyframeComponent = getKeyframeComponent(panel, animationIndex, propertyName);
return keyframeComponent.keyframesEl.querySelectorAll(".frame")[keyframeIndex];
let keyframeComponent = getKeyframeComponent(panel, animationIndex,
propertyName);
return keyframeComponent.keyframesEl
.querySelectorAll(".frame")[keyframeIndex];
}
@@ -14,66 +14,64 @@ const {findOptimalTimeInterval} = require("devtools/client/animationinspector/ut
// findOptimalTimeInterval function. Each object should have the following
// properties:
// - desc: an optional string that will be printed out
// - timeScale: a number that represents how many pixels is 1ms
// - minSpacing: an optional number that represents the minim space between 2
// time graduations
// - minTimeInterval: a number that represents the minimum time in ms
// that should be displayed in one interval
// - expectedInterval: a number that you expect the findOptimalTimeInterval
// function to return as a result.
// Optionally you can pass a string where `interval` is the calculated
// interval, this string will be eval'd and tested to be truthy.
const TEST_DATA = [{
desc: "With 1px being 1ms and no minSpacing, expect the interval to be the " +
"interval multiple",
timeScale: 1,
minSpacing: undefined,
desc: "With no minTimeInterval, expect the interval to be 0",
minTimeInterval: null,
expectedInterval: 0
}, {
desc: "With a minTimeInterval of 0 ms, expect the interval to be 0",
minTimeInterval: 0,
expectedInterval: 0
}, {
desc: "With a minInterval of 1ms, expect the interval to be the 1ms too",
minTimeInterval: 1,
expectedInterval: 1
}, {
desc: "With a very small minTimeInterval, expect the interval to be 1ms",
minTimeInterval: 1e-31,
expectedInterval: 1
}, {
desc: "With a minInterval of 2.5ms, expect the interval to be 2.5ms too",
minTimeInterval: 2.5,
expectedInterval: 2.5
}, {
desc: "With a minInterval of 5ms, expect the interval to be 5ms too",
minTimeInterval: 5,
expectedInterval: 5
}, {
desc: "With a minInterval of 7ms, expect the interval to be the next " +
"multiple of 5",
minTimeInterval: 7,
expectedInterval: 10
}, {
minTimeInterval: 20,
expectedInterval: 25
}, {
desc: "With 1px being 1ms and a custom minSpacing being a multiple of 25 " +
"expect the interval to be the custom min spacing",
timeScale: 1,
minSpacing: 50,
minTimeInterval: 33,
expectedInterval: 50
}, {
desc: "With 1px being 1ms and a custom minSpacing not being multiple of 25 " +
"expect the interval to be the next multiple of 10",
timeScale: 1,
minSpacing: 26,
expectedInterval: 50
minTimeInterval: 987,
expectedInterval: 1000
}, {
desc: "If 1ms corresponds to a distance that is greater than the min " +
"spacing then, expect the interval to be this distance",
timeScale: 20,
minSpacing: undefined,
expectedInterval: 20
minTimeInterval: 1234,
expectedInterval: 2500
}, {
desc: "If 1ms corresponds to a distance that is greater than the min " +
"spacing then, expect the interval to be this distance, even if it " +
"isn't a multiple of 25",
timeScale: 33,
minSpacing: undefined,
expectedInterval: 33
}, {
desc: "If 1ms is a very small distance, then expect this distance to be " +
"multiplied by 25, 50, 100, 200, etc... until it goes over the min " +
"spacing",
timeScale: 0.001,
minSpacing: undefined,
expectedInterval: 12.8
}, {
desc: "If the time scale is such that we need to iterate more than the " +
"maximum allowed number of iterations, then expect an interval lower " +
"than the minimum one",
timeScale: 1e-31,
minSpacing: undefined,
expectedInterval: "interval < 10"
minTimeInterval: 9800,
expectedInterval: 10000
}];
function run_test() {
for (let {timeScale, desc, minSpacing, expectedInterval} of TEST_DATA) {
do_print("Testing timeScale: " + timeScale + " and minSpacing: " +
minSpacing + ". Expecting " + expectedInterval + ".");
for (let {minTimeInterval, desc, expectedInterval} of TEST_DATA) {
do_print(`Testing minTimeInterval: ${minTimeInterval}.
Expecting ${expectedInterval}.`);
let interval = findOptimalTimeInterval(timeScale, minSpacing);
let interval = findOptimalTimeInterval(minTimeInterval);
if (typeof expectedInterval == "string") {
ok(eval(expectedInterval), desc);
} else {
@@ -6,10 +6,9 @@
"use strict";
var Cu = Components.utils;
const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {formatStopwatchTime} = require("devtools/client/animationinspector/utils");
const TEST_DATA = [{
desc: "Formatting 0",
time: 0,
+41 -43
View File
@@ -8,27 +8,23 @@
const {Cu} = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
var {loader} = Cu.import("resource://devtools/shared/Loader.jsm");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
// How many times, maximum, can we loop before we find the optimal time
// interval in the timeline graph.
const OPTIMAL_TIME_INTERVAL_MAX_ITERS = 100;
// Background time graduations should be multiple of this number of millis.
const TIME_INTERVAL_MULTIPLE = 25;
const TIME_INTERVAL_SCALES = 3;
// The default minimum spacing between time graduations in px.
const TIME_GRADUATION_MIN_SPACING = 10;
// Time graduations should be multiple of one of these number.
const OPTIMAL_TIME_INTERVAL_MULTIPLES = [1, 2.5, 5];
// RGB color for the time interval background.
const TIME_INTERVAL_COLOR = [128, 136, 144];
// byte
const TIME_INTERVAL_OPACITY_MIN = 32;
const TIME_INTERVAL_OPACITY_MIN = 64;
// byte
const TIME_INTERVAL_OPACITY_ADD = 32;
const TIME_INTERVAL_OPACITY_MAX = 96;
const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
@@ -69,12 +65,13 @@ exports.createNode = createNode;
* Given a data-scale, draw the background for a graph (vertical lines) into a
* canvas and set that canvas as an image-element with an ID that can be used
* from CSS.
* @param {Document} document The document where the image-element should be set.
* @param {Document} document The document where the image-element should be
* set.
* @param {String} id The ID for the image-element.
* @param {Number} graphWidth The width of the graph.
* @param {Number} timeScale How many px is 1ms in the graph.
* @param {Number} intervalWidth The width of one interval
*/
function drawGraphElementBackground(document, id, graphWidth, timeScale) {
function drawGraphElementBackground(document, id, graphWidth, intervalWidth) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
@@ -93,17 +90,19 @@ function drawGraphElementBackground(document, id, graphWidth, timeScale) {
// Build new millisecond tick lines...
let [r, g, b] = TIME_INTERVAL_COLOR;
let alphaComponent = TIME_INTERVAL_OPACITY_MIN;
let interval = findOptimalTimeInterval(timeScale);
let opacities = [TIME_INTERVAL_OPACITY_MAX, TIME_INTERVAL_OPACITY_MIN];
// Insert one pixel for each division on each scale.
for (let i = 1; i <= TIME_INTERVAL_SCALES; i++) {
let increment = interval * Math.pow(2, i);
for (let x = 0; x < canvas.width; x += increment) {
let position = x | 0;
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
// Insert one tick line on each interval
for (let i = 0; i <= graphWidth / intervalWidth; i++) {
let x = i * intervalWidth;
// Ensure the last line is drawn on canvas
if (x >= graphWidth) {
x = graphWidth - 0.5;
}
alphaComponent += TIME_INTERVAL_OPACITY_ADD;
let position = x | 0;
let alphaComponent = opacities[i % opacities.length];
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
}
// Flush the image data and cache the waterfall background.
@@ -116,31 +115,30 @@ exports.drawGraphElementBackground = drawGraphElementBackground;
/**
* Find the optimal interval between time graduations in the animation timeline
* graph based on a time scale and a minimum spacing.
* @param {Number} timeScale How many px is 1ms in the graph.
* @param {Number} minSpacing The minimum spacing between 2 graduations,
* defaults to TIME_GRADUATION_MIN_SPACING.
* @return {Number} The optimal interval, in pixels.
* graph based on a minimum time interval
* @param {Number} minTimeInterval Minimum time in ms in one interval
* @return {Number} The optimal interval time in ms
*/
function findOptimalTimeInterval(timeScale,
minSpacing=TIME_GRADUATION_MIN_SPACING) {
let timingStep = TIME_INTERVAL_MULTIPLE;
function findOptimalTimeInterval(minTimeInterval) {
let numIters = 0;
let multiplier = 1;
if (timeScale > minSpacing) {
return timeScale;
if (!minTimeInterval) {
return 0;
}
let interval;
while (true) {
let scaledStep = timeScale * timingStep;
for (let i = 0; i < OPTIMAL_TIME_INTERVAL_MULTIPLES.length; i++) {
interval = OPTIMAL_TIME_INTERVAL_MULTIPLES[i] * multiplier;
if (minTimeInterval <= interval) {
return interval;
}
}
if (++numIters > OPTIMAL_TIME_INTERVAL_MAX_ITERS) {
return scaledStep;
return interval;
}
if (scaledStep < minSpacing) {
timingStep *= 2;
continue;
}
return scaledStep;
multiplier *= 10;
}
}
@@ -163,10 +161,10 @@ function formatStopwatchTime(time) {
let pad = (nb, max) => {
if (nb < max) {
return new Array((max+"").length - (nb+"").length + 1).join("0") + nb;
return new Array((max + "").length - (nb + "").length + 1).join("0") + nb;
}
return nb;
}
};
minutes = pad(minutes, 10);
seconds = pad(seconds, 10);
@@ -301,10 +299,10 @@ var TimeScale = {
// The width for all iterations.
let iterationW = w * (count || 1);
// The start position of the delay.
let delayX = this.durationToDistance((delay < 0 ? 0 : delay) / rate);
let delayX = delay < 0 ? x : this.startTimeToDistance(start);
// The width of the delay.
let delayW = this.durationToDistance(Math.abs(delay) / rate);
// The width of the delay if it is positive, 0 otherwise.
// The width of the delay if it is negative, 0 otherwise.
let negativeDelayW = delay < 0 ? delayW : 0;
return {x, w, iterationW, delayX, delayW, negativeDelayW};
@@ -4,6 +4,7 @@
// Test various GCLI commands
const TEST_URI = "data:text/html;charset=utf-8,gcli-commands";
const HUDService = require("devtools/client/webconsole/hudservice");
function test() {
return Task.spawn(spawnTest).then(finish, helpers.handleError);
@@ -101,6 +101,7 @@ function update(state = initialState, action, emitChange) {
case constants.SET_BREAKPOINT_CONDITION: {
const id = makeLocationId(action.breakpoint.location);
const bp = state.breakpoints[id];
emitChange("breakpoint-condition-updated", bp);
if (action.status === 'start') {
return mergeIn(state, ['breakpoints', id], {
@@ -117,8 +118,10 @@ function update(state = initialState, action, emitChange) {
});
}
else if (action.status === 'error') {
emitChange("breakpoint-removed", bp);
return deleteIn(state, ['breakpoints', id]);
}
break;
}}
+16 -2
View File
@@ -93,6 +93,7 @@ var DebuggerView = {
"breakpoint-enabled": this.addEditorBreakpoint,
"breakpoint-disabled": this.removeEditorBreakpoint,
"breakpoint-removed": this.removeEditorBreakpoint,
"breakpoint-condition-updated": this.renderEditorBreakpointCondition,
"breakpoint-moved": ({ breakpoint, prevLocation }) => {
const selectedSource = queries.getSelectedSource(getState());
const { location } = breakpoint;
@@ -351,13 +352,13 @@ var DebuggerView = {
},
addEditorBreakpoint: function(breakpoint) {
const { location } = breakpoint;
const { location, condition } = breakpoint;
const source = queries.getSelectedSource(this.controller.getState());
if (source &&
source.actor === location.actor &&
!breakpoint.disabled) {
this.editor.addBreakpoint(location.line - 1);
this.editor.addBreakpoint(location.line - 1, condition);
}
},
@@ -370,6 +371,19 @@ var DebuggerView = {
}
},
renderEditorBreakpointCondition: function (breakpoint) {
const { location, condition } = breakpoint;
const source = queries.getSelectedSource(this.controller.getState());
if (source && source.actor === location.actor) {
if (condition) {
this.editor.setBreakpointCondition(location.line - 1);
} else {
this.editor.removeBreakpointCondition(location.line - 1);
}
}
},
/**
* Display the source editor.
*/
+2 -2
View File
@@ -102,8 +102,8 @@ Tools.inspector = {
],
preventClosingOnKey: true,
onkey: function(panel) {
panel.toolbox.highlighterUtils.togglePicker();
onkey: function(panel, toolbox) {
toolbox.highlighterUtils.togglePicker();
},
isTargetSupported: function(target) {
@@ -680,6 +680,9 @@ InspectorPanel.prototype = {
!this.selection.isPseudoElementNode();
let isEditableElement = isSelectionElement &&
!this.selection.isAnonymousNode();
let isDuplicatableElement = isSelectionElement &&
!this.selection.isAnonymousNode() &&
!this.selection.isRoot();
let isScreenshotable = isSelectionElement &&
this.canGetUniqueSelector &&
this.selection.nodeFront.isTreeDisplayed;
@@ -709,6 +712,7 @@ InspectorPanel.prototype = {
// "Copy outer HTML", "Scroll Into View" & "Screenshot Node" as appropriate
let unique = this.panelDoc.getElementById("node-menu-copyuniqueselector");
let screenshot = this.panelDoc.getElementById("node-menu-screenshotnode");
let duplicateNode = this.panelDoc.getElementById("node-menu-duplicatenode");
let copyInnerHTML = this.panelDoc.getElementById("node-menu-copyinner");
let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter");
let scrollIntoView = this.panelDoc.getElementById("node-menu-scrollnodeintoview");
@@ -725,10 +729,20 @@ InspectorPanel.prototype = {
expandAll.removeAttribute("disabled");
}
this._target.actorHasMethod("domwalker", "duplicateNode").then(value => {
duplicateNode.hidden = !value;
});
this._target.actorHasMethod("domnode", "scrollIntoView").then(value => {
scrollIntoView.hidden = !value;
});
if (isDuplicatableElement) {
duplicateNode.removeAttribute("disabled");
}
else {
duplicateNode.setAttribute("disabled", "true");
}
if (isSelectionElement) {
unique.removeAttribute("disabled");
copyInnerHTML.removeAttribute("disabled");
@@ -1239,6 +1253,20 @@ InspectorPanel.prototype = {
this.selection.nodeFront.scrollIntoView();
},
/**
* Duplicate the selected node
*/
duplicateNode: function() {
let selection = this.selection;
if (!selection.isElementNode() ||
selection.isRoot() ||
selection.isAnonymousNode() ||
selection.isPseudoElementNode()) {
return;
}
this.walker.duplicateNode(selection.nodeFront).catch(e => console.error(e));
},
/**
* Delete the selected node.
*/
+3
View File
@@ -110,6 +110,9 @@
<menuitem id="node-menu-screenshotnode"
label="&inspectorScreenshotNode.label;"
oncommand="inspector.screenshotNode()" />
<menuitem id="node-menu-duplicatenode"
label="&inspectorDuplicateNode.label;"
oncommand="inspector.duplicateNode()"/>
<menuitem id="node-menu-delete"
label="&inspectorHTMLDelete.label;"
accesskey="&inspectorHTMLDelete.accesskey;"
@@ -1,10 +1,11 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* global nsContextMenu*/
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
requestLongerTimeout(2);
// Tests that selecting a node using the browser context menu (inspect element)
// or the element picker focuses that node so that the keyboard can be used
// immediately.
@@ -12,22 +13,19 @@
const TEST_URL = "data:text/html;charset=utf8,<div>test element</div>";
add_task(function*() {
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
info("Select the test node with the browser ctx menu");
yield selectWithBrowserMenu(inspector);
yield clickOnInspectMenuItem(testActor, "div");
assertNodeSelected(inspector, "div");
info("Press arrowUp to focus <body> " +
"(which works if the node was focused properly)");
let onNodeHighlighted = toolbox.once("node-highlight");
EventUtils.synthesizeKey("VK_UP", {});
yield waitForChildrenUpdated(inspector);
yield onNodeHighlighted;
yield selectPreviousNodeWithArrowUp(inspector);
assertNodeSelected(inspector, "body");
info("Select the test node with the element picker");
yield selectWithElementPicker(inspector);
yield selectWithElementPicker(inspector, testActor);
assertNodeSelected(inspector, "div");
info("Press arrowUp to focus <body> " +
@@ -44,40 +42,20 @@ function assertNodeSelected(inspector, tagName) {
`The <${tagName}> node is selected`);
}
function* selectWithBrowserMenu(inspector) {
yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
type: "contextmenu",
button: 2
}, gBrowser.selectedBrowser);
// nsContextMenu also requires the popupNode to be set, but we can't set it to
// node under e10s as it's a CPOW, not a DOM node. But under e10s,
// nsContextMenu won't use the property anyway, so just try/catching is ok.
try {
document.popupNode = getNode("div");
} catch (e) {}
let contentAreaContextMenu = document.querySelector("#contentAreaContextMenu");
let contextMenu = new nsContextMenu(contentAreaContextMenu);
yield contextMenu.inspectNode();
contentAreaContextMenu.hidden = true;
contentAreaContextMenu.hidePopup();
contextMenu.hiding();
yield inspector.once("inspector-updated");
function selectPreviousNodeWithArrowUp(inspector) {
let onNodeHighlighted = inspector.toolbox.once("node-highlight");
let onUpdated = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_UP", {});
return Promise.all([onUpdated, onNodeHighlighted]);
}
function* selectWithElementPicker(inspector) {
yield inspector.toolbox.highlighterUtils.startPicker();
function* selectWithElementPicker(inspector, testActor) {
yield startPicker(inspector.toolbox);
yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
type: "mousemove",
}, gBrowser.selectedBrowser);
executeInContent("Test:SynthesizeKey", {
key: "VK_RETURN",
options: {}
}, false);
yield testActor.synthesizeKey({key: "VK_RETURN", options: {}});
yield inspector.once("inspector-updated");
}
@@ -63,7 +63,7 @@ support-files =
[browser_rules_completion-popup-hidden-after-navigation.js]
[browser_rules_content_01.js]
[browser_rules_content_02.js]
skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work with e10s
skip-if = e10s && debug && os == 'win' # Bug 1250058 - Docshell leak on win debug e10s
[browser_rules_context-menu-show-mdn-docs-01.js]
[browser_rules_context-menu-show-mdn-docs-02.js]
[browser_rules_context-menu-show-mdn-docs-03.js]
@@ -19,34 +19,12 @@ const STRINGS = Services.strings
.createBundle("chrome://devtools-shared/locale/styleinspector.properties");
add_task(function*() {
yield addTab("data:text/html;charset=utf-8," + CONTENT);
let tab = yield addTab("data:text/html;charset=utf-8," + CONTENT);
info("Getting the test element");
let element = getNode("span");
let testActor = yield getTestActorWithoutToolbox(tab);
let inspector = yield clickOnInspectMenuItem(testActor, "span");
info("Opening the inspector using the content context-menu");
let onInspectorReady = gDevTools.once("inspector-ready");
document.popupNode = element;
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
let contextMenu = new nsContextMenu(contentAreaContextMenu);
yield contextMenu.inspectNode();
// Clean up context menu:
contextMenu.hiding();
yield onInspectorReady;
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
info("Getting the inspector and making sure it is fully updated");
let inspector = toolbox.getPanel("inspector");
yield inspector.once("inspector-updated");
let view = inspector.ruleview.view;
checkRuleViewContent(view);
checkRuleViewContent(inspector.ruleview.view);
});
function checkRuleViewContent({styleDocument}) {
@@ -81,3 +59,4 @@ function checkRuleViewContent({styleDocument}) {
is(propertyValues.length, 1, "There's only one property value, as expected");
}
}
@@ -217,7 +217,9 @@ var openInspector = Task.async(function*() {
inspector = toolbox.getPanel("inspector");
info("Waiting for the inspector to update");
yield inspector.once("inspector-updated");
if (inspector._updateProgress) {
yield inspector.once("inspector-updated");
}
return {
toolbox: toolbox,
+3 -1
View File
@@ -59,7 +59,8 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
[browser_inspector_highlighter-hover_01.js]
[browser_inspector_highlighter-hover_02.js]
[browser_inspector_highlighter-hover_03.js]
[browser_inspector_highlighter-iframes.js]
[browser_inspector_highlighter-iframes_01.js]
[browser_inspector_highlighter-iframes_02.js]
[browser_inspector_highlighter-inline.js]
[browser_inspector_highlighter-keybinding_01.js]
[browser_inspector_highlighter-keybinding_02.js]
@@ -79,6 +80,7 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
[browser_inspector_iframe-navigation.js]
[browser_inspector_infobar_01.js]
[browser_inspector_initialization.js]
skip-if = e10s && debug && os == 'win' # Bug 1250058 - Docshell leak on win debug e10s
[browser_inspector_inspect-object-element.js]
[browser_inspector_invalidate.js]
[browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
@@ -37,7 +37,7 @@ add_task(function* () {
yield toolbox.highlighter.showBoxModel(body);
info("Waiting for element picker to become active.");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Moving mouse over iframe padding.");
yield moveMouseOver("iframe", 1, 1);
@@ -27,7 +27,7 @@ add_task(function*() {
let innerFrameDiv = ["iframe", "iframe", "div"];
info("Waiting for element picker to activate.");
yield inspector.toolbox.highlighterUtils.startPicker();
yield startPicker(inspector.toolbox);
info("Moving mouse over outerFrameDiv");
yield moveMouseOver(testActor, outerFrameDiv);
@@ -0,0 +1,56 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set 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";
// Test that the highlighter is correctly positioned when switching context
// to an iframe that has an offset from the parent viewport (eg. 100px margin)
const TEST_URI = "data:text/html;charset=utf-8," +
"<div id=\"outer\"></div>" +
"<iframe style='margin:100px' src='data:text/html," +
"<div id=\"inner\">Look I am here!</div>'>";
add_task(function*() {
info("Enable command-button-frames preference setting");
Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URI);
info("Switch to the iframe context.");
yield switchToFrameContext(1, toolbox, inspector);
info("Check navigation was successful.");
let hasOuterNode = yield testActor.hasNode("#outer");
ok(!hasOuterNode, "Check testActor has no access to outer element");
let hasTestNode = yield testActor.hasNode("#inner");
ok(hasTestNode, "Check testActor has access to inner element");
info("Check highlighting is correct after switching iframe context");
yield selectAndHighlightNode("#inner", inspector);
let isHighlightCorrect = yield testActor.assertHighlightedNode("#inner");
ok(isHighlightCorrect, "The selected node is properly highlighted.");
info("Cleanup command-button-frames preferences.");
Services.prefs.clearUserPref("devtools.command-button-frames.enabled");
});
/**
* Helper designed to switch context to another frame at the provided index.
* Returns a promise that will resolve when the navigation is complete.
* @return {Promise}
*/
function* switchToFrameContext(frameIndex, toolbox, inspector) {
// Verify that the frame list button is visible and populated
let frameListBtn = toolbox.doc.getElementById("command-button-frames");
let frameBtns = frameListBtn.firstChild.querySelectorAll("[data-window-id]");
info("Select the iframe in the frame list.");
let newRoot = inspector.once("new-root");
frameBtns[frameIndex].click();
yield newRoot;
yield inspector.once("inspector-updated");
info("Navigation to the iframe is done.");
}
@@ -11,8 +11,7 @@ const TEST_URL = URL_ROOT + "doc_inspector_highlighter_dom.html";
add_task(function*() {
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL);
info("Starting element picker");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Selecting the simple-div1 DIV");
yield moveMouseOver("#simple-div1");
@@ -11,8 +11,7 @@ const TEST_URL = URL_ROOT + "doc_inspector_highlighter_dom.html";
add_task(function*() {
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL);
info("Starting element picker");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
// Previously chosen child memory
info("Testing whether previously chosen child is remembered");
@@ -11,8 +11,7 @@ const TEST_URL = URL_ROOT + "doc_inspector_highlighter_dom.html";
add_task(function*() {
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL);
info("Starting element picker");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Selecting the #another DIV");
yield moveMouseOver("#another");
@@ -23,8 +22,7 @@ add_task(function*() {
is(inspector.selection.nodeFront.id, "another", "The #another node was selected. Passed.");
// Testing cancel-picker command
info("Starting element picker again");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Selecting the ahoy DIV");
yield moveMouseOver("#ahoy");
@@ -12,8 +12,7 @@ const TEST_URL = "data:text/html;charset=utf8,<div></div>";
add_task(function*() {
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL);
info("Start the element picker");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Start using the picker by hovering over nodes");
let onHover = toolbox.once("picker-node-hovered");
@@ -10,8 +10,7 @@ const TEST_URL = URL_ROOT + "doc_inspector_highlighter_xbl.xul";
add_task(function*() {
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL);
info("Starting element picker");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Selecting the scale");
yield moveMouseOver("#scale");
@@ -14,7 +14,7 @@ add_task(function* () {
let { inspector, toolbox, testActor } = yield openInspectorForURL(TEST_URI);
info("Starting element picker.");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Waiting for highlighter to activate.");
let highlighterShowing = toolbox.once("highlighter-ready");
@@ -114,32 +114,3 @@ function* testBreadcrumbs(selector, inspector) {
ok(button, "A crumbs is checked=true");
is(button.getAttribute("tooltiptext"), expectedText, "Crumb refers to the right node");
}
function* clickOnInspectMenuItem(testActor, selector) {
info("Showing the contextual menu on node " + selector);
yield testActor.synthesizeMouse({
selector: selector,
center: true,
options: {type: "contextmenu", button: 2}
});
// nsContextMenu also requires the popupNode to be set, but we can't set it to
// node under e10s as it's a CPOW, not a DOM node. But under e10s,
// nsContextMenu won't use the property anyway, so just try/catching is ok.
try {
document.popupNode = content.document.querySelector(selector);
} catch (e) {}
let contentAreaContextMenu = document.querySelector("#contentAreaContextMenu");
let contextMenu = new nsContextMenu(contentAreaContextMenu);
info("Triggering inspect action and hiding the menu.");
yield contextMenu.inspectNode();
contentAreaContextMenu.hidden = true;
contentAreaContextMenu.hidePopup();
contextMenu.hiding();
info("Waiting for inspector to update.");
yield getActiveInspector().once("inspector-updated");
}
@@ -17,7 +17,7 @@ add_task(function* () {
yield selectNode("p", inspector);
info("Inspector displayed and ready, starting the picker.");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Destroying the toolbox.");
yield toolbox.destroy();
@@ -15,7 +15,7 @@ add_task(function* () {
let pickerStopped = toolbox.once("picker-stopped");
info("Starting the inspector picker");
yield toolbox.highlighterUtils.startPicker();
yield startPicker(toolbox);
info("Selecting another tool than the inspector in the toolbox");
yield toolbox.selectNextTool();
+44
View File
@@ -89,6 +89,20 @@ function getNode(nodeOrSelector, options = {}) {
return nodeOrSelector;
}
/**
* Start the element picker and focus the content window.
* @param {Toolbox} toolbox
*/
var startPicker = Task.async(function*(toolbox) {
info("Start the element picker");
yield toolbox.highlighterUtils.startPicker();
// Make sure the content window is focused since the picker does not focus
// the content window by default.
yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
content.focus();
});
});
/**
* Highlight a node and set the inspector's current selection to the node or
* the first match of the given css selector.
@@ -159,6 +173,36 @@ function getActiveInspector() {
return gDevTools.getToolbox(target).getPanel("inspector");
}
/**
* Right click on a node in the test page and click on the inspect menu item.
* @param {TestActor}
* @param {String} selector The selector for the node to click on in the page.
* @return {Promise} Resolves to the inspector when it has opened and is updated
*/
var clickOnInspectMenuItem = Task.async(function*(testActor, selector) {
info("Showing the contextual menu on node " + selector);
let contentAreaContextMenu = document.querySelector("#contentAreaContextMenu");
let contextOpened = once(contentAreaContextMenu, "popupshown");
yield testActor.synthesizeMouse({
selector: selector,
center: true,
options: {type: "contextmenu", button: 2}
});
yield contextOpened;
info("Triggering the inspect action");
yield gContextMenu.inspectNode();
info("Hiding the menu");
let contextClosed = once(contentAreaContextMenu, "popuphidden");
contentAreaContextMenu.hidePopup();
yield contextClosed;
return getActiveInspector();
});
/**
* Open the toolbox, with the inspector tool visible, and the one of the sidebar
* tabs selected.
+3 -4
View File
@@ -194,10 +194,7 @@ devtools.jar:
skin/images/command-measure@2x.png (themes/images/command-measure@2x.png)
skin/markup.css (themes/markup.css)
skin/images/editor-error.png (themes/images/editor-error.png)
skin/images/editor-breakpoint.png (themes/images/editor-breakpoint.png)
skin/images/editor-breakpoint@2x.png (themes/images/editor-breakpoint@2x.png)
skin/images/editor-debug-location.png (themes/images/editor-debug-location.png)
skin/images/editor-debug-location@2x.png (themes/images/editor-debug-location@2x.png)
skin/images/breakpoint.svg (themes/images/breakpoint.svg)
skin/webconsole.css (themes/webconsole.css)
skin/images/webconsole.svg (themes/images/webconsole.svg)
skin/images/breadcrumbs-divider@2x.png (themes/images/breadcrumbs-divider@2x.png)
@@ -316,6 +313,7 @@ devtools.jar:
skin/images/tool-memory-active.svg (themes/images/tool-memory-active.svg)
skin/images/close.png (themes/images/close.png)
skin/images/close@2x.png (themes/images/close@2x.png)
skin/images/clear.svg (themes/images/clear.svg)
skin/images/vview-delete.png (themes/images/vview-delete.png)
skin/images/vview-delete@2x.png (themes/images/vview-delete@2x.png)
skin/images/vview-edit.png (themes/images/vview-edit.png)
@@ -350,3 +348,4 @@ devtools.jar:
skin/images/security-state-local.svg (themes/images/security-state-local.svg)
skin/images/security-state-secure.svg (themes/images/security-state-secure.svg)
skin/images/security-state-weak.svg (themes/images/security-state-weak.svg)
skin/images/diff.svg (themes/images/diff.svg)
@@ -15,10 +15,6 @@
sidebar tab -->
<!ENTITY animationInspectorTitle "Animations">
<!-- LOCALIZATION NOTE (invalidElement): This is the label shown in the panel
when an invalid node is currently selected in the inspector. -->
<!ENTITY invalidElement "No animations were found for the current element.">
<!-- LOCALIZATION NOTE (selectElement): This is the label shown in the panel
when an invalid node is currently selected in the inspector, to invite the
user to select a new node by clicking on the element-picker icon. -->
@@ -10,6 +10,18 @@
# A good criteria is the language in which you'd find the best
# documentation on web development on the web.
# LOCALIZATION NOTE (panel.invalidElementSelected):
# This is the label shown in the panel when an invalid node is currently
# selected in the inspector (i.e. a non-element node or a node that is not
# animated).
panel.invalidElementSelected=No animations were found for the current element.
# LOCALIZATION NOTE (panel.pseudoElementSelected):
# This is the label shown in the panel when a pseudo-element is currently
# selected in the inspector (pseudo-elements can be animated, but the tool
# doesn't yet support them).
panel.pseudoElementSelected=Animated pseudo-elements are not supported yet.
# LOCALIZATION NOTE (player.animationNameLabel):
# This string is displayed in each animation player widget. It is the label
# displayed before the animation name.
@@ -61,14 +73,35 @@ player.timeLabel=%Ss
# LOCALIZATION NOTE (player.playbackRateLabel):
# This string is displayed in each animation player widget, as the label of
# drop-down list items that can be used to change the rate at which the
# animation runs (1x being the default, 2x being twice as fast).
player.playbackRateLabel=%Sx
# animation runs (1× being the default, 2× being twice as fast).
player.playbackRateLabel=%S×
# LOCALIZATION NOTE (player.runningOnCompositorTooltip):
# This string is displayed as a tooltip for the icon that indicates that the
# animation is running on the compositor thread.
player.runningOnCompositorTooltip=This animation is running on compositor thread
# LOCALIZATION NOTE (timeline.rateSelectorTooltip):
# This string is displayed in the timeline toolbar, as the tooltip of the
# drop-down list that can be used to change the rate at which the animations
# run.
timeline.rateSelectorTooltip=Set the animations playback rates
# LOCALIZATION NOTE (timeline.pauseResumeButtonTooltip):
# This string is displayed in the timeline toolbar, as the tooltip of the
# pause/resume button that can be used to pause or resume the animations
timeline.pausedButtonTooltip=Resume the animations
# LOCALIZATION NOTE (timeline.pauseResumeButtonTooltip):
# This string is displayed in the timeline toolbar, as the tooltip of the
# pause/resume button that can be used to pause or resume the animations
timeline.resumedButtonTooltip=Pause the animations
# LOCALIZATION NOTE (timeline.rewindButtonTooltip):
# This string is displayed in the timeline toolbar, as the tooltip of the
# rewind button that can be used to rewind the animations
timeline.rewindButtonTooltip=Rewind the animations
# LOCALIZATION NOTE (timeline.timeGraduationLabel):
# This string is displayed at the top of the animation panel, next to each time
# graduation, to indicate what duration (in milliseconds) this graduation
@@ -87,6 +120,17 @@ timeline.cssanimation.nameLabel=%S - CSS Animation
# %S will be replaced by the name of the transition at run-time.
timeline.csstransition.nameLabel=%S - CSS Transition
# LOCALIZATION NOTE (timeline.scriptanimation.nameLabel):
# This string is displayed in a tooltip of the animation panel that is shown
# when hovering over the name of a script-generated animation in the timeline UI.
# %S will be replaced by the name of the animation at run-time.
timeline.scriptanimation.nameLabel=%S - Script Animation
# LOCALIZATION NOTE (timeline.scriptanimation.unnamedLabel):
# This string is displayed in a tooltip of the animation panel that is shown
# when hovering over an unnamed script-generated animation in the timeline UI.
timeline.scriptanimation.unnamedLabel=Script Animation
# LOCALIZATION NOTE (timeline.unknown.nameLabel):
# This string is displayed in a tooltip of the animation panel that is shown
# when hovering over the name of an unknown animation type in the timeline UI.
@@ -150,3 +150,8 @@
shown in the inspector contextual-menu for the item that lets users take
a screenshot of the currently selected node. -->
<!ENTITY inspectorScreenshotNode.label "Screenshot Node">
<!-- LOCALIZATION NOTE (inspectorDuplicateNode.label): This is the label
shown in the inspector contextual-menu for the item that lets users
duplicate the currently selected node. -->
<!ENTITY inspectorDuplicateNode.label "Duplicate Node">
@@ -26,6 +26,10 @@ jit.optimizationFailure=Optimization failed
# example: 30 samples
jit.samples=#1 sample;#1 samples
# LOCALIZATION NOTE (jit.empty):
# This string is displayed when there are no JIT optimizations to display.
jit.empty=No JIT optimizations recorded for this frame.
# LOCALIZATION NOTE (jit.types):
# This string is displayed for the group of Ion Types in the optimizations view.
jit.types=Types
# LOCALIZATION NOTE (jit.attempts):
# This string is displayed for the group of optimization attempts in the optimizations view.
jit.attempts=Attempts

Some files were not shown because too many files have changed in this diff Show More