Merge m-c to m-i

MozReview-Commit-ID: 322JHuSlk86
This commit is contained in:
Phil Ringnalda 2016-12-20 20:21:11 -08:00
commit 3b8c995f23
134 changed files with 5746 additions and 12160 deletions

View File

@ -843,10 +843,6 @@ pref("media.webspeech.synth.enabled", true);
// Enable Web Speech recognition API
pref("media.webspeech.recognition.enable", true);
// Downloads API
pref("dom.mozDownloads.enabled", true);
pref("dom.downloads.max_retention_days", 7);
// External Helper Application Handling
//
// All external helper application handling can require the docshell to be

View File

@ -49,6 +49,7 @@ pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LO
pref("extensions.webservice.discoverURL", "https://discovery.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
pref("extensions.getAddons.recommended.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/list/recommended/all/%MAX_RESULTS%/%OS%/%VERSION%?src=firefox");
pref("extensions.getAddons.link.url", "https://addons.mozilla.org/%LOCALE%/firefox/");
pref("extensions.getAddons.themes.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes/?src=firefox");
pref("extensions.update.autoUpdateDefault", true);

View File

@ -697,18 +697,6 @@ nsDefaultCommandLineHandler.prototype = {
/* nsICommandLineHandler */
handle : function dch_handle(cmdLine) {
// The -url flag is inserted by the operating system when the default
// application handler is used. We check for default browser to remove
// instances where users explicitly decide to "open with" the browser.
// Note that users who launch firefox manually with the -url flag will
// get erroneously counted.
try {
if (cmdLine.findFlag("url", false) &&
ShellService.isDefaultBrowser(false, false)) {
Services.telemetry.getHistogramById("FX_STARTUP_EXTERNAL_CONTENT_HANDLER").add();
}
} catch (e) {}
var urilist = [];
if (AppConstants.platform == "win") {

View File

@ -121,6 +121,16 @@ var gAdvancedPane = {
return;
var advancedPrefs = document.getElementById("advancedPrefs");
var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
// tabSelectionChanged gets called twice due to the selectedIndex being set
// by both the selectedItem and selectedPanel callstacks. This guard is used
// to prevent double-counting in Telemetry.
if (preference.valueFromPreferences != advancedPrefs.selectedIndex) {
Services.telemetry
.getHistogramById("FX_PREFERENCES_CATEGORY_OPENED")
.add(telemetryBucketForCategory("advanced"));
}
preference.valueFromPreferences = advancedPrefs.selectedIndex;
},

View File

@ -123,6 +123,36 @@ function init_dynamic_padding() {
document.documentElement.appendChild(mediaStyle);
}
function telemetryBucketForCategory(category) {
switch (category) {
case "general":
case "search":
case "content":
case "applications":
case "privacy":
case "security":
case "sync":
return category;
case "advanced":
let advancedPaneTabs = document.getElementById("advancedPrefs");
switch (advancedPaneTabs.selectedTab.id) {
case "generalTab":
return "advancedGeneral";
case "dataChoicesTab":
return "advancedDataChoices";
case "networkTab":
return "advancedNetwork";
case "updateTab":
return "advancedUpdates";
case "encryptionTab":
return "advancedCerts";
}
// fall-through for unknown.
default:
return "unknown";
}
}
function onHashChange() {
gotoPref();
}
@ -151,9 +181,9 @@ function gotoPref(aCategory) {
throw ex;
}
let newHash = internalPrefCategoryNameToFriendlyName(category);
let friendlyName = internalPrefCategoryNameToFriendlyName(category);
if (gLastHash || category != kDefaultCategoryInternalName) {
document.location.hash = newHash;
document.location.hash = friendlyName;
}
// Need to set the gLastHash before setting categories.selectedItem since
// the categories 'select' event will re-enter the gotoPref codepath.
@ -163,6 +193,10 @@ function gotoPref(aCategory) {
search(category, "data-category");
let mainContent = document.querySelector(".main-content");
mainContent.scrollTop = 0;
Services.telemetry
.getHistogramById("FX_PREFERENCES_CATEGORY_OPENED")
.add(telemetryBucketForCategory(friendlyName));
}
function search(aQuery, aAttribute) {

View File

@ -81,6 +81,7 @@ included_inclnames_to_ignore = set([
'selfhosted.out.h', # generated in $OBJDIR
'shellmoduleloader.out.h', # generated in $OBJDIR
'unicode/timezone.h', # ICU
'unicode/plurrule.h', # ICU
'unicode/ucal.h', # ICU
'unicode/uclean.h', # ICU
'unicode/ucol.h', # ICU
@ -90,6 +91,7 @@ included_inclnames_to_ignore = set([
'unicode/unorm2.h', # ICU
'unicode/unum.h', # ICU
'unicode/unumsys.h', # ICU
'unicode/upluralrules.h', # ICU
'unicode/ustring.h', # ICU
'unicode/utypes.h', # ICU
'vtune/VTuneWrapper.h' # VTune

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<div id=a />
<script>
addEventListener("DOMContentLoaded", function(){
a.animate([{"transform": "matrix3d(25,8788,-69,-24,-3,85,52,3,63,0,12,36810,-68,15,82,0) rotate(77rad)"}],
{fill: "both", iterationStart: 45, iterationComposite: "accumulate"});
});
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<style>
div {
width: 100px;
height: 100px;
background-color: blue;
transform: rotate(45deg);
}
</style>
<div id="div"></div>
<script>
addEventListener('DOMContentLoaded', function (){
var target = document.getElementById('div');
target.animate([{ transform: 'translateX(100px)', composite: 'accumulate' },
{ transform: 'none' }],
1000);
});
</script>

View File

@ -13,3 +13,5 @@ pref(dom.animations-api.core.enabled,true) load 1277272-1.html
pref(dom.animations-api.core.enabled,true) load 1290535-1.html
pref(dom.animations-api.core.enabled,true) load 1304886-1.html
pref(dom.animations-api.core.enabled,true) load 1322382-1.html
pref(dom.animations-api.core.enabled,true) load 1323114-1.html
pref(dom.animations-api.core.enabled,true) load 1323114-2.html

View File

@ -0,0 +1,252 @@
<!doctype html>
<meta charset=utf-8>
<title>Tests for CSS animation event dispatch</title>
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/>
<script src="../testcommon.js"></script>
<style>
@keyframes anim {
from { margin-left: 0px; }
to { margin-left: 100px; }
}
</style>
<body>
<script>
'use strict';
/**
* Helper class to record the elapsedTime member of each event.
* The EventWatcher class in testharness.js allows us to wait on
* multiple events in a certain order but only records the event
* parameters of the most recent event.
*/
function AnimationEventHandler(target) {
this.target = target;
this.target.onanimationstart = function(evt) {
this.animationstart = evt.elapsedTime;
}.bind(this);
this.target.onanimationiteration = function(evt) {
this.animationiteration = evt.elapsedTime;
}.bind(this);
this.target.onanimationend = function(evt) {
this.animationend = evt.elapsedTime;
}.bind(this);
}
AnimationEventHandler.prototype.clear = function() {
this.animationstart = undefined;
this.animationiteration = undefined;
this.animationend = undefined;
}
function setupAnimation(t, animationStyle) {
var div = addDiv(t, { style: "animation: " + animationStyle });
var watcher = new EventWatcher(t, div, [ 'animationstart',
'animationiteration',
'animationend' ]);
var handler = new AnimationEventHandler(div);
var animation = div.getAnimations()[0];
return [animation, watcher, handler, div];
}
promise_test(function(t) {
// Add 1ms delay to ensure that the delay is not included in the elapsedTime.
const [animation, watcher] = setupAnimation(t, 'anim 100s 1ms');
return watcher.wait_for('animationstart').then(function(evt) {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Idle -> Active');
promise_test(function(t) {
const [animation, watcher, handler] = setupAnimation(t, 'anim 100s');
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function() {
assert_equals(handler.animationstart, 0.0);
assert_equals(handler.animationend, 100);
});
}, 'Idle -> After');
promise_test(function(t) {
const [animation, watcher, handler] =
setupAnimation(t, 'anim 100s 100s paused');
return animation.ready.then(function() {
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for('animationstart');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Before -> Active');
promise_test(function(t) {
const [animation, watcher, handler] =
setupAnimation(t, 'anim 100s 100s paused');
return animation.ready.then(function() {
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart', 'animationend' ]);
}).then(function(evt) {
assert_equals(handler.animationstart, 0.0);
assert_equals(handler.animationend, 100.0);
});
}, 'Before -> After');
promise_test(function(t) {
const [animation, watcher, handler] =
setupAnimation(t, 'anim 100s 100s paused');
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for('animationstart').then(function() {
// Seek to Before phase.
animation.currentTime = 0;
return watcher.wait_for('animationend');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Before');
promise_test(function(t) {
const [animation, watcher, handler] = setupAnimation(t, 'anim 100s paused');
return watcher.wait_for('animationstart').then(function(evt) {
// Seek to After phase.
animation.finish();
return watcher.wait_for('animationend');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 100.0);
});
}, 'Active -> After');
promise_test(function(t) {
const [animation, watcher, handler] =
setupAnimation(t, 'anim 100s 100s paused');
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function() {
// Seek to Before phase.
animation.currentTime = 0;
handler.clear();
return watcher.wait_for([ 'animationstart', 'animationend' ]);
}).then(function() {
assert_equals(handler.animationstart, 100.0);
assert_equals(handler.animationend, 0.0);
});
}, 'After -> Before');
promise_test(function(t) {
const [animation, watcher, handler] =
setupAnimation(t, 'anim 100s 100s paused');
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function() {
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
handler.clear();
return watcher.wait_for('animationstart');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 100.0);
});
}, 'After -> Active');
promise_test(function(t) {
const [animation, watcher, handler]
= setupAnimation(t, 'anim 100s 100s 3 paused');
return animation.ready.then(function() {
// Seek to iteration 0 (no animationiteration event should be dispatched)
animation.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for('animationstart');
}).then(function(evt) {
// Seek to iteration 2
animation.currentTime = 300 * MS_PER_SEC;
handler.clear();
return watcher.wait_for('animationiteration');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 200);
// Seek to After phase (no animationiteration event should be dispatched)
animation.currentTime = 400 * MS_PER_SEC;
return watcher.wait_for('animationend');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 300);
});
}, 'Active -> Active (forwards)');
promise_test(function(t) {
const [animation, watcher, handler] = setupAnimation(t, 'anim 100s 100s 3');
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function() {
// Seek to iteration 2 (no animationiteration event should be dispatched)
animation.pause();
animation.currentTime = 300 * MS_PER_SEC;
return watcher.wait_for('animationstart');
}).then(function() {
// Seek to mid of iteration 0 phase.
animation.currentTime = 200 * MS_PER_SEC;
return watcher.wait_for('animationiteration');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 200.0);
// Seek to before phase (no animationiteration event should be dispatched)
animation.currentTime = 0;
return watcher.wait_for('animationend');
});
}, 'Active -> Active (backwards)');
promise_test(function(t) {
const [animation, watcher, handler, div] =
setupAnimation(t, 'anim 100s paused');
return watcher.wait_for('animationstart').then(function(evt) {
// Seek to Idle phase.
div.style.display = 'none';
flushComputedStyle(div);
// FIXME: bug 1302648: Add test for animationcancel event here.
// Restart this animation.
div.style.display = '';
return watcher.wait_for('animationstart');
});
}, 'Active -> Idle -> Active: animationstart is fired by restarting animation');
promise_test(function(t) {
const [animation, watcher, handler, div] =
setupAnimation(t, 'anim 100s 100s 2 paused');
// Make After.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function(evt) {
animation.playbackRate = -1;
return watcher.wait_for('animationstart');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 200);
// Seek to 1st iteration
animation.currentTime = 200 * MS_PER_SEC - 1;
return watcher.wait_for('animationiteration');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 100);
// Seek to before
animation.currentTime = 100 * MS_PER_SEC - 1;
return watcher.wait_for('animationend');
}).then(function(evt) {
assert_equals(evt.elapsedTime, 0);
assert_equals(animation.playState, 'running'); // delay
});
}, 'Negative playbackRate sanity test(Before -> Active -> Before)');
done();
</script>
</body>
</html>

View File

@ -0,0 +1,160 @@
<!doctype html>
<meta charset=utf-8>
<title>Tests for CSS animation event order</title>
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/>
<script src="../testcommon.js"></script>
<style>
@keyframes anim {
from { margin-left: 0px; }
to { margin-left: 100px; }
}
</style>
<body>
<script type='text/javascript'>
'use strict';
/**
* Asserts that the set of actual and received events match.
* @param actualEvents An array of the received AnimationEvent objects.
* @param expectedEvents A series of array objects representing the expected
* events, each having the form:
* [ event type, target element, elapsed time ]
*/
function checkEvents(actualEvents, ...expectedEvents) {
assert_equals(actualEvents.length, expectedEvents.length,
`Number of actual events (${actualEvents.length}: \
${actualEvents.map(event => event.type).join(', ')}) should match expected \
events (${expectedEvents.map(event => event.type).join(', ')})`);
actualEvents.forEach((actualEvent, i) => {
assert_equals(expectedEvents[i][0], actualEvent.type,
'Event type should match');
assert_equals(expectedEvents[i][1], actualEvent.target,
'Event target should match');
assert_equals(expectedEvents[i][2], actualEvent.elapsedTime,
'Event\'s elapsed time should match');
});
}
function setupAnimation(t, animationStyle, receiveEvents) {
const div = addDiv(t, { style: "animation: " + animationStyle });
const watcher = new EventWatcher(t, div, [ 'animationstart',
'animationiteration',
'animationend' ]);
['start', 'iteration', 'end'].forEach(name => {
div['onanimation' + name] = function(evt) {
receiveEvents.push({ type: evt.type,
target: evt.target,
elapsedTime: evt.elapsedTime });
}.bind(this);
});
const animation = div.getAnimations()[0];
return [animation, watcher, div];
}
promise_test(function(t) {
let events = [];
const [animation1, watcher1, div1] =
setupAnimation(t, 'anim 100s 2 paused', events);
const [animation2, watcher2, div2] =
setupAnimation(t, 'anim 100s 2 paused', events);
return Promise.all([ watcher1.wait_for('animationstart'),
watcher2.wait_for('animationstart') ]).then(function() {
checkEvents(events, ['animationstart', div1, 0],
['animationstart', div2, 0]);
events.length = 0; // Clear received event array
animation1.currentTime = 100 * MS_PER_SEC;
animation2.currentTime = 100 * MS_PER_SEC;
return Promise.all([ watcher1.wait_for('animationiteration'),
watcher2.wait_for('animationiteration') ]);
}).then(function() {
checkEvents(events, ['animationiteration', div1, 100],
['animationiteration', div2, 100]);
events.length = 0; // Clear received event array
animation1.finish();
animation2.finish();
return Promise.all([ watcher1.wait_for('animationend'),
watcher2.wait_for('animationend') ]);
}).then(function() {
checkEvents(events, ['animationend', div1, 200],
['animationend', div2, 200]);
});
}, 'Test same events are ordered by elements.');
promise_test(function(t) {
let events = [];
const [animation1, watcher1, div1] =
setupAnimation(t, 'anim 200s 400s', events);
const [animation2, watcher2, div2] =
setupAnimation(t, 'anim 300s 2', events);
return watcher2.wait_for('animationstart').then(function(evt) {
animation1.currentTime = 400 * MS_PER_SEC;
animation2.currentTime = 400 * MS_PER_SEC;
events.length = 0; // Clear received event array
return Promise.all([ watcher1.wait_for('animationstart'),
watcher2.wait_for('animationiteration') ]);
}).then(function() {
checkEvents(events, ['animationiteration', div2, 300],
['animationstart', div1, 0]);
});
}, 'Test start and iteration events are ordered by time.');
promise_test(function(t) {
let events = [];
const [animation1, watcher1, div1] =
setupAnimation(t, 'anim 150s', events);
const [animation2, watcher2, div2] =
setupAnimation(t, 'anim 100s 2', events);
return Promise.all([ watcher1.wait_for('animationstart'),
watcher2.wait_for('animationstart') ]).then(function() {
animation1.currentTime = 150 * MS_PER_SEC;
animation2.currentTime = 150 * MS_PER_SEC;
events.length = 0; // Clear received event array
return Promise.all([ watcher1.wait_for('animationend'),
watcher2.wait_for('animationiteration') ]);
}).then(function() {
checkEvents(events, ['animationiteration', div2, 100],
['animationend', div1, 150]);
});
}, 'Test iteration and end events are ordered by time.');
promise_test(function(t) {
let events = [];
const [animation1, watcher1, div1] =
setupAnimation(t, 'anim 100s 100s', events);
const [animation2, watcher2, div2] =
setupAnimation(t, 'anim 100s 2', events);
animation1.finish();
animation2.finish();
return Promise.all([ watcher1.wait_for([ 'animationstart',
'animationend' ]),
watcher2.wait_for([ 'animationstart',
'animationend' ]) ]).then(function() {
checkEvents(events, ['animationstart', div2, 0],
['animationstart', div1, 0],
['animationend', div1, 100],
['animationend', div2, 200]);
});
}, 'Test start and end events are sorted correctly when fired simultaneously');
done();
</script>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
'use strict';
setup({explicit_done: true});
SpecialPowers.pushPrefEnv(
{ "set": [["dom.animations-api.core.enabled", true]]},
function() {
window.open("file_event-dispatch.html");
});
</script>
</html>

View File

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
'use strict';
setup({explicit_done: true});
SpecialPowers.pushPrefEnv(
{ "set": [["dom.animations-api.core.enabled", true]]},
function() {
window.open("file_event-order.html");
});
</script>
</html>

View File

@ -19,6 +19,8 @@ support-files =
css-animations/file_document-get-animations.html
css-animations/file_effect-target.html
css-animations/file_element-get-animations.html
css-animations/file_event-dispatch.html
css-animations/file_event-order.html
css-animations/file_keyframeeffect-getkeyframes.html
css-animations/file_pseudoElement-get-animations.html
css-transitions/file_animation-cancel.html
@ -74,6 +76,8 @@ support-files =
[css-animations/test_document-get-animations.html]
[css-animations/test_effect-target.html]
[css-animations/test_element-get-animations.html]
[css-animations/test_event-dispatch.html]
[css-animations/test_event-order.html]
[css-animations/test_keyframeeffect-getkeyframes.html]
[css-animations/test_pseudoElement-get-animations.html]
[css-transitions/test_animation-cancel.html]

View File

@ -102,7 +102,6 @@
#include "nsIDOMDocumentType.h"
#include "nsGenericHTMLElement.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsContentCreatorFunctions.h"
#include "nsIControllers.h"
#include "nsView.h"

View File

@ -93,7 +93,6 @@
#include "nsICategoryManager.h"
#include "nsGenericHTMLElement.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsContentCreatorFunctions.h"
#include "nsIControllers.h"
#include "nsView.h"
@ -264,12 +263,11 @@ nsIContent::GetDesiredIMEState()
return IMEState(IMEState::DISABLED);
}
nsIEditor* editor = nsContentUtils::GetHTMLEditor(pc);
nsCOMPtr<nsIEditorIMESupport> imeEditor = do_QueryInterface(editor);
if (!imeEditor) {
if (!editor) {
return IMEState(IMEState::DISABLED);
}
IMEState state;
imeEditor->GetPreferredIMEState(&state);
editor->GetPreferredIMEState(&state);
return state;
}

View File

@ -61,7 +61,6 @@
#include "nsIDOMMutationEvent.h"
#include "nsIDOMNodeList.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsILinkHandler.h"
#include "mozilla/dom/NodeInfo.h"
#include "mozilla/dom/NodeInfoInlines.h"

View File

@ -1,508 +0,0 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
Cu.import("resource://gre/modules/DownloadsIPC.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
"@mozilla.org/telephony/volume-service;1",
"nsIVolumeService");
/**
* The content process implementations of navigator.mozDownloadManager and its
* DOMDownload download objects. Uses DownloadsIPC.jsm to communicate with
* DownloadsAPI.jsm in the parent process.
*/
function debug(aStr) {
#ifdef MOZ_DEBUG
dump("-*- DownloadsAPI.js : " + aStr + "\n");
#endif
}
function DOMDownloadManagerImpl() {
debug("DOMDownloadManagerImpl constructor");
}
DOMDownloadManagerImpl.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
// nsIDOMGlobalPropertyInitializer implementation
init: function(aWindow) {
debug("DownloadsManager init");
this.initDOMRequestHelper(aWindow,
["Downloads:Added",
"Downloads:Removed"]);
},
uninit: function() {
debug("uninit");
downloadsCache.evict(this._window);
},
set ondownloadstart(aHandler) {
this.__DOM_IMPL__.setEventHandler("ondownloadstart", aHandler);
},
get ondownloadstart() {
return this.__DOM_IMPL__.getEventHandler("ondownloadstart");
},
getDownloads: function() {
debug("getDownloads()");
return this.createPromise(function (aResolve, aReject) {
DownloadsIPC.getDownloads().then(
function(aDownloads) {
// Turn the list of download objects into DOM objects and
// send them.
let array = new this._window.Array();
for (let id in aDownloads) {
let dom = createDOMDownloadObject(this._window, aDownloads[id]);
array.push(this._prepareForContent(dom));
}
aResolve(array);
}.bind(this),
function() {
aReject("GetDownloadsError");
}
);
}.bind(this));
},
clearAllDone: function() {
debug("clearAllDone()");
// This is a void function; we just kick it off. No promises, etc.
DownloadsIPC.clearAllDone();
},
remove: function(aDownload) {
debug("remove " + aDownload.url + " " + aDownload.id);
return this.createPromise(function (aResolve, aReject) {
if (!downloadsCache.has(this._window, aDownload.id)) {
debug("no download " + aDownload.id);
aReject("InvalidDownload");
return;
}
DownloadsIPC.remove(aDownload.id).then(
function(aResult) {
let dom = createDOMDownloadObject(this._window, aResult);
// Change the state right away to not race against the update message.
dom.wrappedJSObject.state = "finalized";
aResolve(this._prepareForContent(dom));
}.bind(this),
function() {
aReject("RemoveError");
}
);
}.bind(this));
},
adoptDownload: function(aAdoptDownloadDict) {
// Our AdoptDownloadDict only includes simple types, which WebIDL enforces.
// We have no object/any types so we do not need to worry about invoking
// JSON.stringify (and it inheriting our security privileges).
debug("adoptDownload");
return this.createPromise(function (aResolve, aReject) {
if (!aAdoptDownloadDict) {
debug("Download dictionary is required!");
aReject("InvalidDownload");
return;
}
if (!aAdoptDownloadDict.storageName || !aAdoptDownloadDict.storagePath ||
!aAdoptDownloadDict.contentType) {
debug("Missing one of: storageName, storagePath, contentType");
aReject("InvalidDownload");
return;
}
// Convert storageName/storagePath to a local filesystem path.
let volume;
// getVolumeByName throws if you give it something it doesn't like
// because XPConnect converts the NS_ERROR_NOT_AVAILABLE to an
// exception. So catch it.
try {
volume = volumeService.getVolumeByName(aAdoptDownloadDict.storageName);
} catch (ex) {}
if (!volume) {
debug("Invalid storage name: " + aAdoptDownloadDict.storageName);
aReject("InvalidDownload");
return;
}
let computedPath = volume.mountPoint + '/' +
aAdoptDownloadDict.storagePath;
// We validate that there is actually a file at the given path in the
// parent process in DownloadsAPI.js because that's where the file
// access would actually occur either way.
// Create a DownloadsAPI.jsm 'jsonDownload' style representation.
let jsonDownload = {
url: aAdoptDownloadDict.url,
path: computedPath,
contentType: aAdoptDownloadDict.contentType,
startTime: aAdoptDownloadDict.startTime.valueOf() || Date.now(),
sourceAppManifestURL: ""
};
DownloadsIPC.adoptDownload(jsonDownload).then(
function(aResult) {
let domDownload = createDOMDownloadObject(this._window, aResult);
aResolve(this._prepareForContent(domDownload));
}.bind(this),
function(aResult) {
// This will be one of: AdoptError (generic catch-all),
// AdoptNoSuchFile, AdoptFileIsDirectory
aReject(aResult.error);
}
);
}.bind(this));
},
/**
* Turns a chrome download object into a content accessible one.
* When we have __DOM_IMPL__ available we just use that, otherwise
* we run _create() with the wrapped js object.
*/
_prepareForContent: function(aChromeObject) {
if (aChromeObject.__DOM_IMPL__) {
return aChromeObject.__DOM_IMPL__;
}
let res = this._window.DOMDownload._create(this._window,
aChromeObject.wrappedJSObject);
return res;
},
receiveMessage: function(aMessage) {
let data = aMessage.data;
switch(aMessage.name) {
case "Downloads:Added":
debug("Adding " + uneval(data));
let event = new this._window.DownloadEvent("downloadstart", {
download:
this._prepareForContent(createDOMDownloadObject(this._window, data))
});
this.__DOM_IMPL__.dispatchEvent(event);
break;
}
},
classID: Components.ID("{c6587afa-0696-469f-9eff-9dac0dd727fe}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
Ci.nsISupportsWeakReference,
Ci.nsIObserver,
Ci.nsIDOMGlobalPropertyInitializer]),
};
/**
* Keep track of download objects per window.
*/
var downloadsCache = {
init: function() {
this.cache = new WeakMap();
},
has: function(aWindow, aId) {
let downloads = this.cache.get(aWindow);
return !!(downloads && downloads[aId]);
},
get: function(aWindow, aDownload) {
let downloads = this.cache.get(aWindow);
if (!(downloads && downloads[aDownload.id])) {
debug("Adding download " + aDownload.id + " to cache.");
if (!downloads) {
this.cache.set(aWindow, {});
downloads = this.cache.get(aWindow);
}
// Create the object and add it to the cache.
let impl = Cc["@mozilla.org/downloads/download;1"]
.createInstance(Ci.nsISupports);
impl.wrappedJSObject._init(aWindow, aDownload);
downloads[aDownload.id] = impl;
}
return downloads[aDownload.id];
},
evict: function(aWindow) {
this.cache.delete(aWindow);
}
};
downloadsCache.init();
/**
* The DOM facade of a download object.
*/
function createDOMDownloadObject(aWindow, aDownload) {
return downloadsCache.get(aWindow, aDownload);
}
function DOMDownloadImpl() {
debug("DOMDownloadImpl constructor ");
this.wrappedJSObject = this;
this.totalBytes = 0;
this.currentBytes = 0;
this.url = null;
this.path = null;
this.storageName = null;
this.storagePath = null;
this.contentType = null;
/* fields that require getters/setters */
this._error = null;
this._startTime = new Date();
this._state = "stopped";
/* private fields */
this.id = null;
}
DOMDownloadImpl.prototype = {
createPromise: function(aPromiseInit) {
return new this._window.Promise(aPromiseInit);
},
pause: function() {
debug("DOMDownloadImpl pause");
let id = this.id;
// We need to wrap the Promise.jsm promise in a "real" DOM promise...
return this.createPromise(function(aResolve, aReject) {
DownloadsIPC.pause(id).then(aResolve, aReject);
});
},
resume: function() {
debug("DOMDownloadImpl resume");
let id = this.id;
// We need to wrap the Promise.jsm promise in a "real" DOM promise...
return this.createPromise(function(aResolve, aReject) {
DownloadsIPC.resume(id).then(aResolve, aReject);
});
},
set onstatechange(aHandler) {
this.__DOM_IMPL__.setEventHandler("onstatechange", aHandler);
},
get onstatechange() {
return this.__DOM_IMPL__.getEventHandler("onstatechange");
},
get error() {
return this._error;
},
set error(aError) {
this._error = aError;
},
get startTime() {
return this._startTime;
},
set startTime(aStartTime) {
if (aStartTime instanceof Date) {
this._startTime = aStartTime;
}
else {
this._startTime = new Date(aStartTime);
}
},
get state() {
return this._state;
},
// We require a setter here to simplify the internals of the Download Manager
// since we actually pass dummy JSON objects to the child process and update
// them. This is the case for all other setters for read-only attributes
// implemented in this object.
set state(aState) {
// We need to ensure that XPCOM consumers of this API respect the enum
// values as well.
if (["downloading",
"stopped",
"succeeded",
"finalized"].indexOf(aState) != -1) {
this._state = aState;
}
},
/**
* Initialize a DOMDownload instance for the given window using the
* 'jsonDownload' serialized format of the download encoded by
* DownloadsAPI.jsm.
*/
_init: function(aWindow, aDownload) {
this._window = aWindow;
this.id = aDownload.id;
this._update(aDownload);
Services.obs.addObserver(this, "downloads-state-change-" + this.id,
/* ownsWeak */ true);
debug("observer set for " + this.id);
},
/**
* Updates the state of the object and fires the statechange event.
*/
_update: function(aDownload) {
debug("update " + uneval(aDownload));
if (this.id != aDownload.id) {
return;
}
let props = ["totalBytes", "currentBytes", "url", "path", "storageName",
"storagePath", "state", "contentType", "startTime",
"sourceAppManifestURL"];
let changed = false;
let changedProps = {};
props.forEach((prop) => {
if (prop in aDownload && (aDownload[prop] != this[prop])) {
this[prop] = aDownload[prop];
changedProps[prop] = changed = true;
}
});
// When the path changes, we should update the storage name and
// storage path used for our downloaded file in case our download
// was re-targetted to a different storage and/or filename.
if (changedProps["path"]) {
let storages = this._window.navigator.getDeviceStorages("sdcard");
let preferredStorageName;
// Use the first one or the default storage. Just like jsdownloads picks
// the default / preferred download directory.
storages.forEach((aStorage) => {
if (aStorage.default || !preferredStorageName) {
preferredStorageName = aStorage.storageName;
}
});
// Now get the path for this storage area.
let volume;
if (preferredStorageName) {
let volume = volumeService.getVolumeByName(preferredStorageName);
if (volume) {
// Finally, create the relative path of the file that can be used
// later on to retrieve the file via DeviceStorage. Our path
// needs to omit the starting '/'.
this.storageName = preferredStorageName;
this.storagePath =
this.path.substring(this.path.indexOf(volume.mountPoint) +
volume.mountPoint.length + 1);
}
}
}
if (aDownload.error) {
//
// When we get a generic error failure back from the js downloads api
// we will verify the status of device storage to see if we can't provide
// a better error result value.
//
// XXX If these checks expand further, consider moving them into their
// own function.
//
let result = aDownload.error.result;
let storage = this._window.navigator.getDeviceStorage("sdcard");
// If we don't have access to device storage we'll opt out of these
// extra checks as they are all dependent on the state of the storage.
if (result == Cr.NS_ERROR_FAILURE && storage) {
// We will delay sending the notification until we've inferred which
// error is really happening.
changed = false;
debug("Attempting to infer error via device storage sanity checks.");
// Get device storage and request availability status.
let available = storage.available();
available.onsuccess = (function() {
debug("Storage Status = '" + available.result + "'");
let inferredError = result;
switch (available.result) {
case "unavailable":
inferredError = Cr.NS_ERROR_FILE_NOT_FOUND;
break;
case "shared":
inferredError = Cr.NS_ERROR_FILE_ACCESS_DENIED;
break;
}
this._updateWithError(aDownload, inferredError);
}).bind(this);
available.onerror = (function() {
this._updateWithError(aDownload, result);
}).bind(this);
}
this.error =
new this._window.DOMError("DownloadError", result);
} else {
this.error = null;
}
// The visible state has not changed, so no need to fire an event.
if (!changed) {
return;
}
this._sendStateChange();
},
_updateWithError: function(aDownload, aError) {
this.error =
new this._window.DOMError("DownloadError", aError);
this._sendStateChange();
},
_sendStateChange: function() {
// __DOM_IMPL__ may not be available at first update.
if (this.__DOM_IMPL__) {
let event = new this._window.DownloadEvent("statechange", {
download: this.__DOM_IMPL__
});
debug("Dispatching statechange event. state=" + this.state);
this.__DOM_IMPL__.dispatchEvent(event);
}
},
observe: function(aSubject, aTopic, aData) {
debug("DOMDownloadImpl observe " + aTopic);
if (aTopic !== "downloads-state-change-" + this.id) {
return;
}
try {
let download = JSON.parse(aData);
// We get the start time as milliseconds, not as a Date object.
if (download.startTime) {
download.startTime = new Date(download.startTime);
}
this._update(download);
} catch(e) {}
},
classID: Components.ID("{96b81b99-aa96-439d-8c59-92eeed34705f}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
Ci.nsIObserver,
Ci.nsISupportsWeakReference])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DOMDownloadManagerImpl,
DOMDownloadImpl]);

View File

@ -1,360 +0,0 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Downloads.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
/**
* Parent process logic that services download API requests from the
* DownloadAPI.js instances in content processeses. The actual work of managing
* downloads is done by Toolkit's Downloads.jsm. This module is loaded by B2G's
* shell.js
*/
function debug(aStr) {
#ifdef MOZ_DEBUG
dump("-*- DownloadsAPI.jsm : " + aStr + "\n");
#endif
}
function sendPromiseMessage(aMm, aMessageName, aData, aError) {
debug("sendPromiseMessage " + aMessageName);
let msg = {
id: aData.id,
promiseId: aData.promiseId
};
if (aError) {
msg.error = aError;
}
aMm.sendAsyncMessage(aMessageName, msg);
}
var DownloadsAPI = {
init: function() {
debug("init");
this._ids = new WeakMap(); // Maps toolkit download objects to ids.
this._index = {}; // Maps ids to downloads.
["Downloads:GetList",
"Downloads:ClearAllDone",
"Downloads:Remove",
"Downloads:Pause",
"Downloads:Resume",
"Downloads:Adopt"].forEach((msgName) => {
ppmm.addMessageListener(msgName, this);
});
let self = this;
Task.spawn(function () {
let list = yield Downloads.getList(Downloads.ALL);
yield list.addView(self);
debug("view added to download list.");
}).then(null, Components.utils.reportError);
this._currentId = 0;
},
/**
* Returns a unique id for each download, hashing the url and the path.
*/
downloadId: function(aDownload) {
let id = this._ids.get(aDownload, null);
if (!id) {
id = "download-" + this._currentId++;
this._ids.set(aDownload, id);
this._index[id] = aDownload;
}
return id;
},
getDownloadById: function(aId) {
return this._index[aId];
},
/**
* Converts a download object into a plain json object that we'll
* send to the DOM side.
*/
jsonDownload: function(aDownload) {
let res = {
totalBytes: aDownload.totalBytes,
currentBytes: aDownload.currentBytes,
url: aDownload.source.url,
path: aDownload.target.path,
contentType: aDownload.contentType,
startTime: aDownload.startTime.getTime(),
sourceAppManifestURL: aDownload._unknownProperties &&
aDownload._unknownProperties.sourceAppManifestURL
};
if (aDownload.error) {
res.error = aDownload.error;
}
res.id = this.downloadId(aDownload);
// The state of the download. Can be any of "downloading", "stopped",
// "succeeded", finalized".
// Default to "stopped"
res.state = "stopped";
if (!aDownload.stopped &&
!aDownload.canceled &&
!aDownload.succeeded &&
!aDownload.DownloadError) {
res.state = "downloading";
} else if (aDownload.succeeded) {
res.state = "succeeded";
}
return res;
},
/**
* download view methods.
*/
onDownloadAdded: function(aDownload) {
let download = this.jsonDownload(aDownload);
debug("onDownloadAdded " + uneval(download));
ppmm.broadcastAsyncMessage("Downloads:Added", download);
},
onDownloadRemoved: function(aDownload) {
let download = this.jsonDownload(aDownload);
download.state = "finalized";
debug("onDownloadRemoved " + uneval(download));
ppmm.broadcastAsyncMessage("Downloads:Removed", download);
this._index[this._ids.get(aDownload)] = null;
this._ids.delete(aDownload);
},
onDownloadChanged: function(aDownload) {
let download = this.jsonDownload(aDownload);
debug("onDownloadChanged " + uneval(download));
ppmm.broadcastAsyncMessage("Downloads:Changed", download);
},
receiveMessage: function(aMessage) {
debug("message: " + aMessage.name);
switch (aMessage.name) {
case "Downloads:GetList":
this.getList(aMessage.data, aMessage.target);
break;
case "Downloads:ClearAllDone":
this.clearAllDone(aMessage.data, aMessage.target);
break;
case "Downloads:Remove":
this.remove(aMessage.data, aMessage.target);
break;
case "Downloads:Pause":
this.pause(aMessage.data, aMessage.target);
break;
case "Downloads:Resume":
this.resume(aMessage.data, aMessage.target);
break;
case "Downloads:Adopt":
this.adoptDownload(aMessage.data, aMessage.target);
break;
default:
debug("Invalid message: " + aMessage.name);
}
},
getList: function(aData, aMm) {
debug("getList called!");
let self = this;
Task.spawn(function () {
let list = yield Downloads.getList(Downloads.ALL);
let downloads = yield list.getAll();
let res = [];
downloads.forEach((aDownload) => {
res.push(self.jsonDownload(aDownload));
});
aMm.sendAsyncMessage("Downloads:GetList:Return", res);
}).then(null, Components.utils.reportError);
},
clearAllDone: function(aData, aMm) {
debug("clearAllDone called!");
Task.spawn(function () {
let list = yield Downloads.getList(Downloads.ALL);
list.removeFinished();
}).then(null, Components.utils.reportError);
},
remove: function(aData, aMm) {
debug("remove id " + aData.id);
let download = this.getDownloadById(aData.id);
if (!download) {
sendPromiseMessage(aMm, "Downloads:Remove:Return",
aData, "NoSuchDownload");
return;
}
Task.spawn(function() {
yield download.finalize(true);
let list = yield Downloads.getList(Downloads.ALL);
yield list.remove(download);
}).then(
function() {
sendPromiseMessage(aMm, "Downloads:Remove:Return", aData);
},
function() {
sendPromiseMessage(aMm, "Downloads:Remove:Return",
aData, "RemoveError");
}
);
},
pause: function(aData, aMm) {
debug("pause id " + aData.id);
let download = this.getDownloadById(aData.id);
if (!download) {
sendPromiseMessage(aMm, "Downloads:Pause:Return",
aData, "NoSuchDownload");
return;
}
download.cancel().then(
function() {
sendPromiseMessage(aMm, "Downloads:Pause:Return", aData);
},
function() {
sendPromiseMessage(aMm, "Downloads:Pause:Return",
aData, "PauseError");
}
);
},
resume: function(aData, aMm) {
debug("resume id " + aData.id);
let download = this.getDownloadById(aData.id);
if (!download) {
sendPromiseMessage(aMm, "Downloads:Resume:Return",
aData, "NoSuchDownload");
return;
}
download.start().then(
function() {
sendPromiseMessage(aMm, "Downloads:Resume:Return", aData);
},
function() {
sendPromiseMessage(aMm, "Downloads:Resume:Return",
aData, "ResumeError");
}
);
},
/**
* Receive a download to adopt in the same representation we produce from
* our "jsonDownload" normalizer and add it to the list of downloads.
*/
adoptDownload: function(aData, aMm) {
let adoptJsonRep = aData.jsonDownload;
debug("adoptDownload " + uneval(adoptJsonRep));
Task.spawn(function* () {
// Verify that the file exists on disk. This will result in a rejection
// if the file does not exist. We will also use this information for the
// file size to avoid weird inconsistencies. We ignore the filesystem
// timestamp in favor of whatever the caller is telling us.
let fileInfo = yield OS.File.stat(adoptJsonRep.path);
// We also require that the file is not a directory.
if (fileInfo.isDir) {
throw new Error("AdoptFileIsDirectory");
}
// We need to create a Download instance to add to the list. Create a
// serialized representation and then from there the instance.
let serializedRep = {
// explicit initializations in toSerializable
source: {
url: adoptJsonRep.url
// This is where isPrivate would go if adoption supported private
// browsing.
},
target: {
path: adoptJsonRep.path,
},
startTime: adoptJsonRep.startTime,
// kPlainSerializableDownloadProperties propagations
succeeded: true, // (all adopted downloads are required to be completed)
totalBytes: fileInfo.size,
contentType: adoptJsonRep.contentType,
// unknown properties added/used by the DownloadsAPI
currentBytes: fileInfo.size,
sourceAppManifestURL: adoptJsonRep.sourceAppManifestURL
};
let download = yield Downloads.createDownload(serializedRep);
// The ALL list is a DownloadCombinedList instance that combines the
// PUBLIC (persisted to disk) and PRIVATE (ephemeral) download lists..
// When we call add on it, it dispatches to the appropriate list based on
// the 'isPrivate' field of the source. (Which we don't initialize and
// defaults to false.)
let allDownloadList = yield Downloads.getList(Downloads.ALL);
// This add will automatically notify all views of the added download,
// including DownloadsAPI instances and the DownloadAutoSaveView that's
// subscribed to the PUBLIC list and will save the download.
yield allDownloadList.add(download);
debug("download adopted");
// The notification above occurred synchronously, and so we will have
// already dispatched an added notification for our download to the child
// process in question. As such, we only need to relay the download id
// since the download will already have been cached.
return download;
}.bind(this)).then(
(download) => {
sendPromiseMessage(aMm, "Downloads:Adopt:Return",
{
id: this.downloadId(download),
promiseId: aData.promiseId
});
},
(ex) => {
let reportAs = "AdoptError";
// Provide better error codes for expected errors.
if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
reportAs = "AdoptNoSuchFile";
} else if (ex.message === "AdoptFileIsDirectory") {
reportAs = ex.message;
} else {
// Anything else is unexpected and should be reported to help track
// down what's going wrong.
debug("unexpected download error: " + ex);
Cu.reportError(ex);
}
sendPromiseMessage(aMm, "Downloads:Adopt:Return",
{
promiseId: aData.promiseId
},
reportAs);
});
}
};
DownloadsAPI.init();

View File

@ -1,6 +0,0 @@
# DownloadsAPI.js
component {c6587afa-0696-469f-9eff-9dac0dd727fe} DownloadsAPI.js
contract @mozilla.org/downloads/manager;1 {c6587afa-0696-469f-9eff-9dac0dd727fe}
component {96b81b99-aa96-439d-8c59-92eeed34705f} DownloadsAPI.js
contract @mozilla.org/downloads/download;1 {96b81b99-aa96-439d-8c59-92eeed34705f}

View File

@ -1,224 +0,0 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = ["DownloadsIPC"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
/**
* This module lives in the child process and receives the ipc messages
* from the parent. It saves the download's state and redispatch changes
* to DOM objects using an observer notification.
*
* This module needs to be loaded once and only once per process.
*/
function debug(aStr) {
#ifdef MOZ_DEBUG
dump("-*- DownloadsIPC.jsm : " + aStr + "\n");
#endif
}
const ipcMessages = ["Downloads:Added",
"Downloads:Removed",
"Downloads:Changed",
"Downloads:GetList:Return",
"Downloads:Remove:Return",
"Downloads:Pause:Return",
"Downloads:Resume:Return",
"Downloads:Adopt:Return"];
this.DownloadsIPC = {
downloads: {},
init: function() {
debug("init");
Services.obs.addObserver(this, "xpcom-shutdown", false);
ipcMessages.forEach((aMessage) => {
cpmm.addMessageListener(aMessage, this);
});
// We need to get the list of current downloads.
this.ready = false;
this.getListPromises = [];
this.downloadPromises = {};
cpmm.sendAsyncMessage("Downloads:GetList", {});
this._promiseId = 0;
},
notifyChanges: function(aId) {
// TODO: use the subject instead of stringifying.
if (this.downloads[aId]) {
debug("notifyChanges notifying changes for " + aId);
Services.obs.notifyObservers(null, "downloads-state-change-" + aId,
JSON.stringify(this.downloads[aId]));
} else {
debug("notifyChanges failed for " + aId)
}
},
_updateDownloadsArray: function(aDownloads) {
this.downloads = [];
// We actually have an array of downloads.
aDownloads.forEach((aDownload) => {
this.downloads[aDownload.id] = aDownload;
});
},
receiveMessage: function(aMessage) {
let download = aMessage.data;
debug("message: " + aMessage.name);
switch(aMessage.name) {
case "Downloads:GetList:Return":
this._updateDownloadsArray(download);
if (!this.ready) {
this.getListPromises.forEach(aPromise =>
aPromise.resolve(this.downloads));
this.getListPromises.length = 0;
}
this.ready = true;
break;
case "Downloads:Added":
this.downloads[download.id] = download;
this.notifyChanges(download.id);
break;
case "Downloads:Removed":
if (this.downloads[download.id]) {
this.downloads[download.id] = download;
this.notifyChanges(download.id);
delete this.downloads[download.id];
}
break;
case "Downloads:Changed":
// Only update properties that actually changed.
let cached = this.downloads[download.id];
if (!cached) {
debug("No download found for " + download.id);
return;
}
let props = ["totalBytes", "currentBytes", "url", "path", "state",
"contentType", "startTime"];
let changed = false;
props.forEach((aProp) => {
if (download[aProp] && (download[aProp] != cached[aProp])) {
cached[aProp] = download[aProp];
changed = true;
}
});
// Updating the error property. We always get a 'state' change as
// well.
cached.error = download.error;
if (changed) {
this.notifyChanges(download.id);
}
break;
case "Downloads:Remove:Return":
case "Downloads:Pause:Return":
case "Downloads:Resume:Return":
case "Downloads:Adopt:Return":
if (this.downloadPromises[download.promiseId]) {
if (!download.error) {
this.downloadPromises[download.promiseId].resolve(download);
} else {
this.downloadPromises[download.promiseId].reject(download);
}
delete this.downloadPromises[download.promiseId];
}
break;
}
},
/**
* Returns a promise that is resolved with the list of current downloads.
*/
getDownloads: function() {
debug("getDownloads()");
let deferred = Promise.defer();
if (this.ready) {
debug("Returning existing list.");
deferred.resolve(this.downloads);
} else {
this.getListPromises.push(deferred);
}
return deferred.promise;
},
/**
* Void function to trigger removal of completed downloads.
*/
clearAllDone: function() {
debug("clearAllDone");
cpmm.sendAsyncMessage("Downloads:ClearAllDone", {});
},
promiseId: function() {
return this._promiseId++;
},
remove: function(aId) {
debug("remove " + aId);
let deferred = Promise.defer();
let pId = this.promiseId();
this.downloadPromises[pId] = deferred;
cpmm.sendAsyncMessage("Downloads:Remove",
{ id: aId, promiseId: pId });
return deferred.promise;
},
pause: function(aId) {
debug("pause " + aId);
let deferred = Promise.defer();
let pId = this.promiseId();
this.downloadPromises[pId] = deferred;
cpmm.sendAsyncMessage("Downloads:Pause",
{ id: aId, promiseId: pId });
return deferred.promise;
},
resume: function(aId) {
debug("resume " + aId);
let deferred = Promise.defer();
let pId = this.promiseId();
this.downloadPromises[pId] = deferred;
cpmm.sendAsyncMessage("Downloads:Resume",
{ id: aId, promiseId: pId });
return deferred.promise;
},
adoptDownload: function(aJsonDownload) {
debug("adoptDownload");
let deferred = Promise.defer();
let pId = this.promiseId();
this.downloadPromises[pId] = deferred;
cpmm.sendAsyncMessage("Downloads:Adopt",
{ jsonDownload: aJsonDownload, promiseId: pId });
return deferred.promise;
},
observe: function(aSubject, aTopic, aData) {
if (aTopic == "xpcom-shutdown") {
ipcMessages.forEach((aMessage) => {
cpmm.removeMessageListener(aMessage, this);
});
}
}
};
DownloadsIPC.init();

View File

@ -1,21 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
if CONFIG["MOZ_B2G"]:
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
EXTRA_COMPONENTS += [
'DownloadsAPI.manifest',
]
EXTRA_PP_COMPONENTS += [
'DownloadsAPI.js',
]
EXTRA_PP_JS_MODULES += [
'DownloadsAPI.jsm',
'DownloadsIPC.jsm',
]

View File

@ -1,67 +0,0 @@
/**
* A helper to clear out the existing downloads known to the mozDownloadManager
* / downloads.js.
*
* It exists because previously mozDownloadManager.clearAllDone() thought that
* when it returned that all the completed downloads would be cleared out. It
* was wrong and this led to various intermittent test failurse. In discussion
* on https://bugzil.la/979446#c13 and onwards, it was decided that
* clearAllDone() was in the wrong and that the jsdownloads API it depends on
* was not going to change to make it be in the right.
*
* The existing uses of clearAllDone() in tests seemed to be about:
* - Exploding if there was somehow still a download in progress
* - Clearing out the download list at the start of a test so that calls to
* getDownloads() wouldn't have to worry about existing downloads, etc.
*
* From discussion, the right way to handle clearing is to wait for the expected
* removal events to occur for the existing downloads. So that's what we do.
* We still generate a test failure if there are any in-progress downloads.
*
* @param {Boolean} [getDownloads=false]
* If true, invoke getDownloads after clearing the download list and return
* its value.
*/
function clearAllDoneHelper(getDownloads) {
var clearedPromise = new Promise(function(resolve, reject) {
function gotDownloads(downloads) {
// If there are no downloads, we're already done.
if (downloads.length === 0) {
resolve();
return;
}
// Track the set of expected downloads that will be finalized.
var expectedIds = new Set();
function changeHandler(evt) {
var download = evt.download;
if (download.state === "finalized") {
expectedIds.delete(download.id);
if (expectedIds.size === 0) {
resolve();
}
}
}
downloads.forEach(function(download) {
if (download.state === "downloading") {
ok(false, "A download is still active: " + download.path);
reject("Active download");
}
download.onstatechange = changeHandler;
expectedIds.add(download.id);
});
navigator.mozDownloadManager.clearAllDone();
}
function gotBadNews(err) {
ok(false, "Problem clearing all downloads: " + err);
reject(err);
}
navigator.mozDownloadManager.getDownloads().then(gotDownloads, gotBadNews);
});
if (!getDownloads) {
return clearedPromise;
}
return clearedPromise.then(function() {
return navigator.mozDownloadManager.getDownloads();
});
}

View File

@ -1,15 +0,0 @@
[DEFAULT]
# The actual requirement for mozDownloadManager is MOZ_GONK because of
# the nsIVolumeService dependency. Until https://bugzil.la/1130264 is
# addressed, there is no way for mulet to run these tests.
run-if = toolkit == 'gonk'
support-files =
serve_file.sjs
clear_all_done_helper.js
[test_downloads_navigator_object.html]
[test_downloads_basic.html]
[test_downloads_large.html]
[test_downloads_bad_file.html]
[test_downloads_pause_remove.html]
[test_downloads_pause_resume.html]

View File

@ -1,170 +0,0 @@
// Serves a file with a given mime type and size at an optionally given rate.
function getQuery(request) {
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[name] = unescape(value);
});
return query;
}
function handleResponse() {
// Is this a rate limited response?
if (this.state.rate > 0) {
// Calculate how many bytes we have left to send.
var bytesToWrite = this.state.totalBytes - this.state.sentBytes;
// Do we have any bytes left to send? If not we'll just fall thru and
// cancel our repeating timer and finalize the response.
if (bytesToWrite > 0) {
// Figure out how many bytes to send, based on the rate limit.
bytesToWrite =
(bytesToWrite > this.state.rate) ? this.state.rate : bytesToWrite;
for (let i = 0; i < bytesToWrite; i++) {
try {
this.response.bodyOutputStream.write("0", 1);
} catch (e) {
// Connection was closed by client.
if (e == Components.results.NS_ERROR_NOT_AVAILABLE) {
// There's no harm in calling this multiple times.
this.response.finish();
// It's possible that our timer wasn't cancelled in time
// and we'll be called again.
if (this.timer) {
this.timer.cancel();
this.timer = null;
}
return;
}
}
}
// Update the number of bytes we've sent to the client.
this.state.sentBytes += bytesToWrite;
// Wait until the next call to do anything else.
return;
}
}
else {
// Not rate limited, write it all out.
for (let i = 0; i < this.state.totalBytes; i++) {
this.response.write("0");
}
}
// Finalize the response.
this.response.finish();
// All done sending, go ahead and cancel our repeating timer.
this.timer.cancel();
// Clear the timer.
this.timer = null;
}
function handleRequest(request, response) {
var query = getQuery(request);
// sending at a specific rate requires our response to be asynchronous so
// we handle all requests asynchronously. See handleResponse().
response.processAsync();
// Default status when responding.
var version = "1.1";
var statusCode = 200;
var description = "OK";
// Default values for content type, size and rate.
var contentType = "text/plain";
var contentRange = null;
var size = 1024;
var rate = 0;
// optional content type to be used by our response.
if ("contentType" in query) {
contentType = query["contentType"];
}
// optional size (in bytes) for generated file.
if ("size" in query) {
size = parseInt(query["size"]);
}
// optional range request check.
if (request.hasHeader("range")) {
version = "1.1";
statusCode = 206;
description = "Partial Content";
// We'll only support simple range byte style requests.
var [offset, total] = request.getHeader("range").slice("bytes=".length).split("-");
// Enforce valid Number values.
offset = parseInt(offset);
offset = isNaN(offset) ? 0 : offset;
// Same.
total = parseInt(total);
total = isNaN(total) ? 0 : total;
// We'll need to original total size as part of the Content-Range header
// value in our response.
var originalSize = size;
// If we have a total size requested, we must make sure to send that number
// of bytes only (minus the start offset).
if (total && total < size) {
size = total - offset;
} else if (offset) {
// Looks like we just have a byte offset to deal with.
size = size - offset;
}
// We specifically need to add a Content-Range header to all responses for
// requests that include a range request header.
contentRange = "bytes " + offset + "-" + (size - 1) + "/" + originalSize;
}
// optional rate (in bytes/s) at which to send the file.
if ("rate" in query) {
rate = parseInt(query["rate"]);
}
// The context for the responseHandler.
var context = {
response: response,
state: {
contentType: contentType,
totalBytes: size,
sentBytes: 0,
rate: rate
},
timer: null
};
// The notify implementation for the timer.
context.notify = handleResponse.bind(context);
context.timer =
Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
// generate the content.
response.setStatusLine(version, statusCode, description);
response.setHeader("Content-Type", contentType, false);
if (contentRange) {
response.setHeader("Content-Range", contentRange, false);
}
response.setHeader("Content-Length", size.toString(), false);
// initialize the timer and start writing out the response.
context.timer.initWithCallback(
context,
1000,
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK
);
}

View File

@ -1,93 +0,0 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=960749
-->
<head>
<title>Test for Bug 960749 Downloads API</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=960749">Mozilla Bug 960749</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<a href="serve_file.sjs?contentType=application/octet-stream&size=1024" download=".&lt;.EVIL.&gt;\ / : * ? &quot; |file.bin" id="download1">Download #1</a>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
// Testing a simple download, waiting for it to be done.
SimpleTest.waitForExplicitFinish();
var index = -1;
var expected = "_.EVIL.__ _ _ _ _ _ _file.bin";
function next() {
index += 1;
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index]();
} catch(ex) {
ok(false, "Caught exception", ex);
}
}
function checkTargetFilename(download) {
ok(download.path.endsWith(expected),
"Download path leaf name '" + download.path +
"' should match '" + expected + "' filename.");
SimpleTest.finish();
}
function downloadChange(evt) {
var download = evt.download;
if (download.state === "succeeded") {
checkTargetFilename(download);
}
}
function downloadStart(evt) {
var download = evt.download;
download.onstatechange = downloadChange;
}
var steps = [
// Start by setting the pref to true.
function() {
SpecialPowers.pushPrefEnv({
set: [["dom.mozDownloads.enabled", true]]
}, next);
},
// Setup the event listeners.
function() {
SpecialPowers.pushPermissions([
{type: "downloads", allow: true, context: document}
], function() {
navigator.mozDownloadManager.ondownloadstart = downloadStart;
next();
});
},
// Click on the <a download> to start the download.
function() {
document.getElementById("download1").click();
}
];
next();
</script>
</pre>
</body>
</html>

View File

@ -1,128 +0,0 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=938023
-->
<head>
<title>Test for Bug 938023 Downloads API</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938023">Mozilla Bug 938023</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<a href="serve_file.sjs?contentType=application/octet-stream&size=1024" download="test.bin" id="download1">Download #1</a>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
// Testing a simple download, waiting for it to be done.
SimpleTest.waitForExplicitFinish();
var index = -1;
var todayDate = new Date();
var baseServeURL = "http://mochi.test:8888/tests/dom/downloads/tests/";
var lastKnownCurrentBytes = 0;
function next() {
index += 1;
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index]();
} catch(ex) {
ok(false, "Caught exception", ex);
}
}
function checkConsistentDownloadAttributes(download) {
var href = document.getElementById("download1").getAttribute("href");
var expectedServeURL = baseServeURL + href;
var destinationRegEx = /test\(?[0-9]*\)?\.bin$/;
// bug 945323: Download path isn't honoring download attribute
ok(destinationRegEx.test(download.path),
"Download path '" + download.path +
"' should match '" + destinationRegEx + "' regexp.");
ok(download.startTime >= todayDate,
"Download start time should be greater than or equal to today");
is(download.error, null, "Download does not have an error");
is(download.url, expectedServeURL,
"Download URL = " + expectedServeURL);
ok(download.id !== null, "Download id is defined");
is(download.contentType, "application/octet-stream",
"Download content type is application/octet-stream");
}
function downloadChange(evt) {
var download = evt.download;
checkConsistentDownloadAttributes(download);
is(download.totalBytes, 1024, "Download total size is 1024 bytes");
if (download.state === "succeeded") {
is(download.currentBytes, 1024, "Download current size is 1024 bytes");
SimpleTest.finish();
} else if (download.state === "downloading") {
// Note that this case may or may not trigger, depending on whether the
// download is initially reported with 0 bytes (we should happen) or with
// 1024 bytes (we should not happen). If we do happen, an additional 8
// TEST-PASS events should be logged.
ok(download.currentBytes > lastKnownCurrentBytes,
"Download current size is larger than last download change event");
lastKnownCurrentBytes = download.currentBytes;
} else {
ok(false, "Unexpected download state = " + download.state);
}
}
function downloadStart(evt) {
var download = evt.download;
checkConsistentDownloadAttributes(download);
// We used to check that the currentBytes was 0. This was incorrect. It
// is very common to first hear about the download already at 1024 bytes.
is(download.state, "downloading", "Download state is downloading");
download.onstatechange = downloadChange;
}
var steps = [
// Start by setting the pref to true.
function() {
SpecialPowers.pushPrefEnv({
set: [["dom.mozDownloads.enabled", true]]
}, next);
},
// Setup the event listeners.
function() {
SpecialPowers.pushPermissions([
{type: "downloads", allow: true, context: document}
], function() {
navigator.mozDownloadManager.ondownloadstart = downloadStart;
next();
});
},
// Click on the <a download> to start the download.
function() {
document.getElementById("download1").click();
}
];
next();
</script>
</pre>
</body>
</html>

View File

@ -1,110 +0,0 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=938023
-->
<head>
<title>Test for Bug 938023 Downloads API</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="clear_all_done_helper.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938023">Mozilla Bug 938023</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<a href="serve_file.sjs?contentType=application/octet-stream&size=102400" download="test.bin" id="download1">Large Download</a>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
// Testing downloading a file, then checking getDownloads() and clearAllDone().
SimpleTest.waitForExplicitFinish();
var index = -1;
function next(args) {
index += 1;
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index](args);
} catch(ex) {
ok(false, "Caught exception", ex);
}
}
// Catch all error function.
function error() {
ok(false, "API failure");
SimpleTest.finish();
}
function getDownloads(downloads) {
ok(downloads.length == 1, "One downloads after getDownloads");
clearAllDoneHelper(true).then(clearAllDone, error);
}
function clearAllDone(downloads) {
ok(downloads.length == 0, "No downloads after clearAllDone");
SimpleTest.finish();
}
function downloadChange(evt) {
var download = evt.download;
if (download.state == "succeeded") {
ok(download.totalBytes == 102400, "Download size is 100k bytes.");
navigator.mozDownloadManager.getDownloads().then(getDownloads, error);
}
}
var steps = [
// Start by setting the pref to true.
function() {
SpecialPowers.pushPrefEnv({
set: [["dom.mozDownloads.enabled", true]]
}, next);
},
// Setup permission and clear current list.
function() {
SpecialPowers.pushPermissions([
{type: "downloads", allow: true, context: document}
], function() {
clearAllDoneHelper(true).then(next, error);
});
},
function(downloads) {
ok(downloads.length == 0, "Start with an empty download list.");
next();
},
// Setup the event listeners.
function() {
navigator.mozDownloadManager.ondownloadstart =
function(evt) {
ok(true, "Download started");
evt.download.addEventListener("statechange", downloadChange);
}
next();
},
// Click on the <a download> to start the download.
function() {
document.getElementById("download1").click();
}
];
next();
</script>
</pre>
</body>
</html>

View File

@ -1,75 +0,0 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=938023
-->
<head>
<title>Test for Bug 938023 Downloads API</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938023">Mozilla Bug 938023</a>
<p id="display"></p>
<div id="content" style="display: none">
<iframe></iframe>
</div>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
SimpleTest.waitForExplicitFinish();
var index = -1;
function next() {
index += 1;
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index]();
} catch(ex) {
ok(false, "Caught exception", ex);
}
}
var steps = [
// Start by setting the pref to true.
function() {
SpecialPowers.pushPrefEnv({
set: [["dom.mozDownloads.enabled", true]]
}, next);
},
function() {
SpecialPowers.pushPermissions([
{type: "downloads", allow: 0, context: document}
], function() {
is(frames[0].navigator.mozDownloadManager, null, "navigator.mozDownloadManager is null when the page doesn't have permissions");
next();
});
},
function() {
SpecialPowers.pushPrefEnv({
set: [["dom.mozDownloads.enabled", false]]
}, function() {
is(navigator.mozDownloadManager, undefined, "navigator.mozDownloadManager is undefined");
next();
});
},
function() {
SimpleTest.finish();
}
];
next();
</script>
</pre>
</body>
</html>

View File

@ -1,117 +0,0 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=938023
-->
<head>
<title>Test for Bug 938023 Downloads API</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="clear_all_done_helper.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938023">Mozilla Bug 938023</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<a href="serve_file.sjs?contentType=application/octet-stream&size=102400&rate=1024" download="test.bin" id="download1">Large Download</a>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
// Testing pausing a download and then removing it.
SimpleTest.waitForExplicitFinish();
var index = -1;
function next(args) {
index += 1;
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index](args);
} catch(ex) {
ok(false, "Caught exception", ex);
}
}
var pausing = false;
// Catch all error function.
function error() {
ok(false, "API failure");
SimpleTest.finish();
}
function checkDownloadList(downloads) {
ok(downloads.length == 0, "No downloads left");
SimpleTest.finish();
}
function checkRemoved(download) {
ok(download.state == "finalized", "Download removed.");
navigator.mozDownloadManager.getDownloads()
.then(checkDownloadList, error);
}
function downloadChange(evt) {
var download = evt.download;
if (download.state == "downloading" && !pausing) {
pausing = true;
download.pause();
} else if (download.state == "stopped") {
ok(pausing, "Download stopped by pause()");
navigator.mozDownloadManager.remove(download)
.then(checkRemoved, error);
}
}
var steps = [
// Start by setting the pref to true.
function() {
SpecialPowers.pushPrefEnv({
set: [["dom.mozDownloads.enabled", true]]
}, next);
},
// Setup permission and clear current list.
function() {
SpecialPowers.pushPermissions([
{type: "downloads", allow: true, context: document}
], function() {
clearAllDoneHelper(true).then(next, error);
});
},
function(downloads) {
ok(downloads.length == 0, "Start with an empty download list.");
next();
},
// Setup the event listeners.
function() {
navigator.mozDownloadManager.ondownloadstart =
function(evt) {
ok(true, "Download started");
evt.download.addEventListener("statechange", downloadChange);
}
next();
},
// Click on the <a download> to start the download.
function() {
document.getElementById("download1").click();
}
];
next();
</script>
</pre>
</body>
</html>

View File

@ -1,121 +0,0 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=938023
-->
<head>
<title>Test for Bug 938023 Downloads API</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="clear_all_done_helper.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=938023">Mozilla Bug 938023</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<a href="serve_file.sjs?contentType=application/octet-stream&size=102400&rate=1024" download="test.bin" id="download1">Large Download</a>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
// Testing pausing a download and then resuming it.
SimpleTest.waitForExplicitFinish();
var index = -1;
function next(args) {
index += 1;
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
steps[index](args);
} catch(ex) {
ok(false, "Caught exception", ex);
}
}
var pausing = false;
var resuming = false;
// Catch all error function.
function error() {
ok(false, "API failure");
SimpleTest.finish();
}
function checkDownloadList(downloads) {
ok(downloads.length == 0, "No downloads left");
SimpleTest.finish();
}
function checkResumeSucceeded(download) {
ok(download.state == "succeeded", "Download resumed successfully.");
clearAllDoneHelper(true).then(checkDownloadList, error);
}
function downloadChange(evt) {
var download = evt.download;
info("got download event, state: " + download.state +
" current bytes: " + download.currentBytes +
" pausing?: " + pausing + " resuming?: " + resuming);
if (download.state == "downloading" && !pausing) {
pausing = true;
download.pause();
} else if (download.state == "stopped" && !resuming) {
resuming = true;
ok(pausing, "Download stopped by pause()");
download.resume()
.then(function() { checkResumeSucceeded(download); }, error);
}
}
var steps = [
// Start by setting the pref to true.
function() {
SpecialPowers.pushPrefEnv({
set: [["dom.mozDownloads.enabled", true]]
}, next);
},
// Setup permission and clear current list.
function() {
SpecialPowers.pushPermissions([
{type: "downloads", allow: true, context: document}
], function() {
clearAllDoneHelper(true).then(next, error);
});
},
function(downloads) {
ok(downloads.length == 0, "Start with an empty download list.");
next();
},
// Setup the event listeners.
function() {
navigator.mozDownloadManager.ondownloadstart =
function(evt) {
ok(true, "Download started");
evt.download.addEventListener("statechange", downloadChange);
}
next();
},
// Click on the <a download> to start the download.
function() {
document.getElementById("download1").click();
}
];
next();
</script>
</pre>
</body>
</html>

View File

@ -23,7 +23,6 @@
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
#include "nsIDOMRange.h"
#include "nsIEditorIMESupport.h"
#include "nsIFrame.h"
#include "nsINode.h"
#include "nsIPresShell.h"
@ -642,12 +641,11 @@ IMEContentObserver::IsEditorComposing() const
// whether the editor already started to handle composition because
// web contents can change selection, text content and/or something from
// compositionstart event listener which is run before EditorBase handles it.
nsCOMPtr<nsIEditorIMESupport> editorIMESupport = do_QueryInterface(mEditor);
if (NS_WARN_IF(!editorIMESupport)) {
if (NS_WARN_IF(!mEditor)) {
return false;
}
bool isComposing = false;
nsresult rv = editorIMESupport->GetComposing(&isComposing);
nsresult rv = mEditor->GetComposing(&isComposing);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}

View File

@ -73,22 +73,15 @@ function triggerShortAnimation(node) {
node.style.animation = "anim1 1ms linear";
}
// This function triggers a long animation with two iterations, which is
// *nearly* at the end of its first iteration. It will hit the end of that
// iteration (firing an event) almost immediately, 1ms in the future.
// This function triggers a very short (10ms long) animation with many
// iterations, which will cause a start event followed by an iteration event
// on each subsequent tick, to fire.
//
// NOTE: It's important that this animation have a *long* duration. If it were
// short (e.g. 1ms duration), then we might jump past all its iterations in
// a single refresh-driver tick. And if that were to happens, we'd *never* fire
// any animationiteration events -- the CSS Animations spec says this event
// must not be fired "...when an animationend event would fire at the same time"
// (which would be the case in this example with a 1ms duration). So, to make
// sure our event does fire, we use a long duration and a nearly-as-long
// negative delay. This ensures we hit the end of the first iteration right
// away, and that we don't risk hitting the end of the second iteration at the
// same time.
// NOTE: We need the many iterations since if an animation frame coincides
// with the animation starting or ending we dispatch only the start or end
// event and not the iteration event.
function triggerAnimationIteration(node) {
node.style.animation = "anim1 300s -299.999s linear 2";
node.style.animation = "anim1 10ms linear 20000";
}
// GENERAL UTILITY FUNCTIONS

View File

@ -77,7 +77,6 @@
#include "nsDOMStringMap.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsLayoutUtils.h"
#include "mozAutoDocUpdate.h"
#include "nsHtml5Module.h"
@ -1829,11 +1828,8 @@ nsGenericHTMLFormElement::GetDesiredIMEState()
nsIEditor* editor = GetEditorInternal();
if (!editor)
return nsGenericHTMLElement::GetDesiredIMEState();
nsCOMPtr<nsIEditorIMESupport> imeEditor = do_QueryInterface(editor);
if (!imeEditor)
return nsGenericHTMLElement::GetDesiredIMEState();
IMEState state;
nsresult rv = imeEditor->GetPreferredIMEState(&state);
nsresult rv = editor->GetPreferredIMEState(&state);
if (NS_FAILED(rv))
return nsGenericHTMLElement::GetDesiredIMEState();
return state;

View File

@ -27,7 +27,6 @@
#include "nsAttrValueInlines.h"
#include "nsGenericHTMLElement.h"
#include "nsIDOMEventListener.h"
#include "nsIEditorIMESupport.h"
#include "nsIEditorObserver.h"
#include "nsIWidget.h"
#include "nsIDocumentEncoder.h"
@ -2017,10 +2016,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags)
// document may be unloaded.
mValueBeingSet = aValue;
mIsCommittingComposition = true;
nsCOMPtr<nsIEditorIMESupport> editorIMESupport =
do_QueryInterface(mEditor);
MOZ_RELEASE_ASSERT(editorIMESupport);
nsresult rv = editorIMESupport->ForceCompositionEnd();
nsresult rv = mEditor->ForceCompositionEnd();
if (!self.get()) {
return true;
}
@ -2284,9 +2280,8 @@ bool
nsTextEditorState::EditorHasComposition()
{
bool isComposing = false;
nsCOMPtr<nsIEditorIMESupport> editorIMESupport = do_QueryInterface(mEditor);
return editorIMESupport &&
NS_SUCCEEDED(editorIMESupport->GetComposing(&isComposing)) &&
return mEditor &&
NS_SUCCEEDED(mEditor->GetComposing(&isComposing)) &&
isComposing;
}

View File

@ -107,11 +107,6 @@ if CONFIG['OS_ARCH'] == 'WINNT':
if CONFIG['MOZ_SECUREELEMENT']:
DIRS += ['secureelement']
if CONFIG['MOZ_B2G']:
DIRS += [
'downloads',
]
DIRS += ['presentation']
TEST_DIRS += [

View File

@ -22,6 +22,7 @@
#include "nsXULAppAPI.h"
#include "nsEscape.h"
#include "nsNetCID.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "nsServiceManagerUtils.h"
@ -31,6 +32,8 @@ namespace dom {
static const char kStartupTopic[] = "sessionstore-windows-restored";
static const uint32_t kStartupDelay = 0;
const char kTestingPref[] = "dom.storage.testing";
NS_IMPL_ISUPPORTS(DOMStorageObserver,
nsIObserver,
nsISupportsWeakReference)
@ -72,15 +75,9 @@ DOMStorageObserver::Init()
// Observe low device storage notifications.
obs->AddObserver(sSelf, "disk-space-watcher", true);
#ifdef DOM_STORAGE_TESTS
// Testing
obs->AddObserver(sSelf, "domstorage-test-flush-force", true);
if (XRE_IsParentProcess()) {
// Only to forward to child process.
obs->AddObserver(sSelf, "domstorage-test-flushed", true);
}
obs->AddObserver(sSelf, "domstorage-test-reload", true);
#ifdef DOM_STORAGE_TESTS
Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
#endif
return NS_OK;
@ -98,6 +95,32 @@ DOMStorageObserver::Shutdown()
return NS_OK;
}
// static
void
DOMStorageObserver::TestingPrefChanged(const char* aPrefName, void* aClosure)
{
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (!obs) {
return;
}
if (Preferences::GetBool(kTestingPref)) {
obs->AddObserver(sSelf, "domstorage-test-flush-force", true);
if (XRE_IsParentProcess()) {
// Only to forward to child process.
obs->AddObserver(sSelf, "domstorage-test-flushed", true);
}
obs->AddObserver(sSelf, "domstorage-test-reload", true);
} else {
obs->RemoveObserver(sSelf, "domstorage-test-flush-force");
if (XRE_IsParentProcess()) {
// Only to forward to child process.
obs->RemoveObserver(sSelf, "domstorage-test-flushed");
}
obs->RemoveObserver(sSelf, "domstorage-test-reload");
}
}
void
DOMStorageObserver::AddSink(DOMStorageObserverSink* aObs)
{

View File

@ -54,6 +54,8 @@ public:
private:
virtual ~DOMStorageObserver() {}
static void TestingPrefChanged(const char* aPrefName, void* aClosure);
static DOMStorageObserver* sSelf;
// Weak references

View File

@ -43,3 +43,11 @@ function notify(top)
{
os().notifyObservers(null, top, null);
}
/**
* Enable testing observer notifications in DOMStorageObserver.cpp.
*/
function localStorageEnableTestingMode(cb)
{
SpecialPowers.pushPrefEnv({ "set": [["dom.storage.testing", true]] }, cb);
}

View File

@ -29,6 +29,9 @@ Case 3: set | set | clear | reload | count ?= 0
function startTest()
{
// Enable testing observer notifications
localStorageEnableTestingMode(function() {
// Have an untouched land
localStorage.clear();
@ -153,6 +156,7 @@ function startTest()
});
});
});
});
}
SimpleTest.waitForExplicitFinish();

View File

@ -3,7 +3,6 @@
<title>Test localStorage usage while in a low device storage situation</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="localStorageCommon.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript">
@ -23,7 +22,7 @@ This test does the following:
function lowDeviceStorage(lowStorage) {
var data = lowStorage ? "full" : "free";
os().notifyObservers(null, "disk-space-watcher", data);
SpecialPowers.Services.obs.notifyObservers(null, "disk-space-watcher", data);
}
function startTest() {

View File

@ -1,92 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
const DEBUG = false;
const TETHERING_TYPE_WIFI = "wifi";
const TETHERING_TYPE_BLUETOOTH = "bt";
const TETHERING_TYPE_USB = "usb";
function TetheringManager() {
}
TetheringManager.prototype = {
__proto__: DOMRequestIpcHelper.prototype,
classDescription: "TetheringManager",
classID: Components.ID("{bd8a831c-d8ec-4f00-8803-606e50781097}"),
contractID: "@mozilla.org/dom/tetheringmanager;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsISupportsWeakReference,
Ci.nsIObserver]),
init: function(aWindow) {
const messages = ["WifiManager:setWifiTethering:Return:OK",
"WifiManager:setWifiTethering:Return:NO"];
this.initDOMRequestHelper(aWindow, messages);
},
// TODO : aMessage format may be different after supporting bt/usb.
// for now, use wifi format first.
receiveMessage: function(aMessage) {
let data = aMessage.data.data;
let resolver = this.takePromiseResolver(data.resolverId);
if (!resolver) {
return;
}
switch (aMessage.name) {
case "WifiManager:setWifiTethering:Return:OK":
resolver.resolve(data);
break;
case "WifiManager:setWifiTethering:Return:NO":
resolver.reject(data.reason);
break;
}
},
setTetheringEnabled: function setTetheringEnabled(aEnabled, aType, aConfig) {
let self = this;
switch (aType) {
case TETHERING_TYPE_WIFI:
return this.createPromiseWithId(function(aResolverId) {
let data = { resolverId: aResolverId, enabled: aEnabled, config: aConfig };
cpmm.sendAsyncMessage("WifiManager:setWifiTethering", { data: data});
});
case TETHERING_TYPE_BLUETOOTH:
case TETHERING_TYPE_USB:
default:
debug("tethering type(" + aType + ") doesn't support");
return this.createPromiseWithId(function(aResolverId) {
self.takePromiseResolver(aResolverId).reject();
});
}
},
};
this.NSGetFactory =
XPCOMUtils.generateNSGetFactory([TetheringManager]);
var debug;
if (DEBUG) {
debug = function (s) {
dump("-*- TetheringManager component: " + s + "\n");
};
} else {
debug = function (s) {};
}

View File

@ -1,4 +0,0 @@
#TetheringManager.js
component {bd8a831c-d8ec-4f00-8803-606e50781097} TetheringManager.js
contract @mozilla.org/tetheringmanager;1 {bd8a831c-d8ec-4f00-8803-606e50781097}

View File

@ -1,12 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_COMPONENTS += [
'TetheringManager.js',
'TetheringManager.manifest',
]
FINAL_LIBRARY = 'xul'

View File

@ -1,768 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const TYPE_WIFI = "wifi";
const TYPE_BLUETOOTH = "bt";
const TYPE_USB = "usb";
/**
* General tethering setting.
*/
const TETHERING_SETTING_IP = "192.168.1.1";
const TETHERING_SETTNG_PREFIX = "24";
const TETHERING_SETTING_START_IP = "192.168.1.10";
const TETHERING_SETTING_END_IP = "192.168.1.30";
const TETHERING_SETTING_DNS1 = "8.8.8.8";
const TETHERING_SETTING_DNS2 = "8.8.4.4";
const TETHERING_NETWORK_ADDR = "192.168.1.0/24";
/**
* Wifi tethering setting.
*/
const TETHERING_SETTING_SSID = "FirefoxHotSpot";
const TETHERING_SETTING_SECURITY = "open";
const TETHERING_SETTING_KEY = "1234567890";
const SETTINGS_RIL_DATA_ENABLED = 'ril.data.enabled';
const SETTINGS_KEY_DATA_APN_SETTINGS = "ril.data.apnSettings";
// Emulate Promise.jsm semantics.
Promise.defer = function() { return new Deferred(); }
function Deferred() {
this.promise = new Promise(function(resolve, reject) {
this.resolve = resolve;
this.reject = reject;
}.bind(this));
Object.freeze(this);
}
var gTestSuite = (function() {
let suite = {};
let tetheringManager;
let pendingEmulatorShellCount = 0;
/**
* A wrapper function of "is".
*
* Calls the marionette function "is" as well as throws an exception
* if the givens values are not equal.
*
* @param value1
* Any type of value to compare.
*
* @param value2
* Any type of value to compare.
*
* @param message
* Debug message for this check.
*
*/
function isOrThrow(value1, value2, message) {
is(value1, value2, message);
if (value1 !== value2) {
throw message;
}
}
/**
* Send emulator shell command with safe guard.
*
* We should only call |finish()| after all emulator command transactions
* end, so here comes with the pending counter. Resolve when the emulator
* gives positive response, and reject otherwise.
*
* Fulfill params:
* result -- an array of emulator response lines.
* Reject params:
* result -- an array of emulator response lines.
*
* @param aCommand
* A string command to be passed to emulator through its telnet console.
*
* @return A deferred promise.
*/
function runEmulatorShellSafe(aCommand) {
let deferred = Promise.defer();
++pendingEmulatorShellCount;
runEmulatorShell(aCommand, function(aResult) {
--pendingEmulatorShellCount;
ok(true, "Emulator shell response: " + JSON.stringify(aResult));
if (Array.isArray(aResult)) {
deferred.resolve(aResult);
} else {
deferred.reject(aResult);
}
});
return deferred.promise;
}
/**
* Wait for timeout.
*
* Resolve when the given duration elapsed. Never reject.
*
* Fulfill params: (none)
*
* @param aTimeoutMs
* The duration after which the timeout event should occurs.
*
* @return A deferred promise.
*/
function waitForTimeout(aTimeoutMs) {
let deferred = Promise.defer();
setTimeout(function() {
deferred.resolve();
}, aTimeoutMs);
return deferred.promise;
}
/**
* Get mozSettings value specified by @aKey.
*
* Resolve if that mozSettings value is retrieved successfully, reject
* otherwise.
*
* Fulfill params:
* The corresponding mozSettings value of the key.
* Reject params: (none)
*
* @param aKey
* A string.
*
* @return A deferred promise.
*/
function getSettings(aKey) {
let request = navigator.mozSettings.createLock().get(aKey);
return wrapDomRequestAsPromise(request)
.then(function resolve(aEvent) {
ok(true, "getSettings(" + aKey + ") - success");
return aEvent.target.result[aKey];
}, function reject(aEvent) {
ok(false, "getSettings(" + aKey + ") - error");
throw aEvent.target.error;
});
}
/**
* Set mozSettings values.
*
* Resolve if that mozSettings value is set successfully, reject otherwise.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aSettings
* An object of format |{key1: value1, key2: value2, ...}|.
* @return A deferred promise.
*/
function setSettings(aSettings) {
let lock = window.navigator.mozSettings.createLock();
let request = lock.set(aSettings);
let deferred = Promise.defer();
lock.onsettingstransactionsuccess = function () {
ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
deferred.resolve();
};
lock.onsettingstransactionfailure = function (aEvent) {
ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
deferred.reject();
throw aEvent.target.error;
};
return deferred.promise;
}
/**
* Set mozSettings value with only one key.
*
* Resolve if that mozSettings value is set successfully, reject otherwise.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aKey
* A string key.
* @param aValue
* An object value.
* @param aAllowError [optional]
* A boolean value. If set to true, an error response won't be treated
* as test failure. Default: false.
*
* @return A deferred promise.
*/
function setSettings1(aKey, aValue, aAllowError) {
let settings = {};
settings[aKey] = aValue;
return setSettings(settings, aAllowError);
}
/**
* Convenient MozSettings getter for SETTINGS_KEY_DATA_APN_SETTINGS.
*/
function getDataApnSettings(aAllowError) {
return getSettings(SETTINGS_KEY_DATA_APN_SETTINGS, aAllowError);
}
/**
* Convenient MozSettings setter for SETTINGS_KEY_DATA_APN_SETTINGS.
*/
function setDataApnSettings(aApnSettings, aAllowError) {
return setSettings1(SETTINGS_KEY_DATA_APN_SETTINGS, aApnSettings, aAllowError);
}
/**
* Set 'ro.tethering.dun_required' system property to 1. Note that this is a
* 'ro' property, it can only be set once.
*/
function setTetheringDunRequired() {
return runEmulatorShellSafe(['setprop', 'ro.tethering.dun_required', '1']);
}
/**
* Wrap DOMRequest onsuccess/onerror events to Promise resolve/reject.
*
* Fulfill params: A DOMEvent.
* Reject params: A DOMEvent.
*
* @param aRequest
* A DOMRequest instance.
*
* @return A deferred promise.
*/
function wrapDomRequestAsPromise(aRequest) {
let deffered = Promise.defer();
ok(aRequest instanceof DOMRequest,
"aRequest is instanceof" + aRequest.constructor);
aRequest.onsuccess = function(aEvent) {
deffered.resolve(aEvent);
};
aRequest.onerror = function(aEvent) {
deffered.reject(aEvent);
};
return deffered.promise;
}
/**
* Wait for one named MozMobileConnection event.
*
* Resolve if that named event occurs. Never reject.
*
* Fulfill params: the DOMEvent passed.
*
* @param aEventName
* A string event name.
*
* @return A deferred promise.
*/
function waitForMobileConnectionEventOnce(aEventName, aServiceId) {
aServiceId = aServiceId || 0;
let deferred = Promise.defer();
let mobileconnection = navigator.mozMobileConnections[aServiceId];
mobileconnection.addEventListener(aEventName, function onevent(aEvent) {
mobileconnection.removeEventListener(aEventName, onevent);
ok(true, "Mobile connection event '" + aEventName + "' got.");
deferred.resolve(aEvent);
});
return deferred.promise;
}
/**
* Wait for RIL data being connected.
*
* This function will check |MozMobileConnection.data.connected| on
* every 'datachange' event. Resolve when |MozMobileConnection.data.connected|
* becomes the expected state. Never reject.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aConnected
* Boolean that indicates the desired data state.
*
* @param aServiceId [optional]
* A numeric DSDS service id. Default: 0.
*
* @return A deferred promise.
*/
function waitForRilDataConnected(aConnected, aServiceId) {
aServiceId = aServiceId || 0;
return waitForMobileConnectionEventOnce('datachange', aServiceId)
.then(function () {
let mobileconnection = navigator.mozMobileConnections[aServiceId];
if (mobileconnection.data.connected !== aConnected) {
return waitForRilDataConnected(aConnected, aServiceId);
}
});
}
/**
* Verify everything about routing when the wifi tethering is either on or off.
*
* We use two unix commands to verify the routing: 'netcfg' and 'ip route'.
* For now the following two things will be checked:
* 1) The default route interface should be 'rmnet0'.
* 2) wlan0 is up and its ip is set to a private subnet.
*
* We also verify iptables output as netd's NatController will execute
* $ iptables -t nat -A POSTROUTING -o rmnet0 -j MASQUERADE
*
* For tethering through dun, we use 'ip rule' to find the secondary routing
* table and look for default route on that table.
*
* Resolve when the verification is successful and reject otherwise.
*
* Fulfill params: (none)
* Reject params: String that indicates the reason of rejection.
*
* @return A deferred promise.
*/
function verifyTetheringRouting(aEnabled, aIsDun) {
let netcfgResult = {};
let ipRouteResult = {};
let ipSecondaryRouteResult = {};
// Execute 'netcfg' and parse to |netcfgResult|, each key of which is the
// interface name and value is { ip(string) }.
function exeAndParseNetcfg() {
return runEmulatorShellSafe(['netcfg'])
.then(function (aLines) {
// Sample output:
//
// lo UP 127.0.0.1/8 0x00000049 00:00:00:00:00:00
// eth0 UP 10.0.2.15/24 0x00001043 52:54:00:12:34:56
// rmnet1 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:58
// rmnet2 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:59
// rmnet3 DOWN 0.0.0.0/0 0x00001002 52:54:00:12:34:5a
// wlan0 UP 192.168.1.1/24 0x00001043 52:54:00:12:34:5b
// sit0 DOWN 0.0.0.0/0 0x00000080 00:00:00:00:00:00
// rmnet0 UP 10.0.2.100/24 0x00001043 52:54:00:12:34:57
//
aLines.forEach(function (aLine) {
let tokens = aLine.split(/\s+/);
if (tokens.length < 5) {
return;
}
let ifname = tokens[0];
let ip = (tokens[2].split('/'))[0];
netcfgResult[ifname] = { ip: ip };
});
});
}
// Execute 'ip route' and parse to |ipRouteResult|, each key of which is the
// interface name and value is { src(string), default(boolean) }.
function exeAndParseIpRoute() {
return runEmulatorShellSafe(['ip', 'route'])
.then(function (aLines) {
// Sample output:
//
// 10.0.2.4 via 10.0.2.2 dev rmnet0
// 10.0.2.3 via 10.0.2.2 dev rmnet0
// 192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.1
// 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15
// 10.0.2.0/24 dev rmnet0 proto kernel scope link src 10.0.2.100
// default via 10.0.2.2 dev rmnet0
// default via 10.0.2.2 dev eth0 metric 2
//
// Parse source ip for each interface.
aLines.forEach(function (aLine) {
let tokens = aLine.trim().split(/\s+/);
let srcIndex = tokens.indexOf('src');
if (srcIndex < 0 || srcIndex + 1 >= tokens.length) {
return;
}
let ifname = tokens[2];
let src = tokens[srcIndex + 1];
ipRouteResult[ifname] = { src: src, default: false };
});
// Parse default interfaces.
aLines.forEach(function (aLine) {
let tokens = aLine.split(/\s+/);
if (tokens.length < 2) {
return;
}
if ('default' === tokens[0]) {
let ifnameIndex = tokens.indexOf('dev');
if (ifnameIndex < 0 || ifnameIndex + 1 >= tokens.length) {
return;
}
let ifname = tokens[ifnameIndex + 1];
if (ipRouteResult[ifname]) {
ipRouteResult[ifname].default = true;
}
return;
}
});
});
}
// Find MASQUERADE in POSTROUTING section. 'MASQUERADE' should be found
// when tethering is enabled. 'MASQUERADE' shouldn't be found when tethering
// is disabled.
function verifyIptables() {
return runEmulatorShellSafe(['iptables', '-t', 'nat', '-L', 'POSTROUTING'])
.then(function(aLines) {
// $ iptables -t nat -L POSTROUTING
//
// Sample output (tethering on):
//
// Chain POSTROUTING (policy ACCEPT)
// target prot opt source destination
// MASQUERADE all -- anywhere anywhere
//
let found = (function find_MASQUERADE() {
// Skip first two lines.
for (let i = 2; i < aLines.length; i++) {
if (-1 !== aLines[i].indexOf('MASQUERADE')) {
return true;
}
}
return false;
})();
if ((aEnabled && !found) || (!aEnabled && found)) {
throw 'MASQUERADE' + (found ? '' : ' not') + ' found while tethering is ' +
(aEnabled ? 'enabled' : 'disabled');
}
});
}
// Execute 'ip rule show', there must be one rule for tethering network
// address to lookup for a secondary routing table, return that table id.
function verifyIpRule() {
if (!aIsDun) {
return;
}
return runEmulatorShellSafe(['ip', 'rule', 'show'])
.then(function (aLines) {
// Sample output:
//
// 0: from all lookup local
// 32765: from 192.168.1.0/24 lookup 60
// 32766: from all lookup main
// 32767: from all lookup default
//
let tableId = (function findTableId() {
for (let i = 0; i < aLines.length; i++) {
let tokens = aLines[i].split(/\s+/);
if (-1 != tokens.indexOf(TETHERING_NETWORK_ADDR)) {
let lookupIndex = tokens.indexOf('lookup');
if (lookupIndex < 0 || lookupIndex + 1 >= tokens.length) {
return;
}
return tokens[lookupIndex + 1];
}
}
return;
})();
if ((aEnabled && !tableId) || (!aEnabled && tableId)) {
throw 'Secondary table' + (tableId ? '' : ' not') + ' found while tethering is ' +
(aEnabled ? 'enabled' : 'disabled');
}
return tableId;
});
}
// Given the table id, use 'ip rule show table <table id>' to find the
// default route on that secondary routing table.
function execAndParseSecondaryTable(aTableId) {
if (!aIsDun || !aEnabled) {
return;
}
return runEmulatorShellSafe(['ip', 'route', 'show', 'table', aTableId])
.then(function (aLines) {
// We only look for default route in secondary table.
aLines.forEach(function (aLine) {
let tokens = aLine.split(/\s+/);
if (tokens.length < 2) {
return;
}
if ('default' === tokens[0]) {
let ifnameIndex = tokens.indexOf('dev');
if (ifnameIndex < 0 || ifnameIndex + 1 >= tokens.length) {
return;
}
let ifname = tokens[ifnameIndex + 1];
ipSecondaryRouteResult[ifname] = { default: true };
return;
}
});
});
}
function verifyDefaultRouteAndIp(aExpectedWifiTetheringIp) {
log(JSON.stringify(ipRouteResult));
log(JSON.stringify(ipSecondaryRouteResult));
log(JSON.stringify(netcfgResult));
if (aEnabled) {
isOrThrow(ipRouteResult['rmnet0'].src, netcfgResult['rmnet0'].ip, 'rmnet0.ip');
isOrThrow(ipRouteResult['rmnet0'].default, true, 'rmnet0.default');
isOrThrow(ipRouteResult['wlan0'].src, netcfgResult['wlan0'].ip, 'wlan0.ip');
isOrThrow(ipRouteResult['wlan0'].src, aExpectedWifiTetheringIp, 'expected ip');
isOrThrow(ipRouteResult['wlan0'].default, false, 'wlan0.default');
if (aIsDun) {
isOrThrow(ipRouteResult['rmnet1'].src, netcfgResult['rmnet1'].ip, 'rmnet1.ip');
isOrThrow(ipRouteResult['rmnet1'].default, false, 'rmnet1.default');
// Dun's network default route is set on secondary routing table.
isOrThrow(ipSecondaryRouteResult['rmnet1'].default, true, 'secondary rmnet1.default');
}
}
}
return verifyIptables()
.then(verifyIpRule)
.then(tableId => execAndParseSecondaryTable(tableId))
.then(exeAndParseNetcfg)
.then(exeAndParseIpRoute)
.then(() => verifyDefaultRouteAndIp(TETHERING_SETTING_IP));
}
/**
* Request to enable/disable wifi tethering.
*
* Enable/disable wifi tethering by using setTetheringEnabled API
* Resolve when the routing is verified to set up successfully in 20 seconds. The polling
* period is 1 second.
*
* Fulfill params: (none)
* Reject params: The error message.
*
* @param aEnabled
* Boolean that indicates to enable or disable wifi tethering.
* @param aIsDun
* Boolean that indicates whether dun is required.
*
* @return A deferred promise.
*/
function setWifiTetheringEnabled(aEnabled, aIsDun) {
let RETRY_INTERVAL_MS = 1000;
let retryCnt = 20;
let config = {
"ip" : TETHERING_SETTING_IP,
"prefix" : TETHERING_SETTNG_PREFIX,
"startIp" : TETHERING_SETTING_START_IP,
"endIp" : TETHERING_SETTING_END_IP,
"dns1" : TETHERING_SETTING_DNS1,
"dns2" : TETHERING_SETTING_DNS2,
"wifiConfig": {
"ssid" : TETHERING_SETTING_SSID,
"security" : TETHERING_SETTING_SECURITY
}
};
return tetheringManager.setTetheringEnabled(aEnabled, TYPE_WIFI, config)
.then(function waitForRoutingVerified() {
return verifyTetheringRouting(aEnabled, aIsDun)
.then(null, function onreject(aReason) {
log('verifyTetheringRouting rejected due to ' + aReason +
' (' + retryCnt + ')');
if (!retryCnt--) {
throw aReason;
}
return waitForTimeout(RETRY_INTERVAL_MS).then(waitForRoutingVerified);
});
});
}
/**
* Ensure wifi is enabled/disabled.
*
* Issue a wifi enable/disable request if wifi is not in the desired state;
* return a resolved promise otherwise.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @return a resolved promise or deferred promise.
*/
function ensureWifiEnabled(aEnabled) {
let wifiManager = window.navigator.mozWifiManager;
if (wifiManager.enabled === aEnabled) {
return Promise.resolve();
}
let request = wifiManager.setWifiEnabled(aEnabled);
return wrapDomRequestAsPromise(request)
}
/**
* Ensure tethering manager exists.
*
* Check navigator property |mozTetheringManager| to ensure we could access
* tethering related function.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @return A deferred promise.
*/
function ensureTetheringManager() {
let deferred = Promise.defer();
tetheringManager = window.navigator.mozTetheringManager;
if (tetheringManager instanceof MozTetheringManager) {
deferred.resolve();
} else {
log("navigator.mozTetheringManager is unavailable");
deferred.reject();
}
return deferred.promise;
}
/**
* Add required permissions for tethering. Never reject.
*
* The permissions required for wifi testing are 'wifi-manage' and 'settings-write'.
* Never reject.
*
* Fulfill params: (none)
*
* @return A deferred promise.
*/
function acquirePermission() {
let deferred = Promise.defer();
let permissions = [{ 'type': 'wifi-manage', 'allow': 1, 'context': window.document },
{ 'type': 'settings-write', 'allow': 1, 'context': window.document },
{ 'type': 'settings-read', 'allow': 1, 'context': window.document },
{ 'type': 'settings-api-write', 'allow': 1, 'context': window.document },
{ 'type': 'settings-api-read', 'allow': 1, 'context': window.document },
{ 'type': 'mobileconnection', 'allow': 1, 'context': window.document }];
SpecialPowers.pushPermissions(permissions, function() {
deferred.resolve();
});
return deferred.promise;
}
/**
* Common test routine.
*
* Start a test with the given test case chain. The test environment will be
* settled down before the test. After the test, all the affected things will
* be restored.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aTestCaseChain
* The test case entry point, which can be a function or a promise.
*
* @return A deferred promise.
*/
suite.startTest = function(aTestCaseChain) {
function setUp() {
return ensureTetheringManager()
.then(acquirePermission);
}
function tearDown() {
waitFor(finish, function() {
return pendingEmulatorShellCount === 0;
});
}
return setUp()
.then(aTestCaseChain)
.then(function onresolve() {
tearDown();
}, function onreject(aReason) {
ok(false, 'Promise rejects during test' + (aReason ? '(' + aReason + ')' : ''));
tearDown();
});
};
//---------------------------------------------------
// Public test suite functions
//---------------------------------------------------
suite.ensureWifiEnabled = ensureWifiEnabled;
suite.setWifiTetheringEnabled = setWifiTetheringEnabled;
suite.getDataApnSettings = getDataApnSettings;
suite.setDataApnSettings = setDataApnSettings;
suite.setTetheringDunRequired = setTetheringDunRequired;
/**
* The common test routine for wifi tethering.
*
* Set 'ril.data.enabled' to true
* before testing and restore it afterward. It will also verify 'ril.data.enabled'
* and 'tethering.wifi.enabled' to be false in the beginning. Note that this routine
* will NOT change the state of 'tethering.wifi.enabled' so the user should enable
* than disable on his/her own. This routine will only check if tethering is turned
* off after testing.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aTestCaseChain
* The test case entry point, which can be a function or a promise.
*
* @return A deferred promise.
*/
suite.startTetheringTest = function(aTestCaseChain) {
let oriDataEnabled;
function verifyInitialState() {
return getSettings(SETTINGS_RIL_DATA_ENABLED)
.then(enabled => initTetheringTestEnvironment(enabled));
}
function initTetheringTestEnvironment(aEnabled) {
oriDataEnabled = aEnabled;
if (aEnabled) {
return Promise.resolve();
} else {
return Promise.all([waitForRilDataConnected(true),
setSettings1(SETTINGS_RIL_DATA_ENABLED, true)]);
}
}
function restoreToInitialState() {
return setSettings1(SETTINGS_RIL_DATA_ENABLED, oriDataEnabled);
}
return suite.startTest(function() {
return verifyInitialState()
.then(aTestCaseChain)
.then(restoreToInitialState, function onreject(aReason) {
return restoreToInitialState()
.then(() => { throw aReason; }); // Re-throw the orignal reject reason.
});
});
};
return suite;
})();

View File

@ -1,7 +0,0 @@
[DEFAULT]
run-if = buildapp == 'b2g'
[test_wifi_tethering_enabled.js]
; The following test must be the last tethering test ran, as it sets the
; 'ro.tethering.dun_required' property.
[test_wifi_tethering_dun.js]

View File

@ -1,37 +0,0 @@
/* 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/. */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
gTestSuite.startTest(function() {
let origApnSettings;
return gTestSuite.getDataApnSettings()
.then(value => {
origApnSettings = value;
})
.then(() => {
// Set dun apn settings.
let apnSettings = [[ { "carrier": "T-Mobile US",
"apn": "epc1.tmobile.com",
"mmsc": "http://mms.msg.eng.t-mobile.com/mms/wapenc",
"types": ["default","supl","mms"] },
{ "carrier": "T-Mobile US",
"apn": "epc2.tmobile.com",
"types": ["dun"] } ]];
return gTestSuite.setDataApnSettings(apnSettings);
})
.then(() => gTestSuite.setTetheringDunRequired())
.then(() => gTestSuite.startTetheringTest(function() {
return gTestSuite.ensureWifiEnabled(false)
.then(() => gTestSuite.setWifiTetheringEnabled(true, true))
.then(() => gTestSuite.setWifiTetheringEnabled(false, true));
}))
// Restore apn settings.
.then(() => {
if (origApnSettings) {
return gTestSuite.setDataApnSettings(origApnSettings);
}
});
});

View File

@ -1,12 +0,0 @@
/* 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/. */
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
gTestSuite.startTetheringTest(function() {
return gTestSuite.ensureWifiEnabled(false)
.then(() => gTestSuite.setWifiTetheringEnabled(true))
.then(() => gTestSuite.setWifiTetheringEnabled(false));
});

View File

@ -1,18 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
[Constructor(DOMString type, optional DownloadEventInit eventInitDict),
Pref="dom.mozDownloads.enabled",
ChromeOnly]
interface DownloadEvent : Event
{
readonly attribute DOMDownload? download;
};
dictionary DownloadEventInit : EventInit
{
DOMDownload? download = null;
};

View File

@ -1,166 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
// Represents the state of a download.
// "downloading": The resource is actively transfering.
// "stopped" : No network tranfer is happening.
// "succeeded" : The resource has been downloaded successfully.
// "finalized" : We won't try to download this resource, but the DOM
// object is still alive.
enum DownloadState {
"downloading",
"stopped",
"succeeded",
"finalized"
};
[NoInterfaceObject,
NavigatorProperty="mozDownloadManager",
JSImplementation="@mozilla.org/downloads/manager;1",
Pref="dom.mozDownloads.enabled",
ChromeOnly]
interface DOMDownloadManager : EventTarget {
// This promise returns an array of downloads with all the current
// download objects.
Promise<sequence<DOMDownload>> getDownloads();
// Removes one download from the downloads set. Returns a promise resolved
// with the finalized download.
[UnsafeInPrerendering]
Promise<DOMDownload> remove(DOMDownload download);
// Removes all completed downloads. This kicks off an asynchronous process
// that will eventually complete, but will not have completed by the time this
// method returns. If you care about the side-effects of this method, know
// that each existing download will have its onstatechange method invoked and
// will have a new state of "finalized". (After the download is finalized, no
// further events will be generated on it.)
[UnsafeInPrerendering]
void clearAllDone();
// Add completed downloads from applications that must perform the download
// process themselves. For example, email. The method is resolved with a
// fully populated DOMDownload instance on success, or rejected in the
// event all required options were not provided.
//
// The adopted download will also be reported via the ondownloadstart event
// handler.
//
// Applications must currently be certified to use this, but it could be
// widened at a later time.
//
// Note that "download" is not actually optional, but WebIDL requires that it
// be marked as such because it is not followed by a required argument. The
// promise will be rejected if the dictionary is omitted or the specified
// file does not exist on disk.
Promise<DOMDownload> adoptDownload(optional AdoptDownloadDict download);
// Fires when a new download starts.
attribute EventHandler ondownloadstart;
};
[JSImplementation="@mozilla.org/downloads/download;1",
Pref="dom.mozDownloads.enabled",
ChromeOnly]
interface DOMDownload : EventTarget {
// The full size of the resource.
readonly attribute long long totalBytes;
// The number of bytes that we have currently downloaded.
readonly attribute long long currentBytes;
// The url of the resource.
readonly attribute DOMString url;
// The full path in local storage where the file will end up once the download
// is complete. This is equivalent to the concatenation of the 'storagePath'
// to the 'mountPoint' of the nsIVolume associated with the 'storageName'
// (with delimiter).
readonly attribute DOMString path;
// The DeviceStorage volume name on which the file is being downloaded.
readonly attribute DOMString storageName;
// The DeviceStorage path on the volume with 'storageName' of the file being
// downloaded.
readonly attribute DOMString storagePath;
// The state of the download. One of: downloading, stopped, succeeded, or
// finalized. A finalized download is a download that has been removed /
// cleared and is no longer tracked by the download manager and will not
// receive any further onstatechange updates.
readonly attribute DownloadState state;
// The mime type for this resource.
readonly attribute DOMString contentType;
// The timestamp this download started.
readonly attribute Date startTime;
// An opaque identifier for this download. All instances of the same
// download (eg. in different windows) will have the same id.
readonly attribute DOMString id;
// The manifestURL of the application that added this download. Only used for
// downloads added via the adoptDownload API call.
readonly attribute DOMString? sourceAppManifestURL;
// A DOM error object, that will be not null when a download is stopped
// because something failed.
readonly attribute DOMError? error;
// Pauses the download.
[UnsafeInPrerendering]
Promise<DOMDownload> pause();
// Resumes the download. This resolves only once the download has
// succeeded.
[UnsafeInPrerendering]
Promise<DOMDownload> resume();
// This event is triggered anytime a property of the object changes:
// - when the transfer progresses, updating currentBytes.
// - when the state and/or error attributes change.
attribute EventHandler onstatechange;
};
// Used to initialize the DOMDownload object for adopted downloads.
// fields directly maps to the DOMDownload fields.
dictionary AdoptDownloadDict {
// The URL of this resource if there is one available. An empty string if
// the download is not accessible via URL. An empty string is chosen over
// null so that existinc code does not need to null-check but the value is
// still falsey. (Note: If you do have a usable URL, you should probably not
// be using the adoptDownload API and instead be initiating downloads the
// normal way.)
DOMString url;
// The storageName of the DeviceStorage instance the file was saved to.
// Required but marked as optional so the bindings don't auto-coerce the value
// null to "null".
DOMString? storageName;
// The path of the file within the DeviceStorage instance named by
// 'storageName'. This is used to automatically compute the 'path' of the
// download. Note that when DeviceStorage gives you a path to a file, the
// first path segment is the name of the specific device storage and you do
// *not* want to include this. For example, if DeviceStorage tells you the
// file has a path of '/sdcard1/actual/path/file.ext', then the storageName
// should be 'sdcard1' and the storagePath should be 'actual/path/file.ext'.
//
// The existence of the file will be validated will be validated with stat()
// and the size the file-system tells us will be what we use.
//
// Required but marked as optional so the bindings don't auto-coerce the value
// null to "null".
DOMString? storagePath;
// The mime type for this resource. Required, but marked as optional because
// WebIDL otherwise auto-coerces the value null to "null".
DOMString? contentType;
// The time the download was started. If omitted, the current time is used.
Date? startTime;
};

View File

@ -1,57 +0,0 @@
/* 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/. */
enum TetheringType {
"bluetooth",
"usb",
"wifi"
};
enum SecurityType {
"open",
"wpa-psk",
"wpa2-psk"
};
dictionary WifiTetheringConfig {
DOMString ssid;
SecurityType security;
DOMString key;
};
dictionary TetheringConfiguration {
DOMString ip;
DOMString prefix;
DOMString startIp;
DOMString endIp;
DOMString dns1;
DOMString dns2;
WifiTetheringConfig wifiConfig;
};
[JSImplementation="@mozilla.org/tetheringmanager;1",
NavigatorProperty="mozTetheringManager",
ChromeOnly]
interface MozTetheringManager {
/**
* Enable/Disable tethering.
* @param enabled True to enable tethering, False to disable tethering.
* @param type Tethering type to enable/disable.
* @param config Configuration should have following fields when enable is True:
* - ip ip address.
* - prefix mask length.
* - startIp start ip address allocated by DHCP server for tethering.
* - endIp end ip address allocated by DHCP server for tethering.
* - dns1 first DNS server address.
* - dns2 second DNS server address.
* - wifiConfig wifi tethering configuration
* - ssid SSID network name.
* - security open, wpa-psk or wpa2-psk.
* - key password for wpa-psk or wpa2-psk.
* config should not be set when enabled is False.
*/
Promise<any> setTetheringEnabled(boolean enabled,
TetheringType type,
optional TetheringConfiguration config);
};

View File

@ -128,7 +128,6 @@ WEBIDL_FILES = [
'DOMStringList.webidl',
'DOMStringMap.webidl',
'DOMTokenList.webidl',
'Downloads.webidl',
'DragEvent.webidl',
'DynamicsCompressorNode.webidl',
'Element.webidl',
@ -320,7 +319,6 @@ WEBIDL_FILES = [
'MouseScrollEvent.webidl',
'MozPowerManager.webidl',
'MozSelfSupport.webidl',
'MozTetheringManager.webidl',
'MozTimeManager.webidl',
'MozWakeLock.webidl',
'MutationEvent.webidl',
@ -687,7 +685,6 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'DeviceProximityEvent.webidl',
'DeviceStorageAreaChangedEvent.webidl',
'DeviceStorageChangeEvent.webidl',
'DownloadEvent.webidl',
'ErrorEvent.webidl',
'FontFaceSetLoadEvent.webidl',
'GamepadAxisMoveEvent.webidl',

View File

@ -141,8 +141,7 @@ struct IMEState;
* delegate the actual commands to the editor independent of the XPFE
* implementation.
*/
class EditorBase : public nsIEditor
, public nsIEditorIMESupport
class EditorBase : public nsIEditorIMESupport
, public nsSupportsWeakReference
, public nsIPhonetic
{

View File

@ -37,7 +37,6 @@
#include "nsIDOMNode.h" // for nsIDOMNode
#include "nsIDocument.h" // for nsIDocument
#include "nsIEditor.h" // for EditorBase::GetSelection, etc.
#include "nsIEditorIMESupport.h"
#include "nsIEditorMailSupport.h" // for nsIEditorMailSupport
#include "nsIFocusManager.h" // for nsIFocusManager
#include "nsIFormControl.h" // for nsIFormControl, etc.

View File

@ -103,6 +103,9 @@ public:
bool GetReturnInParagraphCreatesNewParagraph();
Element* GetSelectionContainer();
// nsIEditor overrides
NS_IMETHOD GetPreferredIMEState(widget::IMEState* aState) override;
// TextEditor overrides
NS_IMETHOD GetIsDocumentEditable(bool* aIsDocumentEditable) override;
NS_IMETHOD BeginningOfDocument() override;
@ -124,9 +127,6 @@ public:
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
// nsIEditorIMESupport overrides
NS_IMETHOD GetPreferredIMEState(widget::IMEState* aState) override;
// nsIHTMLEditor methods
NS_DECL_NSIHTMLEDITOR

View File

@ -51,7 +51,6 @@
#include "nsIDOMNode.h"
#include "nsIDocument.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsIEditorMailSupport.h"
#include "nsIEditRules.h"
#include "nsIFile.h"

View File

@ -27,7 +27,6 @@
#include "nsIContentIterator.h"
#include "nsIDOMElement.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsIEditRules.h"
#include "nsNameSpaceManager.h"
#include "nsINode.h"

View File

@ -40,7 +40,6 @@
#include "nsIDOMNode.h"
#include "nsIDOMNodeList.h"
#include "nsIDocumentEncoder.h"
#include "nsIEditorIMESupport.h"
#include "nsIEditRules.h"
#include "nsINode.h"
#include "nsIPresShell.h"

View File

@ -28,7 +28,6 @@
#include "nsIDragService.h"
#include "nsIDragSession.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIPrincipal.h"

View File

@ -34,14 +34,12 @@ SimpleTest.waitForFocus(()=>{
return isNSEditableElement() ? SpecialPowers.wrap(aEditor)
.QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement)
.editor
.QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport)
.composing :
SpecialPowers.wrap(window)
.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
.getInterface(SpecialPowers.Ci.nsIWebNavigation)
.QueryInterface(SpecialPowers.Ci.nsIDocShell)
.editor
.QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport)
.composing;
}
function clear() {

View File

@ -20,17 +20,15 @@ SimpleTest.waitForExplicitFinish();
var gInputElement = document.getElementById("input");
function getEditorIMESupport(aInputElement)
function getEditor(aInputElement)
{
var editableElement = SpecialPowers.wrap(aInputElement).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement);
ok(editableElement, "The input element doesn't have nsIDOMNSEditableElement interface");
ok(editableElement.editor, "There is no editor for the input element");
var editorIMESupport = SpecialPowers.wrap(editableElement).editor.QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport);
ok(editorIMESupport, "The input element doesn't have nsIEditorIMESupport interface");
return editorIMESupport;
return editableElement.editor;
}
var gEditorIMESupport;
var gEditor;
function testNotGenerateCompositionByCreatedEvents(aEventInterface)
{
@ -41,7 +39,7 @@ function testNotGenerateCompositionByCreatedEvents(aEventInterface)
compositionEvent.initMouseEvent("compositionstart", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
}
gInputElement.dispatchEvent(compositionEvent);
ok(!gEditorIMESupport.composing, "Composition shouldn't be started with a created compositionstart event (" + aEventInterface + ")");
ok(!gEditor.composing, "Composition shouldn't be started with a created compositionstart event (" + aEventInterface + ")");
compositionEvent = document.createEvent(aEventInterface);
if (compositionEvent.initCompositionEvent) {
@ -50,7 +48,7 @@ function testNotGenerateCompositionByCreatedEvents(aEventInterface)
compositionEvent.initMouseEvent("compositionupdate", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
}
gInputElement.dispatchEvent(compositionEvent);
ok(!gEditorIMESupport.composing, "Composition shouldn't be started with a created compositionupdate event (" + aEventInterface + ")");
ok(!gEditor.composing, "Composition shouldn't be started with a created compositionupdate event (" + aEventInterface + ")");
is(gInputElement.value, "", "Input element shouldn't be modified with a created compositionupdate event (" + aEventInterface + ")");
compositionEvent = document.createEvent(aEventInterface);
@ -60,14 +58,14 @@ function testNotGenerateCompositionByCreatedEvents(aEventInterface)
compositionEvent.initMouseEvent("compositionend", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
}
gInputElement.dispatchEvent(compositionEvent);
ok(!gEditorIMESupport.composing, "Composition shouldn't be committed with a created compositionend event (" + aEventInterface + ")");
ok(!gEditor.composing, "Composition shouldn't be committed with a created compositionend event (" + aEventInterface + ")");
is(gInputElement.value, "", "Input element shouldn't be committed with a created compositionend event (" + aEventInterface + ")");
}
function doTests()
{
gInputElement.focus();
gEditorIMESupport = getEditorIMESupport(gInputElement);
gEditor = getEditor(gInputElement);
testNotGenerateCompositionByCreatedEvents("CompositionEvent");
testNotGenerateCompositionByCreatedEvents("MouseEvent");

View File

@ -21,6 +21,16 @@ interface nsIEditActionListener;
interface nsIInlineSpellChecker;
interface nsITransferable;
%{C++
namespace mozilla {
namespace widget {
struct IMEState;
} // namespace widget
} // namespace mozilla
%}
native IMEState(mozilla::widget::IMEState);
[scriptable, uuid(094be624-f0bf-400f-89e2-6a84baab9474)]
interface nsIEditor : nsISupports
{
@ -564,4 +574,19 @@ interface nsIEditor : nsISupports
* or nsIEditorObserver::CancelEditAction(). Otherwise, false.
*/
[noscript] readonly attribute boolean isInEditAction;
/**
* forceCompositionEnd() force the composition end
*/
void forceCompositionEnd();
/**
* Get preferred IME status of current widget.
*/
[noscript] IMEState getPreferredIMEState();
/**
* whether this editor has active IME transaction
*/
readonly attribute boolean composing;
};

View File

@ -3,38 +3,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "domstubs.idl"
%{C++
namespace mozilla {
namespace widget {
struct IMEState;
} // namespace widget
} // namespace mozilla
%}
native IMEState(mozilla::widget::IMEState);
#include "nsIEditor.idl"
[scriptable, uuid(0ba7f490-afb8-46dd-87fc-bc6137fbc899)]
interface nsIEditorIMESupport : nsISupports
interface nsIEditorIMESupport : nsIEditor
{
/**
* forceCompositionEnd() force the composition end
*/
void forceCompositionEnd();
/**
* Get preferred IME status of current widget.
*/
[noscript] IMEState getPreferredIMEState();
/**
* whether this editor has active IME transaction
*/
readonly attribute boolean composing;
};

View File

@ -736,7 +736,7 @@ struct JSClass {
// application.
#define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
#define JSCLASS_GLOBAL_SLOT_COUNT \
(JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 39)
(JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 40)
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
#define JSCLASS_GLOBAL_FLAGS \

View File

@ -11,6 +11,7 @@
#include "builtin/Intl.h"
#include "mozilla/Casting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Range.h"
@ -25,6 +26,7 @@
#include "builtin/IntlTimeZoneData.h"
#include "ds/Sort.h"
#if ENABLE_INTL_API
#include "unicode/plurrule.h"
#include "unicode/ucal.h"
#include "unicode/ucol.h"
#include "unicode/udat.h"
@ -32,6 +34,7 @@
#include "unicode/uenum.h"
#include "unicode/unum.h"
#include "unicode/unumsys.h"
#include "unicode/upluralrules.h"
#include "unicode/ustring.h"
#endif
#include "vm/DateTime.h"
@ -47,6 +50,7 @@
using namespace js;
using mozilla::AssertedCast;
using mozilla::IsFinite;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
@ -79,15 +83,46 @@ using mozilla::RangedPtr;
namespace {
typedef bool UBool;
typedef char16_t UChar;
typedef double UDate;
enum UErrorCode {
U_ZERO_ERROR,
U_BUFFER_OVERFLOW_ERROR,
};
}
namespace icu {
class StringEnumeration {
public:
explicit StringEnumeration();
};
StringEnumeration::StringEnumeration()
{
MOZ_CRASH("StringEnumeration::StringEnumeration: Intl API disabled");
}
class PluralRules {
public:
StringEnumeration* getKeywords(UErrorCode& status) const;
};
StringEnumeration*
PluralRules::getKeywords(UErrorCode& status) const
{
MOZ_CRASH("PluralRules::getKeywords: Intl API disabled");
}
} // icu namespace
namespace {
typedef bool UBool;
typedef char16_t UChar;
typedef double UDate;
inline UBool
U_FAILURE(UErrorCode code)
{
@ -118,6 +153,32 @@ UCharToChar16(const UChar* chars)
MOZ_CRASH("UCharToChar16: Intl API disabled");
}
const char*
uloc_getAvailable(int32_t n)
{
MOZ_CRASH("uloc_getAvailable: Intl API disabled");
}
int32_t
uloc_countAvailable()
{
MOZ_CRASH("uloc_countAvailable: Intl API disabled");
}
struct UFormattable;
void
ufmt_close(UFormattable* fmt)
{
MOZ_CRASH("ufmt_close: Intl API disabled");
}
double
ufmt_getDouble(UFormattable* fmt, UErrorCode *status)
{
MOZ_CRASH("ufmt_getDouble: Intl API disabled");
}
struct UEnumeration;
int32_t
@ -138,6 +199,12 @@ uenum_close(UEnumeration* en)
MOZ_CRASH("uenum_close: Intl API disabled");
}
UEnumeration*
uenum_openFromStringEnumeration(icu::StringEnumeration* adopted, UErrorCode* ec)
{
MOZ_CRASH("uenum_openFromStringEnumeration: Intl API disabled");
}
struct UCollator;
enum UColAttribute {
@ -317,6 +384,17 @@ unum_setTextAttribute(UNumberFormat* fmt, UNumberFormatTextAttribute tag, const
MOZ_CRASH("unum_setTextAttribute: Intl API disabled");
}
UFormattable*
unum_parseToUFormattable(const UNumberFormat* fmt,
UFormattable *result,
const UChar* text,
int32_t textLength,
int32_t* parsePos, /* 0 = start */
UErrorCode* status)
{
MOZ_CRASH("unum_parseToUFormattable: Intl API disabled");
}
typedef void* UNumberingSystem;
UNumberingSystem*
@ -681,6 +759,34 @@ udat_getSymbols(const UDateFormat *fmt, UDateFormatSymbolType type, int32_t symb
MOZ_CRASH("udat_getSymbols: Intl API disabled");
}
typedef void* UPluralRules;
enum UPluralType {
UPLURAL_TYPE_CARDINAL,
UPLURAL_TYPE_ORDINAL
};
void
uplrules_close(UPluralRules *uplrules)
{
MOZ_CRASH("uplrules_close: Intl API disabled");
}
UPluralRules*
uplrules_openForType(const char *locale, UPluralType type, UErrorCode *status)
{
MOZ_CRASH("uplrules_openForType: Intl API disabled");
}
int32_t
uplrules_select(const UPluralRules *uplrules,
double number,
UChar *keyword, int32_t capacity,
UErrorCode *status)
{
MOZ_CRASH("uplrules_select: Intl API disabled");
}
} // anonymous namespace
#endif
@ -1612,6 +1718,100 @@ js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/**
*
* This creates new UNumberFormat with calculated digit formatting
* properties for PluralRules.
*
* This is similar to NewUNumberFormat but doesn't allow for currency or
* percent types.
*
*/
static UNumberFormat*
NewUNumberFormatForPluralRules(JSContext* cx, HandleObject pluralRules)
{
RootedObject internals(cx, GetInternals(cx, pluralRules));
if (!internals)
return nullptr;
RootedValue value(cx);
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return nullptr;
JSAutoByteString locale(cx, value.toString());
if (!locale)
return nullptr;
uint32_t uMinimumIntegerDigits = 1;
uint32_t uMinimumFractionDigits = 0;
uint32_t uMaximumFractionDigits = 3;
int32_t uMinimumSignificantDigits = -1;
int32_t uMaximumSignificantDigits = -1;
RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
bool hasP;
if (!HasProperty(cx, internals, id, &hasP))
return nullptr;
if (hasP) {
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
&value))
{
return nullptr;
}
uMinimumSignificantDigits = value.toInt32();
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
&value))
{
return nullptr;
}
uMaximumSignificantDigits = value.toInt32();
} else {
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
{
return nullptr;
}
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
&value))
{
return nullptr;
}
uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
&value))
{
return nullptr;
}
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
}
UErrorCode status = U_ZERO_ERROR;
UNumberFormat* nf = unum_open(UNUM_DECIMAL, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return nullptr;
}
ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
if (uMinimumSignificantDigits != -1) {
unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
} else {
unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
}
return toClose.forget();
}
/**
* Returns a new UNumberFormat with the locale and number formatting options
* of the given NumberFormat.
@ -1695,32 +1895,32 @@ NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
{
return nullptr;
}
uMinimumSignificantDigits = int32_t(value.toNumber());
uMinimumSignificantDigits = value.toInt32();
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
&value))
{
return nullptr;
}
uMaximumSignificantDigits = int32_t(value.toNumber());
uMaximumSignificantDigits = value.toInt32();
} else {
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
{
return nullptr;
}
uMinimumIntegerDigits = int32_t(value.toNumber());
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
&value))
{
return nullptr;
}
uMinimumFractionDigits = int32_t(value.toNumber());
uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
&value))
{
return nullptr;
}
uMaximumFractionDigits = int32_t(value.toNumber());
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
}
if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
@ -3427,6 +3627,381 @@ js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/**************** PluralRules *****************/
static void pluralRules_finalize(FreeOp* fop, JSObject* obj);
static const uint32_t UPLURAL_RULES_SLOT = 0;
static const uint32_t PLURAL_RULES_SLOTS_COUNT = 1;
static const ClassOps PluralRulesClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
pluralRules_finalize
};
static const Class PluralRulesClass = {
js_Object_str,
JSCLASS_HAS_RESERVED_SLOTS(PLURAL_RULES_SLOTS_COUNT) |
JSCLASS_FOREGROUND_FINALIZE,
&PluralRulesClassOps
};
#if JS_HAS_TOSOURCE
static bool
pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().PluralRules);
return true;
}
#endif
static const JSFunctionSpec pluralRules_static_methods[] = {
JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_PluralRules_supportedLocalesOf", 1, 0),
JS_FS_END
};
static const JSFunctionSpec pluralRules_methods[] = {
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_PluralRules_resolvedOptions", 0, 0),
JS_SELF_HOSTED_FN("select", "Intl_PluralRules_select", 1, 0),
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, pluralRules_toSource, 0, 0),
#endif
JS_FS_END
};
/**
* PluralRules constructor.
* Spec: ECMAScript 402 API, PluralRules, 1.1
*/
static bool
PluralRules(JSContext* cx, const CallArgs& args, bool construct)
{
RootedObject obj(cx);
if (!construct) {
JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
if (!intl)
return false;
RootedValue self(cx, args.thisv());
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
obj = ToObject(cx, self);
if (!obj)
return false;
bool extensible;
if (!IsExtensible(cx, obj, &extensible))
return false;
if (!extensible)
return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
} else {
construct = true;
}
}
if (construct) {
RootedObject proto(cx, cx->global()->getOrCreatePluralRulesPrototype(cx));
if (!proto)
return false;
obj = NewObjectWithGivenProto(cx, &PluralRulesClass, proto);
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(UPLURAL_RULES_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
if (!IntlInitialize(cx, obj, cx->names().InitializePluralRules, locales, options))
return false;
args.rval().setObject(*obj);
return true;
}
static bool
PluralRules(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return PluralRules(cx, args, args.isConstructing());
}
bool
js::intl_PluralRules(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
return PluralRules(cx, args, true);
}
static void
pluralRules_finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->onMainThread());
// This is-undefined check shouldn't be necessary, but for internal
// brokenness in object allocation code. For the moment, hack around it by
// explicitly guarding against the possibility of the reserved slot not
// containing a private. See bug 949220.
const Value& slot = obj->as<NativeObject>().getReservedSlot(UPLURAL_RULES_SLOT);
if (!slot.isUndefined()) {
if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
uplrules_close(pr);
}
}
static JSObject*
CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
{
RootedFunction ctor(cx);
ctor = global->createConstructor(cx, &PluralRules, cx->names().PluralRules, 0);
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, global->createBlankPrototype(cx, &PluralRulesClass));
if (!proto)
return nullptr;
proto->setReservedSlot(UPLURAL_RULES_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (!JS_DefineFunctions(cx, ctor, pluralRules_static_methods))
return nullptr;
if (!JS_DefineFunctions(cx, proto, pluralRules_methods))
return nullptr;
RootedValue options(cx);
if (!CreateDefaultOptions(cx, &options))
return nullptr;
if (!IntlInitialize(cx, proto, cx->names().InitializePluralRules, UndefinedHandleValue,
options))
{
return nullptr;
}
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().PluralRules, ctorValue, nullptr, nullptr, 0))
return nullptr;
return proto;
}
bool
js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedValue result(cx);
// We're going to use ULocale availableLocales as per ICU recommendation:
// https://ssl.icu-project.org/trac/ticket/12756
if (!intl_availableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
return false;
args.rval().set(result);
return true;
}
bool
js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject pluralRules(cx, &args[0].toObject());
UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
if (!nf)
return false;
ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
RootedObject internals(cx, GetInternals(cx, pluralRules));
if (!internals)
return false;
RootedValue value(cx);
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
return false;
JSAutoByteString locale(cx, value.toString());
if (!locale)
return false;
if (!GetProperty(cx, internals, internals, cx->names().type, &value))
return false;
JSAutoByteString type(cx, value.toString());
if (!type)
return false;
double x = args[1].toNumber();
// We need a NumberFormat in order to format the number
// using the number formatting options (minimum/maximum*Digits)
// before we push the result to PluralRules
//
// This should be fixed in ICU 59 and we'll be able to switch to that
// API: http://bugs.icu-project.org/trac/ticket/12763
//
RootedValue fmtNumValue(cx);
if (!intl_FormatNumber(cx, nf, x, &fmtNumValue))
return false;
RootedString fmtNumValueString(cx, fmtNumValue.toString());
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, fmtNumValueString))
return false;
const UChar* uFmtNumValue = Char16ToUChar(stableChars.twoByteRange().begin().get());
UErrorCode status = U_ZERO_ERROR;
UFormattable* fmt = unum_parseToUFormattable(nf, nullptr, uFmtNumValue,
stableChars.twoByteRange().length(), 0, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
ScopedICUObject<UFormattable, ufmt_close> closeUFormattable(fmt);
double y = ufmt_getDouble(fmt, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
UPluralType category;
if (equal(type, "cardinal")) {
category = UPLURAL_TYPE_CARDINAL;
} else {
MOZ_ASSERT(equal(type, "ordinal"));
category = UPLURAL_TYPE_ORDINAL;
}
UPluralRules* pr = uplrules_openForType(icuLocale(locale.ptr()), category, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
return false;
int size = uplrules_select(pr, y, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, &status);
if (status == U_BUFFER_OVERFLOW_ERROR) {
if (!chars.resize(size))
return false;
status = U_ZERO_ERROR;
uplrules_select(pr, y, Char16ToUChar(chars.begin()), size, &status);
}
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
if (!str)
return false;
args.rval().setString(str);
return true;
}
bool
js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
return false;
JSAutoByteString type(cx, args[1].toString());
if (!type)
return false;
UErrorCode status = U_ZERO_ERROR;
UPluralType category;
if (equal(type, "cardinal")) {
category = UPLURAL_TYPE_CARDINAL;
} else {
MOZ_ASSERT(equal(type, "ordinal"));
category = UPLURAL_TYPE_ORDINAL;
}
UPluralRules* pr = uplrules_openForType(
icuLocale(locale.ptr()),
category,
&status
);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
// We should get a C API for that in ICU 59 and switch to it
// https://ssl.icu-project.org/trac/ticket/12772
//
icu::StringEnumeration* kwenum =
reinterpret_cast<icu::PluralRules*>(pr)->getKeywords(status);
UEnumeration* ue = uenum_openFromStringEnumeration(kwenum, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> closeEnum(ue);
RootedObject res(cx, NewDenseEmptyArray(cx));
if (!res)
return false;
RootedValue element(cx);
uint32_t i = 0;
int32_t catSize;
const char* cat;
do {
cat = uenum_next(ue, &catSize, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
if (!cat)
break;
JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
if (!str)
return false;
element.setString(str);
if (!DefineElement(cx, res, i, element))
return false;
i++;
} while (true);
args.rval().setObject(*res);
return true;
}
bool
js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
{
@ -3916,6 +4491,9 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
if (!numberFormatProto)
return false;
RootedObject pluralRulesProto(cx, CreatePluralRulesPrototype(cx, intl, global));
if (!pluralRulesProto)
return false;
// The |Intl| object is fully set up now, so define the global property.
RootedValue intlValue(cx, ObjectValue(*intl));
@ -3937,6 +4515,7 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));
// Also cache |Intl| to implement spec language that conditions behavior
// based on values being equal to "the standard built-in |Intl| object".

View File

@ -363,6 +363,54 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
/******************** PluralRules ********************/
/**
* Returns a new PluralRules instance.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: pluralRules = intl_PluralRules(locales, options)
*/
extern MOZ_MUST_USE bool
intl_PluralRules(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an object indicating the supported locales for plural rules
* by having a true-valued property for each such locale with the
* canonicalized language tag as the property name. The object has no
* prototype.
*
* Usage: availableLocales = intl_PluralRules_availableLocales()
*/
extern MOZ_MUST_USE bool
intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a plural rule for the number x according to the effective
* locale and the formatting options of the given PluralRules.
*
* A plural rule is a grammatical category that expresses count distinctions
* (such as "one", "two", "few" etc.).
*
* Usage: rule = intl_SelectPluralRule(pluralRules, x)
*/
extern MOZ_MUST_USE bool
intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns an array of plural rules categories for a given
* locale and type.
*
* Usage: categories = intl_GetPluralCategories(locale, type)
*
* Example:
*
* intl_getPluralCategories('pl', 'cardinal'); // ['one', 'few', 'many', 'other']
*/
extern MOZ_MUST_USE bool
intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a plain object with calendar information for a single valid locale
* (callers must perform this validation). The object will have these

View File

@ -21,6 +21,9 @@
intl_availableCalendars: false,
intl_patternForSkeleton: false,
intl_FormatDateTime: false,
intl_SelectPluralRule: false,
intl_GetPluralCategories: false,
intl_GetCalendarInfo: false,
*/
/*
@ -839,6 +842,7 @@ function BestAvailableLocaleIgnoringDefault(availableLocales, locale) {
return BestAvailableLocaleHelper(availableLocales, locale, false);
}
var noRelevantExtensionKeys = [];
/**
* Compares a BCP 47 language priority list against the set of locales in
@ -1252,7 +1256,9 @@ function initializeIntlObject(obj) {
function setLazyData(internals, type, lazyData)
{
assert(internals.type === "partial", "can't set lazy data for anything but a newborn");
assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type");
assert(type === "Collator" || type === "DateTimeFormat" ||
type == "NumberFormat" || type === "PluralRules",
"bad type");
assert(IsObject(lazyData), "non-object lazy data");
// Set in reverse order so that the .type change is a barrier.
@ -1302,7 +1308,9 @@ function isInitializedIntlObject(obj) {
if (IsObject(internals)) {
assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type");
var type = internals.type;
assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type");
assert(type === "partial" || type === "Collator" ||
type === "DateTimeFormat" || type === "NumberFormat" || type === "PluralRules",
"unexpected type");
assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData");
assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps");
} else {
@ -1359,6 +1367,8 @@ function getInternals(obj)
internalProps = resolveCollatorInternals(lazyData)
else if (type === "DateTimeFormat")
internalProps = resolveDateTimeFormatInternals(lazyData)
else if (type === "PluralRules")
internalProps = resolvePluralRulesInternals(lazyData)
else
internalProps = resolveNumberFormatInternals(lazyData);
setInternalProperties(internals, internalProps);
@ -1758,45 +1768,37 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
// Step 6.
var opt = lazyNumberFormatData.opt;
// Compute effective locale.
// Step 9.
var NumberFormat = numberFormatInternalProperties;
// Step 10.
// Step 9.
var localeData = NumberFormat.localeData;
// Step 11.
// Step 10.
var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
lazyNumberFormatData.requestedLocales,
lazyNumberFormatData.opt,
NumberFormat.relevantExtensionKeys,
localeData);
// Steps 12-13. (Step 14 is not relevant to our implementation.)
// Steps 11-12. (Step 13 is not relevant to our implementation.)
internalProps.locale = r.locale;
internalProps.numberingSystem = r.nu;
// Compute formatting options.
// Step 16.
// Step 15.
var s = lazyNumberFormatData.style;
internalProps.style = s;
// Steps 20, 22.
// Steps 19, 21.
if (s === "currency") {
internalProps.currency = lazyNumberFormatData.currency;
internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay;
}
// Step 24.
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
// Steps 27.
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
// Step 30.
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
// Step 33.
if ("minimumSignificantDigits" in lazyNumberFormatData) {
// Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
// actual presence (versus undefined-ness) of these properties.
@ -1805,10 +1807,10 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits;
}
// Step 35.
// Step 27.
internalProps.useGrouping = lazyNumberFormatData.useGrouping;
// Step 42.
// Step 34.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
@ -1836,6 +1838,42 @@ function getNumberFormatInternals(obj, methodName) {
return internalProps;
}
/**
* Applies digit options used for number formatting onto the intl object.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
*/
function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault) {
// We skip Step 1 because we set the properties on a lazyData object.
// Step 2-3.
assert(IsObject(options), "SetNumberFormatDigitOptions");
assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
// Steps 4-6.
const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20);
// Steps 7-8.
let mnsd = options.minimumSignificantDigits;
let mxsd = options.maximumSignificantDigits;
// Steps 9-11.
lazyData.minimumIntegerDigits = mnid;
lazyData.minimumFractionDigits = mnfd;
lazyData.maximumFractionDigits = mxfd;
// Step 12.
if (mnsd !== undefined || mxsd !== undefined) {
mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
lazyData.minimumSignificantDigits = mnsd;
lazyData.maximumSignificantDigits = mxsd;
}
}
/**
* Initializes an object as a NumberFormat.
@ -1885,7 +1923,7 @@ function InitializeNumberFormat(numberFormat, locales, options) {
// }
//
// Note that lazy data is only installed as a final step of initialization,
// so every Collator lazy data object has *all* these properties, never a
// so every NumberFormat lazy data object has *all* these properties, never a
// subset of them.
var lazyNumberFormatData = std_Object_create(null);
@ -1915,11 +1953,11 @@ function InitializeNumberFormat(numberFormat, locales, options) {
opt.localeMatcher = matcher;
// Compute formatting options.
// Step 15.
// Step 14.
var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
lazyNumberFormatData.style = s;
// Steps 17-20.
// Steps 16-19.
var c = GetOption(options, "currency", "string", undefined, undefined);
if (c !== undefined && !IsWellFormedCurrencyCode(c))
ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c);
@ -1934,48 +1972,30 @@ function InitializeNumberFormat(numberFormat, locales, options) {
cDigits = CurrencyDigits(c);
}
// Step 21.
// Step 20.
var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol");
if (s === "currency")
lazyNumberFormatData.currencyDisplay = cd;
// Step 23.
var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
lazyNumberFormatData.minimumIntegerDigits = mnid;
// Steps 22-24.
SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0);
// Steps 25-26.
var mnfdDefault = (s === "currency") ? cDigits : 0;
var mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
lazyNumberFormatData.minimumFractionDigits = mnfd;
// Steps 28-29.
var mxfdDefault;
if (s === "currency")
mxfdDefault = std_Math_max(mnfd, cDigits);
else if (s === "percent")
mxfdDefault = std_Math_max(mnfd, 0);
else
mxfdDefault = std_Math_max(mnfd, 3);
var mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault);
lazyNumberFormatData.maximumFractionDigits = mxfd;
// Steps 31-32.
var mnsd = options.minimumSignificantDigits;
var mxsd = options.maximumSignificantDigits;
// Step 33.
if (mnsd !== undefined || mxsd !== undefined) {
mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
lazyNumberFormatData.minimumSignificantDigits = mnsd;
lazyNumberFormatData.maximumSignificantDigits = mxsd;
// Step 25.
if (lazyNumberFormatData.maximumFractionDigits === undefined) {
let mxfdDefault = s === "currency"
? cDigits
: s === "percent"
? 0
: 3;
lazyNumberFormatData.maximumFractionDigits =
std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault);
}
// Step 34.
// Steps 26.
var g = GetOption(options, "useGrouping", "boolean", undefined, true);
lazyNumberFormatData.useGrouping = g;
// Step 43.
// Steps 35-36.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
@ -2664,7 +2684,6 @@ function ToDateTimeOptions(options, required, defaults) {
return options;
}
/**
* Compares the date and time components requested by options with the available
* date and time formats in formats, and selects the best match according
@ -2985,6 +3004,234 @@ function resolveICUPattern(pattern, result) {
}
}
/********** Intl.PluralRules **********/
/**
* PluralRules internal properties.
*
* Spec: ECMAScript 402 API, PluralRules, 1.3.3.
*/
var pluralRulesInternalProperties = {
_availableLocales: null,
availableLocales: function()
{
var locales = this._availableLocales;
if (locales)
return locales;
locales = intl_PluralRules_availableLocales();
addSpecialMissingLanguageTags(locales);
return (this._availableLocales = locales);
}
};
/**
* Compute an internal properties object from |lazyPluralRulesData|.
*/
function resolvePluralRulesInternals(lazyPluralRulesData) {
assert(IsObject(lazyPluralRulesData), "lazy data not an object?");
var internalProps = std_Object_create(null);
var requestedLocales = lazyPluralRulesData.requestedLocales;
var PluralRules = pluralRulesInternalProperties;
// Step 13.
const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
lazyPluralRulesData.requestedLocales,
lazyPluralRulesData.opt,
noRelevantExtensionKeys, undefined);
// Step 14.
internalProps.locale = r.locale;
internalProps.type = lazyPluralRulesData.type;
internalProps.pluralCategories = intl_GetPluralCategories(
internalProps.locale,
internalProps.type);
internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
if ("minimumSignificantDigits" in lazyPluralRulesData) {
assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch");
internalProps.minimumSignificantDigits = lazyPluralRulesData.minimumSignificantDigits;
internalProps.maximumSignificantDigits = lazyPluralRulesData.maximumSignificantDigits;
}
return internalProps;
}
/**
* Returns an object containing the PluralRules internal properties of |obj|,
* or throws a TypeError if |obj| isn't PluralRules-initialized.
*/
function getPluralRulesInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "PluralRules", methodName);
assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals");
var internalProps = maybeInternalProperties(internals);
if (internalProps)
return internalProps;
internalProps = resolvePluralRulesInternals(internals.lazyData);
setInternalProperties(internals, internalProps);
return internalProps;
}
/**
* Initializes an object as a PluralRules.
*
* This method is complicated a moderate bit by its implementing initialization
* as a *lazy* concept. Everything that must happen now, does -- but we defer
* all the work we can until the object is actually used as a PluralRules.
* This later work occurs in |resolvePluralRulesInternals|; steps not noted
* here occur there.
*
* Spec: ECMAScript 402 API, PluralRules, 1.1.1.
*/
function InitializePluralRules(pluralRules, locales, options) {
assert(IsObject(pluralRules), "InitializePluralRules");
// Step 1.
if (isInitializedIntlObject(pluralRules))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
let internals = initializeIntlObject(pluralRules);
// Lazy PluralRules data has the following structure:
//
// {
// requestedLocales: List of locales,
// type: "cardinal" / "ordinal",
//
// opt: // opt object computer in InitializePluralRules
// {
// localeMatcher: "lookup" / "best fit",
// }
//
// minimumIntegerDigits: integer ∈ [1, 21],
// minimumFractionDigits: integer ∈ [0, 20],
// maximumFractionDigits: integer ∈ [0, 20],
//
// // optional
// minimumSignificantDigits: integer ∈ [1, 21],
// maximumSignificantDigits: integer ∈ [1, 21],
// }
//
// Note that lazy data is only installed as a final step of initialization,
// so every PluralRules lazy data object has *all* these properties, never a
// subset of them.
const lazyPluralRulesData = std_Object_create(null);
// Step 3.
let requestedLocales = CanonicalizeLocaleList(locales);
lazyPluralRulesData.requestedLocales = requestedLocales;
// Steps 4-5.
if (options === undefined)
options = {};
else
options = ToObject(options);
// Step 6.
const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
lazyPluralRulesData.type = type;
// Step 8.
let opt = new Record();
lazyPluralRulesData.opt = opt;
// Steps 9-10.
let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Step 11.
SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0);
// Step 12.
if (lazyPluralRulesData.maximumFractionDigits === undefined) {
lazyPluralRulesData.maximumFractionDigits =
std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
}
setLazyData(internals, "PluralRules", lazyPluralRulesData)
}
/**
* Returns the subset of the given locale list for which this locale list has a
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript 402 API, PluralRules, 1.3.2.
*/
function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(pluralRulesInternalProperties.availableLocales,
pluralRulesInternalProperties);
// Step 2.
let requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
/**
* Returns a String value representing the plural category matching
* the number passed as value according to the
* effective locale and the formatting options of this PluralRules.
*
* Spec: ECMAScript 402 API, PluralRules, 1.4.3.
*/
function Intl_PluralRules_select(value) {
// Step 1.
let pluralRules = this;
// Step 2.
let internals = getPluralRulesInternals(pluralRules, "select");
// Steps 3-4.
let n = ToNumber(value);
// Step 5.
return intl_SelectPluralRule(pluralRules, n);
}
/**
* Returns the resolved options for a PluralRules object.
*
* Spec: ECMAScript 402 API, PluralRules, 1.4.4.
*/
function Intl_PluralRules_resolvedOptions() {
var internals = getPluralRulesInternals(this, "resolvedOptions");
var result = {
locale: internals.locale,
type: internals.type,
pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0),
minimumIntegerDigits: internals.minimumIntegerDigits,
minimumFractionDigits: internals.minimumFractionDigits,
maximumFractionDigits: internals.maximumFractionDigits,
};
var optionalProperties = [
"minimumSignificantDigits",
"maximumSignificantDigits"
];
for (var i = 0; i < optionalProperties.length; i++) {
var p = optionalProperties[i];
if (callFunction(std_Object_hasOwnProperty, internals, p))
_DefineDataProperty(result, p, internals[p]);
}
return result;
}
function Intl_getCanonicalLocales(locales) {
let codes = CanonicalizeLocaleList(locales);
let result = [];

View File

@ -0,0 +1,13 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
/* 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/. */
// objectEmulatingUndefined is only available when running tests in the shell,
// not the browser
if (typeof objectEmulatingUndefined === "function") {
let nf = new Intl.NumberFormat('en-US', objectEmulatingUndefined());
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

View File

@ -0,0 +1,21 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
/* 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/. */
// Tests the format function with a diverse set of locales and options.
var pr;
pr = new Intl.PluralRules("en-us");
assertEq(pr.resolvedOptions().locale, "en-US");
assertEq(pr.resolvedOptions().type, "cardinal");
assertEq(pr.resolvedOptions().pluralCategories.length, 2);
pr = new Intl.PluralRules("de", {type: 'cardinal'});
assertEq(pr.resolvedOptions().pluralCategories.length, 2);
pr = new Intl.PluralRules("de", {type: 'ordinal'});
assertEq(pr.resolvedOptions().pluralCategories.length, 1);
reportCompare(0, 0, 'ok');

View File

@ -0,0 +1,60 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
/* 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/. */
// Tests the format function with a diverse set of locales and options.
var pr;
pr = new Intl.PluralRules("en-us");
assertEq(pr.select(0), "other");
assertEq(pr.select(0.5), "other");
assertEq(pr.select(1.2), "other");
assertEq(pr.select(1.5), "other");
assertEq(pr.select(1.7), "other");
assertEq(pr.select(-1), "one");
assertEq(pr.select(1), "one");
assertEq(pr.select("1"), "one");
assertEq(pr.select(123456789.123456789), "other");
pr = new Intl.PluralRules("de", {type: "cardinal"});
assertEq(pr.select(0), "other");
assertEq(pr.select(0.5), "other");
assertEq(pr.select(1.2), "other");
assertEq(pr.select(1.5), "other");
assertEq(pr.select(1.7), "other");
assertEq(pr.select(-1), "one");
pr = new Intl.PluralRules("de", {type: "ordinal"});
assertEq(pr.select(0), "other");
assertEq(pr.select(0.5), "other");
assertEq(pr.select(1.2), "other");
assertEq(pr.select(1.5), "other");
assertEq(pr.select(1.7), "other");
assertEq(pr.select(-1), "other");
pr = new Intl.PluralRules("pl", {type: "cardinal"});
assertEq(pr.select(0), "many");
assertEq(pr.select(0.5), "other");
assertEq(pr.select(1), "one");
pr = new Intl.PluralRules("pl", {type: "cardinal", maximumFractionDigits: 0});
assertEq(pr.select(1.1), "one");
pr = new Intl.PluralRules("pl", {type: "cardinal", maximumFractionDigits: 1});
assertEq(pr.select(1.1), "other");
var weirdCases = [
NaN,
Infinity,
"word",
[0,2],
{},
];
for (let c of weirdCases) {
assertEq(pr.select(c), "other");
};
reportCompare(0, 0, 'ok');

View File

View File

@ -0,0 +1,373 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl")||xulRuntime.shell)
// -- test in browser only that ICU has locale data for all Mozilla languages
/* 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/. */
// This array contains the locales that ICU supports in
// number formatting whose languages Mozilla localizes Firefox into.
// Current as of ICU 50.1.2 and Firefox March 2013.
var locales = [
"af",
"af-NA",
"af-ZA",
"ar",
"ar-001",
"ar-AE",
"ar-BH",
"ar-DJ",
"ar-DZ",
"ar-EG",
"ar-EH",
"ar-ER",
"ar-IL",
"ar-IQ",
"ar-JO",
"ar-KM",
"ar-KW",
"ar-LB",
"ar-LY",
"ar-MA",
"ar-MR",
"ar-OM",
"ar-PS",
"ar-QA",
"ar-SA",
"ar-SD",
"ar-SO",
"ar-SY",
"ar-TD",
"ar-TN",
"ar-YE",
"as",
"as-IN",
"be",
"be-BY",
"bg",
"bg-BG",
"bn",
"bn-BD",
"bn-IN",
"br",
"br-FR",
"bs",
"bs-Cyrl",
"bs-Cyrl-BA",
"bs-Latn",
"bs-Latn-BA",
"ca",
"ca-AD",
"ca-ES",
"cs",
"cs-CZ",
"cy",
"cy-GB",
"da",
"da-DK",
"de",
"de-AT",
"de-BE",
"de-CH",
"de-DE",
"de-LI",
"de-LU",
"el",
"el-CY",
"el-GR",
"en",
"en-150",
"en-AG",
"en-AS",
"en-AU",
"en-BB",
"en-BE",
"en-BM",
"en-BS",
"en-BW",
"en-BZ",
"en-CA",
"en-CM",
"en-DM",
"en-FJ",
"en-FM",
"en-GB",
"en-GD",
"en-GG",
"en-GH",
"en-GI",
"en-GM",
"en-GU",
"en-GY",
"en-HK",
"en-IE",
"en-IM",
"en-IN",
"en-JE",
"en-JM",
"en-KE",
"en-KI",
"en-KN",
"en-KY",
"en-LC",
"en-LR",
"en-LS",
"en-MG",
"en-MH",
"en-MP",
"en-MT",
"en-MU",
"en-MW",
"en-NA",
"en-NG",
"en-NZ",
"en-PG",
"en-PH",
"en-PK",
"en-PR",
"en-PW",
"en-SB",
"en-SC",
"en-SG",
"en-SL",
"en-SS",
"en-SZ",
"en-TC",
"en-TO",
"en-TT",
"en-TZ",
"en-UG",
"en-UM",
"en-US",
"en-US-posix",
"en-VC",
"en-VG",
"en-VI",
"en-VU",
"en-WS",
"en-ZA",
"en-ZM",
"en-ZW",
"eo",
"es",
"es-419",
"es-AR",
"es-BO",
"es-CL",
"es-CO",
"es-CR",
"es-CU",
"es-DO",
"es-EA",
"es-EC",
"es-ES",
"es-GQ",
"es-GT",
"es-HN",
"es-IC",
"es-MX",
"es-NI",
"es-PA",
"es-PE",
"es-PH",
"es-PR",
"es-PY",
"es-SV",
"es-US",
"es-UY",
"es-VE",
"et",
"et-EE",
"eu",
"eu-ES",
"fa",
"fa-AF",
"fa-IR",
"ff",
"ff-SN",
"fi",
"fi-FI",
"fr",
"fr-BE",
"fr-BF",
"fr-BI",
"fr-BJ",
"fr-BL",
"fr-CA",
"fr-CD",
"fr-CF",
"fr-CG",
"fr-CH",
"fr-CI",
"fr-CM",
"fr-DJ",
"fr-DZ",
"fr-FR",
"fr-GA",
"fr-GF",
"fr-GN",
"fr-GP",
"fr-GQ",
"fr-HT",
"fr-KM",
"fr-LU",
"fr-MA",
"fr-MC",
"fr-MF",
"fr-MG",
"fr-ML",
"fr-MQ",
"fr-MR",
"fr-MU",
"fr-NC",
"fr-NE",
"fr-PF",
"fr-RE",
"fr-RW",
"fr-SC",
"fr-SN",
"fr-SY",
"fr-TD",
"fr-TG",
"fr-TN",
"fr-VU",
"fr-YT",
"ga",
"ga-IE",
"gl",
"gl-ES",
"gu",
"gu-IN",
"he",
"he-IL",
"hi",
"hi-IN",
"hr",
"hr-BA",
"hr-HR",
"hu",
"hu-HU",
"hy",
"hy-AM",
"id",
"id-ID",
"is",
"is-IS",
"it",
"it-CH",
"it-IT",
"it-SM",
"ja",
"ja-JP",
"kk",
"kk-Cyrl",
"kk-Cyrl-KZ",
"km",
"km-KH",
"kn",
"kn-IN",
"ko",
"ko-KP",
"ko-KR",
"lt",
"lt-LT",
"lv",
"lv-LV",
"mk",
"mk-MK",
"ml",
"ml-IN",
"mr",
"mr-IN",
"nb",
"nb-NO",
"nl",
"nl-AW",
"nl-BE",
"nl-CW",
"nl-NL",
"nl-SR",
"nl-SX",
"nn",
"nn-NO",
"or",
"or-IN",
"pa",
"pa-Arab",
"pa-Arab-PK",
"pa-Guru",
"pa-Guru-IN",
"pl",
"pl-PL",
"pt",
"pt-AO",
"pt-BR",
"pt-CV",
"pt-GW",
"pt-MO",
"pt-MZ",
"pt-PT",
"pt-ST",
"pt-TL",
"rm",
"rm-CH",
"ro",
"ro-MD",
"ro-RO",
"ru",
"ru-BY",
"ru-KG",
"ru-KZ",
"ru-MD",
"ru-RU",
"ru-UA",
"si",
"si-LK",
"sk",
"sk-SK",
"sl",
"sl-SI",
"sq",
"sq-AL",
"sq-MK",
"sr",
"sr-Cyrl",
"sr-Cyrl-BA",
"sr-Cyrl-ME",
"sr-Cyrl-RS",
"sr-Latn",
"sr-Latn-BA",
"sr-Latn-ME",
"sr-Latn-RS",
"sv",
"sv-AX",
"sv-FI",
"sv-SE",
"te",
"te-IN",
"th",
"th-TH",
"tr",
"tr-CY",
"tr-TR",
"uk",
"uk-UA",
"vi",
"vi-VN",
"zh",
"zh-Hans",
"zh-Hans-CN",
"zh-Hans-HK",
"zh-Hans-MO",
"zh-Hans-SG",
"zh-Hant",
"zh-Hant-HK",
"zh-Hant-MO",
"zh-Hant-TW",
];
const result = Intl.PluralRules.supportedLocalesOf(locales);
assertEqArray(locales, result);
reportCompare(0, 0, 'ok');

View File

@ -157,6 +157,7 @@
macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \
macro(InitializePluralRules, InitializePluralRules, "InitializePluralRules") \
macro(innermost, innermost, "innermost") \
macro(inNursery, inNursery, "inNursery") \
macro(input, input, "input") \
@ -248,6 +249,8 @@
macro(parseInt, parseInt, "parseInt") \
macro(pattern, pattern, "pattern") \
macro(pending, pending, "pending") \
macro(PluralRules, PluralRules, "PluralRules") \
macro(PluralRulesSelect, PluralRulesSelect, "Intl_PluralRules_Select") \
macro(percentSign, percentSign, "percentSign") \
macro(plusSign, plusSign, "plusSign") \
macro(preventExtensions, preventExtensions, "preventExtensions") \

View File

@ -104,6 +104,7 @@ class GlobalObject : public NativeObject
COLLATOR_PROTO,
NUMBER_FORMAT_PROTO,
DATE_TIME_FORMAT_PROTO,
PLURAL_RULES_PROTO,
MODULE_PROTO,
IMPORT_ENTRY_PROTO,
EXPORT_ENTRY_PROTO,
@ -486,6 +487,10 @@ class GlobalObject : public NativeObject
return getOrCreateObject(cx, DATE_TIME_FORMAT_PROTO, initIntlObject);
}
JSObject* getOrCreatePluralRulesPrototype(JSContext* cx) {
return getOrCreateObject(cx, PLURAL_RULES_PROTO, initIntlObject);
}
static bool ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global);
JSObject* maybeGetModulePrototype() {

View File

@ -2523,6 +2523,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
JS_INLINABLE_FN("IsRegExpObject",
intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,

View File

@ -13,7 +13,6 @@
#include "nsCSSPseudoElements.h"
#include "nsGenericHTMLElement.h"
#include "nsIEditor.h"
#include "nsIEditorIMESupport.h"
#include "nsIPhonetic.h"
#include "nsTextFragment.h"
#include "nsIDOMHTMLTextAreaElement.h"
@ -1207,11 +1206,9 @@ nsTextControlFrame::GetPhonetic(nsAString& aPhonetic)
nsresult rv = GetEditor(getter_AddRefs(editor));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIEditorIMESupport> imeSupport = do_QueryInterface(editor);
if (imeSupport) {
nsCOMPtr<nsIPhonetic> phonetic = do_QueryInterface(imeSupport);
if (phonetic)
phonetic->GetPhonetic(aPhonetic);
nsCOMPtr<nsIPhonetic> phonetic = do_QueryInterface(editor);
if (phonetic) {
phonetic->GetPhonetic(aPhonetic);
}
return NS_OK;
}

View File

@ -17,7 +17,7 @@ body { padding: 50px }
height: 200px; width: 200px;
backface-visibility: hidden;
/* use a -99.9s delay to start at 99.9% and then move to 0% */
animation: flip 100s -99.9s linear 2;
animation: flip 100s -99.9s linear 2 paused;
}
</style>
@ -27,7 +27,13 @@ body { padding: 50px }
<script>
document.getElementById("test").addEventListener("animationiteration", IterationListener, false);
document.getElementById("test").addEventListener("animationstart", StartListener, false);
function StartListener(event) {
var test = document.getElementById("test");
test.style.animationPlayState = 'running';
test.addEventListener("animationiteration", IterationListener, false);
}
function IterationListener(event) {
setTimeout(RemoveReftestWait, 0);

View File

@ -18,7 +18,7 @@ body { padding: 50px }
border: 1px solid black;
transform-style: preserve-3d;
/* use a -99.9s delay to start at 99.9% and then move to 0% */
animation: spin 100s -99.9s linear 2;
animation: spin 100s -99.9s linear 2 paused;
}
#child {
@ -39,7 +39,13 @@ body { padding: 50px }
<script>
document.getElementById("parent").addEventListener("animationiteration", IterationListener, false);
document.getElementById("parent").addEventListener("animationstart", StartListener, false);
function StartListener(event) {
var test = document.getElementById("parent");
test.style.animationPlayState = 'running';
test.addEventListener("animationiteration", IterationListener, false);
}
function IterationListener(event) {
setTimeout(RemoveReftestWait, 0);

View File

@ -129,6 +129,22 @@ ToPrimitive(nsCSSKeyword aKeyword)
}
}
static bool
HasAccumulateMatrix(const nsCSSValueList* aList)
{
const nsCSSValueList *item = aList;
do {
nsCSSKeyword func =
nsStyleTransformMatrix::TransformFunctionOf(item->mValue.GetArrayValue());
if (func == eCSSKeyword_accumulatematrix) {
return true;
}
item = item->mNext;
} while (item);
return false;
}
static bool
TransformFunctionsMatch(nsCSSKeyword func1, nsCSSKeyword func2)
{
@ -3139,12 +3155,21 @@ StyleAnimationValue::AddWeighted(nsCSSPropertyID aProperty,
if (result) {
result->mValue.SetNoneValue();
}
} else if (HasAccumulateMatrix(list2)) {
result = AddDifferentTransformLists(0, list2, aCoeff2, list2,
eCSSKeyword_interpolatematrix);
} else {
result = AddTransformLists(0, list2, aCoeff2, list2);
}
} else {
if (list2->mValue.GetUnit() == eCSSUnit_None) {
result = AddTransformLists(0, list1, aCoeff1, list1);
if (HasAccumulateMatrix(list1)) {
result = AddDifferentTransformLists(0, list1,
aCoeff1, list1,
eCSSKeyword_interpolatematrix);
} else {
result = AddTransformLists(0, list1, aCoeff1, list1);
}
} else if (TransformFunctionListsMatch(list1, list2)) {
result = AddTransformLists(aCoeff1, list1, aCoeff2, list2,
eCSSKeyword_interpolatematrix);

View File

@ -33,11 +33,15 @@ using mozilla::dom::AnimationPlayState;
using mozilla::dom::KeyframeEffectReadOnly;
using mozilla::dom::CSSAnimation;
typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
namespace {
// Pair of an event message and elapsed time used when determining the set of
// events to queue.
typedef Pair<EventMessage, StickyTimeDuration> EventPair;
struct AnimationEventParams {
EventMessage mMessage;
StickyTimeDuration mElapsedTime;
TimeStamp mTimeStamp;
};
} // anonymous namespace
@ -193,78 +197,94 @@ CSSAnimation::QueueEvents()
return;
}
nsAnimationManager* manager = presContext->AnimationManager();
ComputedTiming computedTiming = mEffect->GetComputedTiming();
if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Null) {
return; // do nothing
ComputedTiming::AnimationPhase currentPhase = computedTiming.mPhase;
uint64_t currentIteration = computedTiming.mCurrentIteration;
if (currentPhase == mPreviousPhase &&
currentIteration == mPreviousIteration) {
return;
}
// Note that script can change the start time, so we have to handle moving
// backwards through the animation as well as forwards. An 'animationstart'
// is dispatched if we enter the active phase (regardless if that is from
// before or after the animation's active phase). An 'animationend' is
// dispatched if we leave the active phase (regardless if that is to before
// or after the animation's active phase).
const StickyTimeDuration zeroDuration;
StickyTimeDuration intervalStartTime =
std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
computedTiming.mActiveDuration),
zeroDuration);
StickyTimeDuration intervalEndTime =
std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
computedTiming.mActiveDuration),
zeroDuration);
bool wasActive = mPreviousPhaseOrIteration != PREVIOUS_PHASE_BEFORE &&
mPreviousPhaseOrIteration != PREVIOUS_PHASE_AFTER;
bool isActive =
computedTiming.mPhase == ComputedTiming::AnimationPhase::Active;
bool isSameIteration =
computedTiming.mCurrentIteration == mPreviousPhaseOrIteration;
bool skippedActivePhase =
(mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE &&
computedTiming.mPhase == ComputedTiming::AnimationPhase::After) ||
(mPreviousPhaseOrIteration == PREVIOUS_PHASE_AFTER &&
computedTiming.mPhase == ComputedTiming::AnimationPhase::Before);
bool skippedFirstIteration =
isActive &&
mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE &&
computedTiming.mCurrentIteration > 0;
uint64_t iterationBoundary = mPreviousIteration > currentIteration
? currentIteration + 1
: currentIteration;
StickyTimeDuration iterationStartTime =
computedTiming.mDuration.MultDouble(
(iterationBoundary - computedTiming.mIterationStart));
MOZ_ASSERT(!skippedActivePhase || (!isActive && !wasActive),
"skippedActivePhase only makes sense if we were & are inactive");
TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Before) {
mPreviousPhaseOrIteration = PREVIOUS_PHASE_BEFORE;
} else if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Active) {
mPreviousPhaseOrIteration = computedTiming.mCurrentIteration;
} else if (computedTiming.mPhase == ComputedTiming::AnimationPhase::After) {
mPreviousPhaseOrIteration = PREVIOUS_PHASE_AFTER;
AutoTArray<AnimationEventParams, 2> events;
switch (mPreviousPhase) {
case AnimationPhase::Null:
case AnimationPhase::Before:
if (currentPhase == AnimationPhase::Active) {
events.AppendElement(AnimationEventParams{ eAnimationStart,
intervalStartTime,
startTimeStamp });
} else if (currentPhase == AnimationPhase::After) {
events.AppendElement(AnimationEventParams{ eAnimationStart,
intervalStartTime,
startTimeStamp });
events.AppendElement(AnimationEventParams{ eAnimationEnd,
intervalEndTime,
endTimeStamp });
}
break;
case AnimationPhase::Active:
if (currentPhase == AnimationPhase::Before) {
events.AppendElement(AnimationEventParams{ eAnimationEnd,
intervalStartTime,
startTimeStamp });
} else if (currentPhase == AnimationPhase::Active) {
// The currentIteration must have changed or element we would have
// returned early above.
MOZ_ASSERT(currentIteration != mPreviousIteration);
events.AppendElement(AnimationEventParams{ eAnimationIteration,
iterationStartTime,
iterationTimeStamp });
} else if (currentPhase == AnimationPhase::After) {
events.AppendElement(AnimationEventParams{ eAnimationEnd,
intervalEndTime,
endTimeStamp });
}
break;
case AnimationPhase::After:
if (currentPhase == AnimationPhase::Before) {
events.AppendElement(AnimationEventParams{ eAnimationStart,
intervalEndTime,
startTimeStamp});
events.AppendElement(AnimationEventParams{ eAnimationEnd,
intervalStartTime,
endTimeStamp });
} else if (currentPhase == AnimationPhase::Active) {
events.AppendElement(AnimationEventParams{ eAnimationStart,
intervalEndTime,
endTimeStamp });
}
break;
}
mPreviousPhase = currentPhase;
mPreviousIteration = currentIteration;
AutoTArray<EventPair, 2> events;
StickyTimeDuration initialAdvance = StickyTimeDuration(InitialAdvance());
StickyTimeDuration iterationStart = computedTiming.mDuration *
computedTiming.mCurrentIteration;
const StickyTimeDuration& activeDuration = computedTiming.mActiveDuration;
if (skippedFirstIteration) {
// Notify animationstart and animationiteration in same tick.
events.AppendElement(EventPair(eAnimationStart, initialAdvance));
events.AppendElement(EventPair(eAnimationIteration,
std::max(iterationStart, initialAdvance)));
} else if (!wasActive && isActive) {
events.AppendElement(EventPair(eAnimationStart, initialAdvance));
} else if (wasActive && !isActive) {
events.AppendElement(EventPair(eAnimationEnd, activeDuration));
} else if (wasActive && isActive && !isSameIteration) {
events.AppendElement(EventPair(eAnimationIteration, iterationStart));
} else if (skippedActivePhase) {
events.AppendElement(EventPair(eAnimationStart,
std::min(initialAdvance, activeDuration)));
events.AppendElement(EventPair(eAnimationEnd, activeDuration));
} else {
return; // No events need to be sent
}
for (const EventPair& pair : events){
for (const AnimationEventParams& event : events){
manager->QueueEvent(
AnimationEventInfo(owningElement, owningPseudoType,
pair.first(), mAnimationName,
pair.second(),
ElapsedTimeToTimeStamp(pair.second()),
event.mMessage, mAnimationName,
event.mElapsedTime, event.mTimeStamp,
this));
}
}

View File

@ -76,7 +76,8 @@ public:
, mIsStylePaused(false)
, mPauseShouldStick(false)
, mNeedsNewAnimationIndexWhenRun(false)
, mPreviousPhaseOrIteration(PREVIOUS_PHASE_BEFORE)
, mPreviousPhase(ComputedTiming::AnimationPhase::Null)
, mPreviousIteration(0)
{
// We might need to drop this assertion once we add a script-accessible
// constructor but for animations generated from CSS markup the
@ -257,13 +258,10 @@ protected:
// its animation index should be updated.
bool mNeedsNewAnimationIndexWhenRun;
enum {
PREVIOUS_PHASE_BEFORE = uint64_t(-1),
PREVIOUS_PHASE_AFTER = uint64_t(-2)
};
// One of the PREVIOUS_PHASE_* constants, or an integer for the iteration
// whose start we last notified on.
uint64_t mPreviousPhaseOrIteration;
// Phase and current iteration from the previous time we queued events.
// This is used to determine what new events to dispatch.
ComputedTiming::AnimationPhase mPreviousPhase;
uint64_t mPreviousIteration;
};
} /* namespace dom */

View File

@ -1226,9 +1226,6 @@ advance_clock(0); // complete pending animation start
is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01,
"large negative delay test at 0ms");
check_events([{ type: 'animationstart', target: div,
animationName: 'anim2', elapsedTime: 3.6,
pseudoElement: "" },
{ type: 'animationiteration', target: div,
animationName: 'anim2', elapsedTime: 3.6,
pseudoElement: "" }],
"right after start in large negative delay test");

View File

@ -1407,9 +1407,6 @@ addAsyncAnimTest(function *() {
omta_is_approx("opacity", gTF.ease_in(0.4), 0.01, RunningOn.Compositor,
"large negative delay test at 0ms");
check_events([{ type: 'animationstart', target: gDiv,
animationName: 'anim2', elapsedTime: 3.6,
pseudoElement: "" },
{ type: 'animationiteration', target: gDiv,
animationName: 'anim2', elapsedTime: 3.6,
pseudoElement: "" }],
"right after start in large negative delay test");

View File

@ -801,7 +801,6 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'widget/TabThumbnailWrapper.java',
'widget/ThumbnailView.java',
'widget/TouchDelegateWithReset.java',
'widget/TwoWayView.java',
'ZoomedView.java',
]]
# The following sources are checked in to version control but

View File

@ -3,11 +3,7 @@
- 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/. -->
<!-- TwoWayView doesn't let us set the margin around items (except as
gecko:itemMargin, but that doesn't increase the hit area) so we
have to surround the main View by a ViewGroup to create a pressable margin.
Note: the layout_height values are shared with the parent
<!-- Note: the layout_height values are shared with the parent
View (browser_search at the time of this writing).
The actual width of the FrameLayout is calculated at runtime by the

View File

@ -3,9 +3,6 @@
- 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/. -->
<!-- TwoWayView doesn't let us set the margin around items (except as
gecko:itemMargin, but that doesn't increase the hit area) so we
have to surround the main View by a ViewGroup to create a pressable margin. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="48dp"

View File

@ -100,13 +100,6 @@
<attr name="autoUpdateTheme" format="boolean"/>
</declare-styleable>
<declare-styleable name="TwoWayView">
<attr name="android:orientation"/>
<attr name="android:choiceMode"/>
<attr name="android:listSelector"/>
<attr name="android:drawSelectorOnTop"/>
</declare-styleable>
<declare-styleable name="HomeListView">
<!-- Draws a divider on top of the list, if true. Defaults to false. -->
<attr name="topDivider" format="boolean"/>

View File

@ -233,9 +233,9 @@ var ActionBarHandler = {
_clearSelection: function(element = this._targetElement, win = this._contentWindow) {
// Commit edit compositions, and clear focus from editables.
if (element) {
let imeSupport = this._getEditor(element, win).QueryInterface(Ci.nsIEditorIMESupport);
if (imeSupport.composing) {
imeSupport.forceCompositionEnd();
let editor = this._getEditor(element, win);
if (editor.composing) {
editor.forceCompositionEnd();
}
element.blur();
}
@ -346,9 +346,8 @@ var ActionBarHandler = {
if (element) {
// If we have an active composition string, commit it, and
// ensure proper element focus.
let imeSupport = ActionBarHandler._getEditor(element, win).
QueryInterface(Ci.nsIEditorIMESupport);
if (imeSupport.composing) {
let editor = ActionBarHandler._getEditor(element, win)
if (editor.composing) {
element.blur();
element.focus();
}

View File

@ -4851,9 +4851,8 @@ var FormAssistant = {
// If we have an active composition string, commit it before sending
// the autocomplete event with the text that will replace it.
try {
let imeEditor = editableElement.editor.QueryInterface(Ci.nsIEditorIMESupport);
if (imeEditor.composing)
imeEditor.forceCompositionEnd();
if (editableElement.editor.composing)
editableElement.editor.forceCompositionEnd();
} catch (e) {}
editableElement.setUserInput(aData);

View File

@ -109,21 +109,19 @@
},
test_reflush_changes: function() {
let inputIme = getEditor().QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport);
do_check_true(inputIme.composing);
do_check_true(getEditor().composing);
// Ending the composition then setting the input value triggers the bug.
inputIme.forceCompositionEnd();
getEditor().forceCompositionEnd();
setValue("good"); // Value that testInputConnection.java expects.
setSelection(4);
},
test_set_selection: function() {
let inputIme = getEditor().QueryInterface(SpecialPowers.Ci.nsIEditorIMESupport);
do_check_true(inputIme.composing);
do_check_true(getEditor().composing);
// Ending the composition then setting the selection triggers the bug.
inputIme.forceCompositionEnd();
getEditor().forceCompositionEnd();
setSelection(3); // Offsets that testInputConnection.java expects.
},

View File

@ -1157,6 +1157,7 @@ pref("dom.disable_open_click_delay", 1000);
pref("dom.storage.enabled", true);
pref("dom.storage.default_quota", 5120);
pref("dom.storage.testing", false);
pref("dom.send_after_paint_to_content", false);
@ -2210,6 +2211,10 @@ pref("services.blocklist.addons.collection", "addons");
pref("services.blocklist.addons.checked", 0);
pref("services.blocklist.plugins.collection", "plugins");
pref("services.blocklist.plugins.checked", 0);
pref("services.blocklist.pinning.enabled", true);
pref("services.blocklist.pinning.bucket", "pinning");
pref("services.blocklist.pinning.collection", "pins");
pref("services.blocklist.pinning.checked", 0);
pref("services.blocklist.gfx.collection", "gfx");
pref("services.blocklist.gfx.checked", 0);

View File

@ -54,6 +54,33 @@ Please commit or stash these changes before vendoring, or re-run with `--ignore-
'''.format(files='\n'.join(sorted(modified))))
sys.exit(1)
def check_openssl(self):
'''
Set environment flags for building with openssl.
MacOS doesn't include openssl, but the openssl-sys crate used by
mach-vendor expects one of the system. It's common to have one
installed in /usr/local/opt/openssl by homebrew, but custom link
flags are necessary to build against it.
'''
test_paths = ['/usr/include', '/usr/local/include']
if any([os.path.exists(os.path.join(path, 'openssl/ssl.h')) for path in test_paths]):
# Assume we can use one of these system headers.
return None
if os.path.exists('/usr/local/opt/openssl/include/openssl/ssl.h'):
# Found a likely homebrew install.
self.log(logging.INFO, 'openssl', {},
'Using OpenSSL in /usr/local/opt/openssl')
return {
'OPENSSL_INCLUDE_DIR': '/usr/local/opt/openssl/include',
'DEP_OPENSSL_INCLUDE': '/usr/local/opt/openssl/include',
}
self.log(logging.ERROR, 'openssl', {}, "OpenSSL not found!")
return None
def vendor(self, ignore_modified=False):
self.populate_logger()
self.log_manager.enable_unstructured()
@ -68,7 +95,9 @@ Please commit or stash these changes before vendoring, or re-run with `--ignore-
have_vendor = any(l.strip() == 'vendor' for l in subprocess.check_output([cargo, '--list']).splitlines())
if not have_vendor:
self.log(logging.INFO, 'installing', {}, 'Installing cargo-vendor')
self.run_process(args=[cargo, 'install', 'cargo-vendor'])
env = self.check_openssl()
self.run_process(args=[cargo, 'install', 'cargo-vendor'],
append_env=env)
else:
self.log(logging.DEBUG, 'cargo_vendor', {}, 'cargo-vendor already intalled')
vendor_dir = mozpath.join(self.topsrcdir, 'third_party/rust')

View File

@ -8,6 +8,7 @@ this.EXPORTED_SYMBOLS = ["AddonBlocklistClient",
"GfxBlocklistClient",
"OneCRLBlocklistClient",
"PluginBlocklistClient",
"PinningBlocklistClient",
"FILENAME_ADDONS_JSON",
"FILENAME_GFX_JSON",
"FILENAME_PLUGINS_JSON"];
@ -32,6 +33,10 @@ const PREF_BLOCKLIST_ADDONS_COLLECTION = "services.blocklist.addons.collec
const PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS = "services.blocklist.addons.checked";
const PREF_BLOCKLIST_PLUGINS_COLLECTION = "services.blocklist.plugins.collection";
const PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS = "services.blocklist.plugins.checked";
const PREF_BLOCKLIST_PINNING_ENABLED = "services.blocklist.pinning.enabled";
const PREF_BLOCKLIST_PINNING_BUCKET = "services.blocklist.pinning.bucket";
const PREF_BLOCKLIST_PINNING_COLLECTION = "services.blocklist.pinning.collection";
const PREF_BLOCKLIST_PINNING_CHECKED_SECONDS = "services.blocklist.pinning.checked";
const PREF_BLOCKLIST_GFX_COLLECTION = "services.blocklist.gfx.collection";
const PREF_BLOCKLIST_GFX_CHECKED_SECONDS = "services.blocklist.gfx.checked";
const PREF_BLOCKLIST_ENFORCE_SIGNING = "services.blocklist.signing.enforced";
@ -82,9 +87,8 @@ function fetchRemoteCollection(collection) {
* URL and bucket name. It uses the `FirefoxAdapter` which relies on SQLite to
* persist the local DB.
*/
function kintoClient(connection) {
function kintoClient(connection, bucket) {
let base = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
let bucket = Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET);
let config = {
remote: base,
@ -99,10 +103,11 @@ function kintoClient(connection) {
class BlocklistClient {
constructor(collectionName, lastCheckTimePref, processCallback, signerName) {
constructor(collectionName, lastCheckTimePref, processCallback, bucketName, signerName) {
this.collectionName = collectionName;
this.lastCheckTimePref = lastCheckTimePref;
this.processCallback = processCallback;
this.bucketName = bucketName;
this.signerName = signerName;
}
@ -168,7 +173,7 @@ class BlocklistClient {
let connection;
try {
connection = yield FirefoxAdapter.openConnection({path: KINTO_STORAGE_PATH});
let db = kintoClient(connection);
let db = kintoClient(connection, this.bucketName);
let collection = db.collection(this.collectionName, opts);
let collectionLastModified = yield collection.db.getLastModified();
@ -247,14 +252,54 @@ function* updateCertBlocklist(records) {
}
} catch (e) {
// prevent errors relating to individual blocklist entries from
// causing sync to fail. At some point in the future, we may want to
// accumulate telemetry on these failures.
// causing sync to fail. We will accumulate telemetry on these failures in
// bug 1254099.
Cu.reportError(e);
}
}
certList.saveEntries();
}
/**
* Modify the appropriate security pins based on records from the remote
* collection.
*
* @param {Object} records current records in the local db.
*/
function* updatePinningList(records) {
if (Services.prefs.getBoolPref(PREF_BLOCKLIST_PINNING_ENABLED)) {
const appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
const siteSecurityService = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
// clear the current preload list
siteSecurityService.clearPreloads();
// write each KeyPin entry to the preload list
for (let item of records) {
try {
const {pinType, pins=[], versions} = item;
if (pinType == "KeyPin" && pins.length &&
versions.indexOf(appInfo.version) != -1) {
siteSecurityService.setKeyPins(item.hostName,
item.includeSubdomains,
item.expires,
pins.length,
pins, true);
}
} catch (e) {
// prevent errors relating to individual preload entries from causing
// sync to fail. We will accumulate telemetry for such failures in bug
// 1254099.
}
}
} else {
return;
}
}
/**
* Write list of records into JSON file, and notify nsBlocklistService.
*
@ -276,28 +321,39 @@ function* updateJSONBlocklist(filename, records) {
}
}
this.OneCRLBlocklistClient = new BlocklistClient(
Services.prefs.getCharPref(PREF_BLOCKLIST_ONECRL_COLLECTION),
PREF_BLOCKLIST_ONECRL_CHECKED_SECONDS,
updateCertBlocklist,
Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET),
"onecrl.content-signature.mozilla.org"
);
this.AddonBlocklistClient = new BlocklistClient(
Services.prefs.getCharPref(PREF_BLOCKLIST_ADDONS_COLLECTION),
PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS,
updateJSONBlocklist.bind(undefined, FILENAME_ADDONS_JSON)
updateJSONBlocklist.bind(undefined, FILENAME_ADDONS_JSON),
Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET)
);
this.GfxBlocklistClient = new BlocklistClient(
Services.prefs.getCharPref(PREF_BLOCKLIST_GFX_COLLECTION),
PREF_BLOCKLIST_GFX_CHECKED_SECONDS,
updateJSONBlocklist.bind(undefined, FILENAME_GFX_JSON)
updateJSONBlocklist.bind(undefined, FILENAME_GFX_JSON),
Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET)
);
this.PluginBlocklistClient = new BlocklistClient(
Services.prefs.getCharPref(PREF_BLOCKLIST_PLUGINS_COLLECTION),
PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS,
updateJSONBlocklist.bind(undefined, FILENAME_PLUGINS_JSON)
updateJSONBlocklist.bind(undefined, FILENAME_PLUGINS_JSON),
Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET)
);
this.PinningPreloadClient = new BlocklistClient(
Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_COLLECTION),
PREF_BLOCKLIST_PINNING_CHECKED_SECONDS,
updatePinningList,
Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_BUCKET),
"pinning-preload.content-signature.mozilla.org"
);

View File

@ -13,7 +13,6 @@ const BlocklistClients = Cu.import("resource://services-common/blocklist-clients
const PREF_SETTINGS_SERVER = "services.settings.server";
const PREF_BLOCKLIST_CHANGES_PATH = "services.blocklist.changes.path";
const PREF_BLOCKLIST_BUCKET = "services.blocklist.bucket";
const PREF_BLOCKLIST_LAST_UPDATE = "services.blocklist.last_update_seconds";
const PREF_BLOCKLIST_LAST_ETAG = "services.blocklist.last_etag";
const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
@ -23,7 +22,8 @@ const gBlocklistClients = {
[BlocklistClients.OneCRLBlocklistClient.collectionName]: BlocklistClients.OneCRLBlocklistClient,
[BlocklistClients.AddonBlocklistClient.collectionName]: BlocklistClients.AddonBlocklistClient,
[BlocklistClients.GfxBlocklistClient.collectionName]: BlocklistClients.GfxBlocklistClient,
[BlocklistClients.PluginBlocklistClient.collectionName]: BlocklistClients.PluginBlocklistClient
[BlocklistClients.PluginBlocklistClient.collectionName]: BlocklistClients.PluginBlocklistClient,
[BlocklistClients.PinningPreloadClient.collectionName]: BlocklistClients.PinningPreloadClient
};
// Add a blocklist client for testing purposes. Do not use for any other purpose
@ -44,7 +44,6 @@ this.checkVersions = function() {
// Right now, we only use the collection name and the last modified info
let kintoBase = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
let changesEndpoint = kintoBase + Services.prefs.getCharPref(PREF_BLOCKLIST_CHANGES_PATH);
let blocklistsBucket = Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET);
// Use ETag to obtain a `304 Not modified` when no change occurred.
const headers = {};
@ -82,14 +81,11 @@ this.checkVersions = function() {
let firstError;
for (let collectionInfo of versionInfo.data) {
// Skip changes that don't concern configured blocklist bucket.
if (collectionInfo.bucket != blocklistsBucket) {
continue;
}
let collection = collectionInfo.collection;
let client = gBlocklistClients[collection];
if (client && client.maybeSync) {
if (client &&
client.bucketName == collectionInfo.bucket &&
client.maybeSync) {
let lastModified = 0;
if (collectionInfo.last_modified) {
lastModified = collectionInfo.last_modified;

View File

@ -0,0 +1,298 @@
"use strict"
const { Constructor: CC } = Components;
Cu.import("resource://testing-common/httpd.js");
const { Kinto } = Cu.import("resource://services-common/kinto-offline-client.js");
const { FirefoxAdapter } = Cu.import("resource://services-common/kinto-storage-adapter.js");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
const PREF_BLOCKLIST_PINNING_COLLECTION = "services.blocklist.pinning.collection";
const COLLECTION_NAME = "pins";
const KINTO_STORAGE_PATH = "kinto.sqlite";
// First, we need to setup appInfo or we can't do version checks
var id = "xpcshell@tests.mozilla.org";
var appName = "XPCShell";
var version = "1";
var platformVersion = "1.9.2";
Cu.import("resource://testing-common/AppInfo.jsm", this);
updateAppInfo({
name: appName,
ID: id,
version: version,
platformVersion: platformVersion ? platformVersion : "1.0",
crashReporter: true,
});
let server;
function do_get_kinto_collection(connection, collectionName) {
let config = {
// Set the remote to be some server that will cause test failure when
// hit since we should never hit the server directly (any non-local
// request causes failure in tests), only via maybeSync()
remote: "https://firefox.settings.services.mozilla.com/v1/",
// Set up the adapter and bucket as normal
adapter: FirefoxAdapter,
adapterOptions: {sqliteHandle: connection},
bucket: "pinning"
};
let kintoClient = new Kinto(config);
return kintoClient.collection(collectionName);
}
// Some simple tests to demonstrate that the core preload sync operations work
// correctly and that simple kinto operations are working as expected.
add_task(function* test_something(){
// set the collection name explicitly - since there will be version
// specific collection names in prefs
Services.prefs.setCharPref(PREF_BLOCKLIST_PINNING_COLLECTION,
COLLECTION_NAME);
const { PinningPreloadClient } = Cu.import("resource://services-common/blocklist-clients.js");
const configPath = "/v1/";
const recordsPath = "/v1/buckets/pinning/collections/pins/records";
Services.prefs.setCharPref("services.settings.server",
`http://localhost:${server.identity.primaryPort}/v1`);
// register a handler
function handleResponse (request, response) {
try {
const sample = getSampleResponse(request, server.identity.primaryPort);
if (!sample) {
do_throw(`unexpected ${request.method} request for ${request.path}?${request.queryString}`);
}
response.setStatusLine(null, sample.status.status,
sample.status.statusText);
// send the headers
for (let headerLine of sample.sampleHeaders) {
let headerElements = headerLine.split(':');
response.setHeader(headerElements[0], headerElements[1].trimLeft());
}
response.setHeader("Date", (new Date()).toUTCString());
response.write(sample.responseBody);
} catch (e) {
do_print(e);
}
}
server.registerPathHandler(configPath, handleResponse);
server.registerPathHandler(recordsPath, handleResponse);
let sss = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
// ensure our pins are all missing before we start
ok(!sss.isSecureHost(sss.HEADER_HPKP, "one.example.com", 0));
ok(!sss.isSecureHost(sss.HEADER_HPKP, "two.example.com", 0));
ok(!sss.isSecureHost(sss.HEADER_HPKP, "three.example.com", 0));
// Test an empty db populates
let result = yield PinningPreloadClient.maybeSync(2000, Date.now());
let connection = yield FirefoxAdapter.openConnection({path: KINTO_STORAGE_PATH});
// Open the collection, verify it's been populated:
// Our test data has a single record; it should be in the local collection
let collection = do_get_kinto_collection(connection, COLLECTION_NAME);
let list = yield collection.list();
do_check_eq(list.data.length, 1);
// check that a pin exists for one.example.com
ok(sss.isSecureHost(sss.HEADER_HPKP, "one.example.com", 0));
// Test the db is updated when we call again with a later lastModified value
result = yield PinningPreloadClient.maybeSync(4000, Date.now());
// Open the collection, verify it's been updated:
// Our data now has three new records; all should be in the local collection
collection = do_get_kinto_collection(connection, COLLECTION_NAME);
list = yield collection.list();
do_check_eq(list.data.length, 4);
yield connection.close();
// check that a pin exists for two.example.com and three.example.com
ok(sss.isSecureHost(sss.HEADER_HPKP, "two.example.com", 0));
ok(sss.isSecureHost(sss.HEADER_HPKP, "three.example.com", 0));
// check that a pin does not exist for four.example.com - it's in the
// collection but the version should not match
ok(!sss.isSecureHost(sss.HEADER_HPKP, "four.example.com", 0));
// Try to maybeSync with the current lastModified value - no connection
// should be attempted.
// Clear the kinto base pref so any connections will cause a test failure
Services.prefs.clearUserPref("services.settings.server");
yield PinningPreloadClient.maybeSync(4000, Date.now());
// Try again with a lastModified value at some point in the past
yield PinningPreloadClient.maybeSync(3000, Date.now());
// Check the pinning check time pref is modified, even if the collection
// hasn't changed
Services.prefs.setIntPref("services.blocklist.onecrl.checked", 0);
yield PinningPreloadClient.maybeSync(3000, Date.now());
let newValue = Services.prefs.getIntPref("services.blocklist.pinning.checked");
do_check_neq(newValue, 0);
// Check that a sync completes even when there's bad data in the
// collection. This will throw on fail, so just calling maybeSync is an
// acceptible test (the data below with last_modified of 300 is nonsense).
Services.prefs.setCharPref("services.settings.server",
`http://localhost:${server.identity.primaryPort}/v1`);
yield PinningPreloadClient.maybeSync(5000, Date.now());
});
function run_test() {
// Ensure that signature verification is disabled to prevent interference
// with basic certificate sync tests
Services.prefs.setBoolPref("services.blocklist.signing.enforced", false);
// Set up an HTTP Server
server = new HttpServer();
server.start(-1);
run_next_test();
do_register_cleanup(function() {
server.stop(() => { });
});
}
// get a response for a given request from sample data
function getSampleResponse(req, port) {
const appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
const responses = {
"OPTIONS": {
"sampleHeaders": [
"Access-Control-Allow-Headers: Content-Length,Expires,Backoff,Retry-After,Last-Modified,Total-Records,ETag,Pragma,Cache-Control,authorization,content-type,if-none-match,Alert,Next-Page",
"Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST,DELETE,OPTIONS",
"Access-Control-Allow-Origin: *",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress"
],
"status": {status: 200, statusText: "OK"},
"responseBody": "null"
},
"GET:/v1/?": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress"
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"settings":{"batch_max_requests":25}, "url":`http://localhost:${port}/v1/`, "documentation":"https://kinto.readthedocs.org/", "version":"1.5.1", "commit":"cbc6f58", "hello":"kinto"})
},
"GET:/v1/buckets/pinning/collections/pins/records?_sort=-last_modified": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"3000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data":[{
"pinType": "KeyPin",
"hostName": "one.example.com",
"includeSubdomains": false,
"expires": new Date().getTime() + 1000000,
"pins" : ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
"M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
"versions" : [appInfo.version],
"id":"78cf8900-fdea-4ce5-f8fb-b78710617718",
"last_modified":3000
}]})
},
"GET:/v1/buckets/pinning/collections/pins/records?_sort=-last_modified&_since=3000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"4000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data":[{
"pinType": "KeyPin",
"hostName": "two.example.com",
"includeSubdomains": false,
"expires": new Date().getTime() + 1000000,
"pins" : ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
"M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
"versions" : [appInfo.version],
"id":"dabafde9-df4a-ddba-2548-748da04cc02c",
"last_modified":4000
},{
"pinType": "KeyPin",
"hostName": "three.example.com",
"includeSubdomains": false,
"expires": new Date().getTime() + 1000000,
"pins" : ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
"M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
"versions" : [appInfo.version, "some other version that won't match"],
"id":"dabafde9-df4a-ddba-2548-748da04cc02d",
"last_modified":4000
},{
"pinType": "KeyPin",
"hostName": "four.example.com",
"includeSubdomains": false,
"expires": new Date().getTime() + 1000000,
"pins" : ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
"M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
"versions" : ["some version that won't match"],
"id":"dabafde9-df4a-ddba-2548-748da04cc02e",
"last_modified":4000
}]})
},
"GET:/v1/buckets/pinning/collections/pins/records?_sort=-last_modified&_since=4000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"5000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data":[{
"irrelevant":"this entry looks nothing whatsoever like a pin preload",
"pinType": "KeyPin",
"id":"dabafde9-df4a-ddba-2548-748da04cc02f",
"last_modified":5000
},{
"irrelevant":"this entry has data of the wrong type",
"pinType": "KeyPin",
"hostName": 3,
"includeSubdomains": "nonsense",
"expires": "more nonsense",
"pins" : [1,2,3,4],
"id":"dabafde9-df4a-ddba-2548-748da04cc030",
"last_modified":5000
},{
"irrelevant":"this entry is missing the actual pins",
"pinType": "KeyPin",
"hostName": "missingpins.example.com",
"includeSubdomains": false,
"expires": new Date().getTime() + 1000000,
"versions" : [appInfo.version],
"id":"dabafde9-df4a-ddba-2548-748da04cc031",
"last_modified":5000
}]})
}
};
return responses[`${req.method}:${req.path}?${req.queryString}`] ||
responses[req.method];
}

View File

@ -57,6 +57,7 @@ add_task(function* test_check_maybeSync(){
// add a test kinto client that will respond to lastModified information
// for a collection called 'test-collection'
updater.addTestBlocklistClient("test-collection", {
bucketName: "blocklists",
maybeSync(lastModified, serverTime) {
do_check_eq(lastModified, 1000);
do_check_eq(serverTime, 2000);

View File

@ -12,6 +12,7 @@ support-files =
[test_blocklist_certificates.js]
[test_blocklist_clients.js]
[test_blocklist_updater.js]
[test_blocklist_pinning.js]
[test_kinto.js]
[test_blocklist_signatures.js]

View File

@ -12,7 +12,7 @@ from .graph import Graph
from .taskgraph import TaskGraph
from .optimize import optimize_task_graph
from .util.python_path import find_object
from .util.verifydoc import verify_docs
from .util.verify import verify_docs, verify_task_graph_symbol
logger = logging.getLogger(__name__)
@ -217,6 +217,7 @@ class TaskGraphGenerator(object):
target_task_graph = TaskGraph(
{l: all_tasks[l] for l in target_graph.nodes},
target_graph)
target_task_graph.for_each_task(verify_task_graph_symbol, scratch_pad={})
yield 'target_task_graph', target_task_graph
logger.info("Generating optimized task graph")

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